diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 01:13:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 01:13:14 +0000 |
commit | 60e8a3d404f0640fa5a3f834eae54b4f1fb9127d (patch) | |
tree | 1da89a218d0ecf010c67a87cb2f625c4cb18e7d7 | |
parent | Adding upstream version 0.37.0. (diff) | |
download | mpv-upstream.tar.xz mpv-upstream.zip |
Adding upstream version 0.38.0.upstream/0.38.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
399 files changed, 16934 insertions, 11086 deletions
diff --git a/.github/ISSUE_TEMPLATE/2_bug_report_linux.md b/.github/ISSUE_TEMPLATE/2_bug_report_linux.md index 6faaf85..e8e30d1 100644 --- a/.github/ISSUE_TEMPLATE/2_bug_report_linux.md +++ b/.github/ISSUE_TEMPLATE/2_bug_report_linux.md @@ -15,7 +15,7 @@ Provide following Information: - Source of the mpv binary - If known which version of mpv introduced the problem - Window Manager and version -- GPU driver and version +- GPU model, driver and version - Possible screenshot or video of visual glitches If you're not using git master or the latest release, update. @@ -26,6 +26,10 @@ Releases are listed here: https://github.com/mpv-player/mpv/releases Try to reproduce your issue with --no-config first. If it isn't reproducible with --no-config try to first find out which option or script causes your issue. +If the issue is performance-related, try to reproduce it with --no-config +--profile=fast, which is designed to work on lower-end mobile devices. +Additionally, enable hardware decoding with --hwdec=yes or the Ctrl+h shortcut. + Describe the reproduction steps as precise as possible. It's very likely that the bug you experience wasn't reproduced by the developer because the workflow differs from your own. @@ -36,14 +40,16 @@ differs from your own. ### Log file -Make a log file made with -v -v or --log-file=output.txt, paste it to -https://0x0.st/ or attach it to the github issue, and replace this text with a -link to it. +Make a log file made with -v -v or --log-file=output.txt, attach it to +the issue, and replace this text with a link to it. + +Without the log file, this issue will be closed for ignoring the issue template. -The issue will be closed for ignoring the issue template. +In the case of a crash, please provide a backtrace. ### Sample files -Sample files needed to reproduce this issue can be uploaded to https://0x0.st/ -or similar sites. (Only needed if the issue cannot be reproduced without it.) +Sample files needed to reproduce this issue can be attached to the issue +(preferred), or be uploaded to https://0x0.st/ or similar sites. +(Only needed if the issue cannot be reproduced without it.) Do not use garbage like "cloud storage", especially not Google Drive. diff --git a/.github/ISSUE_TEMPLATE/2_bug_report_macos.md b/.github/ISSUE_TEMPLATE/2_bug_report_macos.md index 809ea39..470b4a0 100644 --- a/.github/ISSUE_TEMPLATE/2_bug_report_macos.md +++ b/.github/ISSUE_TEMPLATE/2_bug_report_macos.md @@ -24,6 +24,10 @@ Releases are listed here: https://github.com/mpv-player/mpv/releases Try to reproduce your issue with --no-config first. If it isn't reproducible with --no-config try to first find out which option or script causes your issue. +If the issue is performance-related, try to reproduce it with --no-config +--profile=fast, which is designed to work on lower-end mobile devices. +Additionally, enable hardware decoding with --hwdec=yes or the Ctrl+h shortcut. + Describe the reproduction steps as precise as possible. It's very likely that the bug you experience wasn't reproduced by the developer because the workflow differs from your own. @@ -34,18 +38,20 @@ differs from your own. ### Log file -Make a log file made with -v -v or --log-file=output.txt. If you use the Bundle -from a version later than 0.32 a default log is created for your last run at -~/Library/Logs/mpv.log. You can jump to that file via the Help > Show log File… -menu. Paste the log to https://0x0.st/ or attach it to the github issue, and -replace this text with a link to it. +Make a log file made with -v -v or --log-file=output.txt, attach it to +the issue, and replace this text with a link to it. + +If you use the Bundle, a default log is created for your last run at +~/Library/Logs/mpv.log. You can jump to that file via the +Help > Show log File… menu. -In the case of a crash please provide the macOS Crash Report (Backtrace). +Without the log file, this issue will be closed for ignoring the issue template. -The issue will be closed for ignoring the issue template. +In the case of a crash, please provide the macOS Crash Report (Backtrace). ### Sample files -Sample files needed to reproduce this issue can be uploaded to https://0x0.st/ -or similar sites. (Only needed if the issue cannot be reproduced without it.) +Sample files needed to reproduce this issue can be attached to the issue +(preferred), or be uploaded to https://0x0.st/ or similar sites. +(Only needed if the issue cannot be reproduced without it.) Do not use garbage like "cloud storage", especially not Google Drive. diff --git a/.github/ISSUE_TEMPLATE/2_bug_report_windows.md b/.github/ISSUE_TEMPLATE/2_bug_report_windows.md index b42c385..d936593 100644 --- a/.github/ISSUE_TEMPLATE/2_bug_report_windows.md +++ b/.github/ISSUE_TEMPLATE/2_bug_report_windows.md @@ -14,6 +14,7 @@ Provide following Information: - Windows Version - Source of the mpv binary - If known which version of mpv introduced the problem +- GPU model, driver and version - Possible screenshot or video of visual glitches If you're not using git master or the latest release, update. @@ -24,6 +25,10 @@ Releases are listed here: https://github.com/mpv-player/mpv/releases Try to reproduce your issue with --no-config first. If it isn't reproducible with --no-config try to first find out which option or script causes your issue. +If the issue is performance-related, try to reproduce it with --no-config +--profile=fast, which is designed to work on lower-end mobile devices. +Additionally, enable hardware decoding with --hwdec=yes or the Ctrl+h shortcut. + Describe the reproduction steps as precise as possible. It's very likely that the bug you experience wasn't reproduced by the developer because the workflow differs from your own. @@ -34,14 +39,16 @@ differs from your own. ### Log file -Make a log file made with -v -v or --log-file=output.txt, paste it to -https://0x0.st/ or attach it to the github issue, and replace this text with a -link to it. +Make a log file made with -v -v or --log-file=output.txt, attach it to +the issue, and replace this text with a link to it. + +Without the log file, this issue will be closed for ignoring the issue template. -The issue will be closed for ignoring the issue template. +In the case of a crash, please provide a backtrace. ### Sample files -Sample files needed to reproduce this issue can be uploaded to https://0x0.st/ -or similar sites. (Only needed if the issue cannot be reproduced without it.) +Sample files needed to reproduce this issue can be attached to the issue +(preferred), or be uploaded to https://0x0.st/ or similar sites. +(Only needed if the issue cannot be reproduced without it.) Do not use garbage like "cloud storage", especially not Google Drive. diff --git a/.github/ISSUE_TEMPLATE/3_bug_report.md b/.github/ISSUE_TEMPLATE/3_bug_report.md index 60df58d..e33a003 100644 --- a/.github/ISSUE_TEMPLATE/3_bug_report.md +++ b/.github/ISSUE_TEMPLATE/3_bug_report.md @@ -13,6 +13,9 @@ Provide following Information: - mpv version - Platform and Version - Source of the mpv binary +- If known which version of mpv introduced the problem +- GPU model, driver and version +- Possible screenshot or video of visual glitches If you're not using git master or the latest release, update. Releases are listed here: https://github.com/mpv-player/mpv/releases @@ -22,6 +25,10 @@ Releases are listed here: https://github.com/mpv-player/mpv/releases Try to reproduce your issue with --no-config first. If it isn't reproducible with --no-config try to first find out which option or script causes your issue. +If the issue is performance-related, try to reproduce it with --no-config +--profile=fast, which is designed to work on lower-end mobile devices. +Additionally, enable hardware decoding with --hwdec=yes or the Ctrl+h shortcut. + Describe the reproduction steps as precise as possible. It's very likely that the bug you experience wasn't reproduced by the developer because the workflow differs from your own. @@ -32,12 +39,16 @@ differs from your own. ### Log file -Make a log file made with -v -v or --log-file=output.txt, paste it to -https://0x0.st/ or attach it to the github issue, and replace this text with a -link to it. +Make a log file made with -v -v or --log-file=output.txt, attach it to +the issue, and replace this text with a link to it. + +Without the log file, this issue will be closed for ignoring the issue template. + +In the case of a crash, please provide a backtrace. ### Sample files -Sample files needed to reproduce this issue can be uploaded to https://0x0.st/ -or similar sites. (Only needed if the issue cannot be reproduced without it.) +Sample files needed to reproduce this issue can be attached to the issue +(preferred), or be uploaded to https://0x0.st/ or similar sites. +(Only needed if the issue cannot be reproduced without it.) Do not use garbage like "cloud storage", especially not Google Drive. diff --git a/.github/ISSUE_TEMPLATE/4_bug_report_build.md b/.github/ISSUE_TEMPLATE/4_bug_report_build.md index e8697f0..8370a30 100644 --- a/.github/ISSUE_TEMPLATE/4_bug_report_build.md +++ b/.github/ISSUE_TEMPLATE/4_bug_report_build.md @@ -7,7 +7,12 @@ assignees: '' --- -### mpv version and platform versions +### Important Information + +Provide following Information: +- mpv version +- Platform and Version +- meson version If you're not using git master or the latest release, update. Releases are listed here: https://github.com/mpv-player/mpv/releases @@ -24,7 +29,7 @@ differs from your own. ### Log file -Upload meson-logs/meson-log.txt or meson setup build output to https://0x0.st/ or attach -it to the github issue, and replace this text with a link to it. +Attach meson-logs/meson-log.txt or meson setup build output to the issue, +and replace this text with a link to it. -The issue will be closed for ignoring the issue template. +Without the log file, this issue will be closed for ignoring the issue template. diff --git a/.github/ISSUE_TEMPLATE/5_feature_request.md b/.github/ISSUE_TEMPLATE/5_feature_request.md index 2fba2ba..a280622 100644 --- a/.github/ISSUE_TEMPLATE/5_feature_request.md +++ b/.github/ISSUE_TEMPLATE/5_feature_request.md @@ -17,6 +17,5 @@ https://github.com/mpv-player/mpv/labels/meta%3Afeature-request ### Log file Even if you think it's not necessary at first, it might help us later to find -possible issues. Make a log file made with -v -v or --log-file=output.txt, paste -it to https://0x0.st/ or attach it to the github issue, and replace this text -with a link to it. +possible issues. Make a log file made with -v -v or --log-file=output.txt, +attach it to the issue, and replace this text with a link to it. diff --git a/.github/ISSUE_TEMPLATE/6_question.md b/.github/ISSUE_TEMPLATE/6_question.md deleted file mode 100644 index b6131f3..0000000 --- a/.github/ISSUE_TEMPLATE/6_question.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -name: 'Ask a Question' -about: 'Ask a question about mpv' -title: '' -labels: 'meta:question' -assignees: '' - ---- - -Don't ask questions about issues, errors or problems you have and instead open -a proper issue with the right template from the previous selection. - -This template is meant for questions about the workings of mpv, to clarify about -unspecified behaviour of options or command, or anything else that is not well -described and needs clarification. - -Before asking a question make sure it hasn't been asked or answered yet. -https://github.com/mpv-player/mpv/labels/meta%3Aquestion - -### Log file - -Even if you think it's not necessary at first, it might help us later to find -possible issues. Make a log file made with -v -v or --log-file=output.txt, paste -it to https://0x0.st/ or attach it to the github issue, and replace this text -with a link to it. diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ddbfd90..c949712 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,11 +9,21 @@ on: paths-ignore: - 'DOCS/**' - 'TOOLS/lua/**' + - '.editorconfig' + - '.gitignore' + - 'Copyright' + - 'README.md' + - 'RELEASE_NOTES' pull_request: branches: [master] paths-ignore: - 'DOCS/**' - 'TOOLS/lua/**' + - '.editorconfig' + - '.gitignore' + - 'Copyright' + - 'README.md' + - 'RELEASE_NOTES' jobs: mingw: @@ -27,13 +37,13 @@ jobs: matrix: target: [i686-w64-mingw32, x86_64-w64-mingw32] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Get time id: get_time run: echo "timestamp=`date +%s%N`" >> $GITHUB_OUTPUT - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: path: ${{ env.CCACHE_DIR }} key: ${{ matrix.target }}-${{ steps.get_time.outputs.timestamp }} @@ -71,7 +81,7 @@ jobs: env: WINEDEBUG: '+loaddll' - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: mpv-${{ matrix.target }} path: mpv-git-*.zip @@ -87,18 +97,33 @@ jobs: os: - "macos-12" - "macos-13" + - "macos-14" + include: + - os: "macos-12" + arch: "intel" + - os: "macos-13" + arch: "intel" + - os: "macos-14" + arch: "arm" + xcode: "Xcode_15.2" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Remove stray upstream python binary symlinks under /usr/local run: | find /usr/local/bin -lname '*/Library/Frameworks/Python.framework/*' -delete -print brew unlink python && brew link --overwrite python + - name: Change Xcode version + if: ${{ matrix.xcode != '' }} + run: | + sudo xcode-select -s /Applications/${{ matrix.xcode }}.app + - name: Install dependencies run: | brew update - brew install autoconf automake pkg-config libtool python freetype fribidi little-cms2 lua@5.1 libass ffmpeg meson libplacebo + brew install autoconf automake pkg-config libtool python freetype fribidi little-cms2 \ + luajit libass ffmpeg meson uchardet mujs libplacebo molten-vk vulkan-loader vulkan-headers - name: Build with meson id: build @@ -109,6 +134,11 @@ jobs: CXX: "${{ matrix.cxx }}" TRAVIS_OS_NAME: "${{ matrix.os }}" + - name: Create App Bundle + run: | + meson compile -C build macos-bundle + tar -czvf mpv.tar.gz -C build mpv.app + - name: Print meson log if: ${{ failure() && steps.build.outcome == 'failure' }} run: | @@ -124,6 +154,11 @@ jobs: run: | cat ./build/meson-logs/testlog.txt + - uses: actions/upload-artifact@v4 + with: + name: mpv-${{ matrix.os }}-${{ matrix.arch }} + path: mpv.tar.gz + linux: runs-on: "ubuntu-22.04" container: @@ -143,7 +178,7 @@ jobs: cxx: "clang++", } steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install dependencies run: | @@ -170,16 +205,109 @@ jobs: run: | cat ./build/meson-logs/testlog.txt + linux-ffmpeg-4-4: + runs-on: ubuntu-22.04 + container: + # We want to test ffmpeg 4.4 which alpine 3.15 has exactly. + # Furthermore, this is a very minimal build of mpv without commonly + # used features to test build compatibility. Musl is a nice bonus as well + image: alpine:3.15 + steps: + - uses: actions/checkout@v4 + - name: Install dependencies + run: | + apk update + apk add \ + binutils \ + ffmpeg-dev \ + gcc \ + git \ + g++ \ + libass-dev \ + lua5.2-dev \ + pulseaudio-dev \ + libx11-dev \ + libxfixes-dev \ + libxkbcommon-dev \ + libxrandr-dev \ + libxpresent-dev \ + libxscrnsaver-dev \ + musl-dev \ + py3-pip \ + samurai + pip install meson + + - name: Build with meson + id: build + run: | + ./ci/build-linux-old.sh + + - name: Print meson log + if: ${{ failure() && steps.build.outcome == 'failure' }} + run: | + cat ./build/meson-logs/meson-log.txt + + - name: Run meson tests + id: tests + run: | + meson test -C build + + - name: Print meson test log + if: ${{ failure() && steps.tests.outcome == 'failure' }} + run: | + cat ./build/meson-logs/testlog.txt + + openbsd: + runs-on: ubuntu-latest # until https://github.com/actions/runner/issues/385 + timeout-minutes: 30 # avoid any weirdness with the VM + steps: + - uses: actions/checkout@v4 + - name: Test in OpenBSD VM + uses: cross-platform-actions/action@v0.23.0 + with: + operating_system: openbsd + version: '7.4' + run: | + sudo pkg_add -U \ + cmake \ + ffmpeg \ + git \ + libarchive \ + libbluray \ + libcaca \ + libcdio-paranoia \ + libdvdnav \ + libiconv \ + libv4l \ + libxkbcommon \ + luajit \ + meson \ + openal \ + pkgconf \ + pulseaudio \ + python3 \ + rubberband \ + sdl2 \ + shaderc \ + spirv-cross \ + spirv-headers \ + uchardet \ + vulkan-loader \ + vulkan-headers \ + zimg + ./ci/build-openbsd.sh + meson test -C build + freebsd: runs-on: ubuntu-latest # until https://github.com/actions/runner/issues/385 timeout-minutes: 30 # avoid any weirdness with the VM steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Test in FreeBSD VM - uses: cross-platform-actions/action@v0.19.1 + uses: cross-platform-actions/action@v0.23.0 with: operating_system: freebsd - version: '13.2' + version: '14.0' run: | sudo pkg update sudo pkg install -y \ @@ -225,9 +353,7 @@ jobs: fail-fast: false matrix: sys: - - clang32 - clang64 - - mingw32 - mingw64 - ucrt64 defaults: @@ -239,7 +365,7 @@ jobs: run: | git config --global core.autocrlf false git config --global core.eol lf - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: msys2/setup-msys2@v2 with: msystem: ${{ matrix.sys }} @@ -269,7 +395,7 @@ jobs: spirv-cross:p uchardet:p vapoursynth:p - vulkan:p + vulkan-devel:p - name: Build with meson id: build diff --git a/.github/workflows/comment.yml b/.github/workflows/comment.yml index 7f9603a..c80f1d8 100644 --- a/.github/workflows/comment.yml +++ b/.github/workflows/comment.yml @@ -45,6 +45,13 @@ jobs: body += `\n* [${art.name}](${art_link})`; } } + body += `\n</details>\n\n<details><summary>macOS</summary>\n`; + for (const art of artifacts) { + const art_link = `https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip`; + if (art.name.includes('macos')) { + body += `\n* [${art.name}](${art_link})`; + } + } body += `\n</details>`; const { data: comments } = await github.rest.issues.listComments({ repo, owner, issue_number }); diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index d1fcb5c..3936769 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -17,8 +17,10 @@ jobs: check-docs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Docs run: | - sudo apt-get install python3-docutils - rst2man --strip-elements-with-class=contents --halt=2 ./DOCS/man/mpv.rst mpv.1 + sudo apt-get install python3-docutils rst2pdf + ./TOOLS/docutils-wrapper.py rst2man --strip-elements-with-class=contents --halt=2 ./DOCS/man/mpv.rst mpv.1 + ./TOOLS/docutils-wrapper.py rst2html --halt=2 ./DOCS/man/mpv.rst mpv.html + ./TOOLS/docutils-wrapper.py rst2pdf -c -b 1 --repeat-table-rows ./DOCS/man/mpv.rst -o mpv.pdf diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 939255e..4a881c2 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -13,7 +13,7 @@ jobs: commit-msg: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 50 diff --git a/DOCS/client-api-changes.rst b/DOCS/client-api-changes.rst index f092647..7d34881 100644 --- a/DOCS/client-api-changes.rst +++ b/DOCS/client-api-changes.rst @@ -31,7 +31,12 @@ API changes =========== :: - + --- mpv 0.38.0 --- + 2.3 - partially revert the changes from API version 1.27, remove libmpv as + the default VO and move it to the bottom of the auto-probing order. + This restores the prior behavior on all platforms other than macOS, + but still auto selects libmpv/cocoa-cb on macOS if it was built with + support for cocoa-cb. --- mpv 0.37.0 --- 2.2 - add mpv_time_ns() --- mpv 0.36.0 --- @@ -273,7 +278,7 @@ API changes - extend the "--start" option; a leading "+", which was previously insignificant is now significant - add "cache-free" and "cache-used" properties - - OSX: the "coreaudio" AO spdif code is split into a separate AO + - macOS: the "coreaudio" AO spdif code is split into a separate AO --- mpv 0.4.0 --- 1.0 - the API is declared stable diff --git a/DOCS/contribute.md b/DOCS/contribute.md index 91422a7..4d2af4e 100644 --- a/DOCS/contribute.md +++ b/DOCS/contribute.md @@ -5,13 +5,13 @@ General ------- The main contact for mpv development is IRC, specifically #mpv -and #mpv-devel on Libera.chat. Github is used for code review and +and #mpv-devel on Libera.chat. GitHub is used for code review and long term discussions. Sending patches --------------- -- Make a github pull request, or send a link to a plaintext patch created with +- Make a GitHub pull request, or send a link to a plaintext patch created with ``git format-patch``. - Plain diffs posted as pastebins are not acceptable! (Especially if the http link returns HTML.) They only cause extra work for everyone, because they lack @@ -111,13 +111,13 @@ Always squash fixup commits when making changes to pull requests - If you make fixup commits to your pull request, you should generally squash them with "git rebase -i". We prefer to have pull requests in a merge ready state. -- We don't squash-merge (nor do we use github's feature that does this) because +- We don't squash-merge (nor do we use GitHub's feature that does this) because pull requests with multiple commits are perfectly legitimate, and the only thing that makes sense in non-trivial cases. - With complex pull requests, it *may* make sense to keep them separate, but they should be clearly marked as such. Reviewing commits is generally easier with fixups squashed. -- Reviewers are encouraged to look at individual commits instead of github's +- Reviewers are encouraged to look at individual commits instead of GitHub's "changes from all commits" view (which just encourages bad git and review practices). @@ -131,12 +131,24 @@ Touching user-visible parts may require updating the mpv docs - Changes to command line options (addition/modification/removal) must be documented in options.rst. - Changes to input properties or input commands must be documented in input.rst. -- All incompatible changes to the user interface (options, properties, commands) - must be documented with a small note in interface-changes.rst. (Additions may - be documented there as well, but this isn't required.) - Changes to the libmpv API must be reflected in the libmpv's headers doxygen, and in client-api-changes.rst. +Interface change policy +----------------------- + +- All incompatible changes to the user interface (options, properties, commands) + must be documented by making a new text file with a txt extension containing a + small note in the DOCS/interface-changes directory. +- The name of the file should be brief and related to the commit that makes the + change. +- Grouping multiple related changes in the same file is also OK. Just be sure to + put each separate change on a different line. +- Documenting additions in DOCS/interface-changes is optional but encouraged. +- interface-changes.rst is never directly updated except when making new major + releases. +- See DOCS/interface-changes/example.txt for an example. + Code formatting --------------- diff --git a/DOCS/interface-changes.rst b/DOCS/interface-changes.rst index f59f890..b55ef97 100644 --- a/DOCS/interface-changes.rst +++ b/DOCS/interface-changes.rst @@ -17,15 +17,65 @@ a large part of the user interface and APIs. Also see compatibility.rst. -This document lists changes to them. New changes are added to the top. Usually, -only incompatible or important changes are mentioned. New options/commands/etc. -are not always listed. +This document lists changes to them. New options/commands/etc. are not always +listed. + +**Never** write to this file directly except when making releases. New changes +are added in the interface-changes directory instead. See contribute.md for more +details. Interface changes ================= :: + --- mpv 0.38.0 --- + - add `term-size` property + - add the `escape-ass` command + - add `>` for fixed precision floating-point property expansion + - add `--input-comands` option + - change `--pulse-latency-hacks` default to `yes` + - add `context-menu` command + - add `menu-data` property + - add `--vo-tct-buffering` option + - add `begin-vo-dragging` command + - add `--deinterlace-field-parity` option + - add `--volume-gain`, `--volume-gain-min`, and `--volume-gain-max` options + - add `current-gpu-context` property + - add `--secondary-sub-ass-override` option + - add `--input-preprocess-wheel` option + - remove shared-script-properties (user-data is a replacement) + - add `--secondary-sub-delay`, decouple secondary subtitles from + `--sub-delay` + - add the `--osd-bar-border-size` option + - `--screenshot-avif-pixfmt` no longer defaults to yuv420p + - `--screenshot-avif-opts` defaults to lossless screenshot + - rename key `MP_KEY_BACK` to `MP_KEY_GO_BACK` + - add `--sub-filter-sdh-enclosures` option + - added the `mp.input` scripting API to query the user for textual input + - add `forced` choice to `subs-with-matching-audio` + - remove `--term-remaining-playtime` option + - change fallback deinterlace to bwdif + - add the command `load-config-file` + - add the command `load-input-conf` + - remove `--vo=rpi`, `--gpu-context=rpi`, and `--hwdec=mmal` + - add `auto` choice to `--deinterlace` + - change `--teletext-page` default from 100 to 0 ("subtitle" in lavc) + - change `--hidpi-window-scale` default to `no` + - add `insert-next`, `insert-next-play`, `insert-at`, and `insert-at-play` + actions to `loadfile` and `loadlist` commands + - add `index` argument to `loadfile` and `loadlist` commands + - move the `options` argument of the `loadfile` command from the third + parameter to the fourth (after `index`) + - add `--drag-and-drop=insert-next` option + - rename `--background` to `--background-color` + - remove `--alpha` and reintroduce `--background` option for better control + over blending alpha components into specific background types + - add `--border-background` option + - add `video-target-params` property + - add `hdr10plus` sub-parameter to `format` video filter + - remove `--focus-on-open` and add replacement `--focus-on` + - remove debanding from the high-quality profile --- mpv 0.37.0 --- - `--save-position-on-quit` and its associated commands now store state files in %LOCALAPPDATA% instead of %APPDATA% directory by default on Windows. @@ -239,7 +289,7 @@ Interface changes - names starting with "." in ~/.mpv/scripts/ (or equivalent) are now ignored - js modules: ~~/scripts/modules.js/ is no longer used, global paths can be set with custom init (see docs), dir-scripts first look at <dir>/modules/ - - the OSX bundle now logs to "~/Library/Logs/mpv.log" by default + - the macOS bundle now logs to "~/Library/Logs/mpv.log" by default - deprecate the --cache-secs option (once removed, the cache cannot be limited by time anymore) - remove deprecated legacy hook API ("hook-add", "hook-ack"). Use either the diff --git a/DOCS/interface-changes/example.txt b/DOCS/interface-changes/example.txt new file mode 100644 index 0000000..c52ee01 --- /dev/null +++ b/DOCS/interface-changes/example.txt @@ -0,0 +1 @@ +deprecate `--foo`, instead use `--bar=foo` as a replacement diff --git a/DOCS/man/ao.rst b/DOCS/man/ao.rst index 4e4e454..4ac1cc5 100644 --- a/DOCS/man/ao.rst +++ b/DOCS/man/ao.rst @@ -12,16 +12,59 @@ in the list. .. note:: - See ``--ao=help`` for a list of compiled-in audio output drivers. The - driver ``--ao=alsa`` is preferred. ``--ao=pulse`` is preferred on systems - where PulseAudio is used. On BSD systems, ``--ao=oss`` is preferred. + See ``--ao=help`` for a list of compiled-in audio output drivers sorted by + autoprobe order. Available audio output drivers are: -``alsa`` (Linux only) - ALSA audio output driver +``alsa`` + ALSA audio output driver. - See `ALSA audio output options`_ for options specific to this AO. + The following global options are supported by this audio output: + + ``--alsa-resample=yes`` + Enable ALSA resampling plugin. (This is disabled by default, because + some drivers report incorrect audio delay in some cases.) + + ``--alsa-mixer-device=<device>`` + Set the mixer device used with ``ao-volume`` (default: ``default``). + + ``--alsa-mixer-name=<name>`` + Set the name of the mixer element (default: ``Master``). This is for + example ``PCM`` or ``Master``. + + ``--alsa-mixer-index=<number>`` + Set the index of the mixer channel (default: 0). Consider the output of + "``amixer scontrols``", then the index is the number that follows the + name of the element. + + ``--alsa-non-interleaved`` + Allow output of non-interleaved formats (if the audio decoder uses + this format). Currently disabled by default, because some popular + ALSA plugins are utterly broken with non-interleaved formats. + + ``--alsa-ignore-chmap`` + Don't read or set the channel map of the ALSA device - only request the + required number of channels, and then pass the audio as-is to it. This + option most likely should not be used. It can be useful for debugging, + or for static setups with a specially engineered ALSA configuration (in + this case you should always force the same layout with ``--audio-channels``, + or it will work only for files which use the layout implicit to your + ALSA device). + + ``--alsa-buffer-time=<microseconds>`` + Set the requested buffer time in microseconds. A value of 0 skips requesting + anything from the ALSA API. This and the ``--alsa-periods`` option uses the + ALSA ``near`` functions to set the requested parameters. If doing so results + in an empty configuration set, setting these parameters is skipped. + + Both options control the buffer size. A low buffer size can lead to higher + CPU usage and audio dropouts, while a high buffer size can lead to higher + latency in volume changes and other filtering. + + ``--alsa-periods=<number>`` + Number of periods requested from the ALSA API. See ``--alsa-buffer-time`` + for further remarks. .. warning:: @@ -95,11 +138,21 @@ Available audio output drivers are: passthrough (even if the device reports it as supported). Use with extreme care. - ``coreaudio_exclusive`` (macOS only) Native macOS audio output driver using direct device access and exclusive mode (bypasses the sound server). +``avfoundation`` (macOS only) + Native macOS audio output driver using ``AVSampleBufferAudioRenderer`` + in AVFoundation, which supports `spatial audio + <https://support.apple.com/en-us/HT211775>`_. + + .. warning:: + + Turning on spatial audio may hang the playback + if mpv is not started out of the bundle, + though playback with spatial audio off always works. + ``openal`` OpenAL audio output driver. @@ -131,15 +184,12 @@ Available audio output drivers are: changes. "native" lets the sound server determine buffers. ``--pulse-latency-hacks=<yes|no>`` - Enable hacks to workaround PulseAudio timing bugs (default: no). If + Enable hacks to workaround PulseAudio timing bugs (default: yes). If enabled, mpv will do elaborate latency calculations on its own. If disabled, it will use PulseAudio automatically updated timing information. Disabling this might help with e.g. networked audio or some plugins, while enabling it might help in some unknown situations - (it used to be required to get good behavior on old PulseAudio versions). - - If you have stuttering video when using pulse, try to enable this - option. (Or try to update PulseAudio.) + (it is currently enabled due to known bugs with PulseAudio 16.0). ``--pulse-allow-suspended=<yes|no>`` Allow mpv to use PulseAudio even if the sink is suspended (default: no). @@ -167,8 +217,8 @@ Available audio output drivers are: By default the channel volumes are used. ``sdl`` - SDL 1.2+ audio output driver. Should work on any platform supported by SDL - 1.2, but may require the ``SDL_AUDIODRIVER`` environment variable to be set + SDL 2.0+ audio output driver. Should work on any platform supported by SDL + 2.0, but may require the ``SDL_AUDIODRIVER`` environment variable to be set appropriately for your system. .. note:: This driver is for compatibility with extremely foreign diff --git a/DOCS/man/changes.rst b/DOCS/man/changes.rst index 63de41c..3f5e0e1 100644 --- a/DOCS/man/changes.rst +++ b/DOCS/man/changes.rst @@ -10,8 +10,7 @@ There is no real changelog, but you can look at the following things: * The git log, which is the "real" changelog * The file https://github.com/mpv-player/mpv/blob/master/DOCS/interface-changes.rst documents changes to the command and user interface, such as options and - properties. (It usually documents breaking changes only, additions and - enhancements are often not listed.) + properties. * C API changes are listed in https://github.com/mpv-player/mpv/blob/master/DOCS/client-api-changes.rst * The file ``mplayer-changes.rst`` in the ``DOCS`` sub directory on the git diff --git a/DOCS/man/console.rst b/DOCS/man/console.rst index 49502b3..b9f169f 100644 --- a/DOCS/man/console.rst +++ b/DOCS/man/console.rst @@ -86,7 +86,11 @@ Shift+INSERT Paste text (uses the primary selection on X11 and Wayland). TAB and Ctrl+i - Complete the command or property name at the cursor. + Complete the text at the cursor. The first press inserts the longest common + prefix of the completions, and subsequent presses cycle through them. + +Shift+TAB + Cycle through the completions backwards. Ctrl+l Clear all log messages from the console. @@ -154,6 +158,11 @@ Configurable Options Set the font border size used for the REPL and the console. +``case_sensitive`` + Default: no on Windows, yes on other platforms. + + Whether Tab completion is case sensitive. Only works with ASCII characters. + ``history_dedup`` Default: true @@ -161,7 +170,8 @@ Configurable Options multiplied by "scale." ``font_hw_ratio`` - Default: 2.0 + Default: auto The ratio of font height to font width. Adjusts table width of completion suggestions. + Values in the range 1.8..2.5 make sense for common monospace fonts. diff --git a/DOCS/man/encode.rst b/DOCS/man/encode.rst index 399eba2..26f3d6c 100644 --- a/DOCS/man/encode.rst +++ b/DOCS/man/encode.rst @@ -77,9 +77,8 @@ You can encode files from one format/codec to another using this facility. and all pts are passed through as-is. Never seek backwards or use multiple input files in this mode! -``--no-ocopy-metadata`` - Turns off copying of metadata from input files to output files when - encoding (which is enabled by default). +``--ocopy-metadata=<yes|no>`` + Copy metadata from input files to output files when encoding (default: yes). ``--oset-metadata=<metadata-tag[,metadata-tag,...]>`` Specifies metadata to include in the output file. diff --git a/DOCS/man/input.rst b/DOCS/man/input.rst index 8dbf58b..6d6b64c 100644 --- a/DOCS/man/input.rst +++ b/DOCS/man/input.rst @@ -49,8 +49,8 @@ input.conf syntax ``[Shift+][Ctrl+][Alt+][Meta+]<key> [{<section>}] <command> ( ; <command> )*`` Note that by default, the right Alt key can be used to create special -characters, and thus does not register as a modifier. The option -``--no-input-right-alt-gr`` changes this behavior. +characters, and thus does not register as a modifier. This can be changed +with ``--input-right-alt-gr`` option. Newlines always start a new binding. ``#`` starts a comment (outside of quoted string arguments). To bind commands to the ``#`` key, ``SHARP`` can be used. @@ -264,7 +264,7 @@ Remember to quote string arguments in input.conf (see `Flat command syntax`_). ``ignore`` Use this to "block" keys that should be unbound, and do nothing. Useful for disabling default bindings, without disabling all bindings with - ``--no-input-default-bindings``. + ``--input-default-bindings=no``. ``seek <target> [<flags>]`` Change the playback position. By default, seeks by a relative amount of @@ -452,9 +452,9 @@ Remember to quote string arguments in input.conf (see `Flat command syntax`_). restarted if for example the new playlist entry is the same as the previous one. -``loadfile <url> [<flags> [<options>]]`` +``loadfile <url> [<flags> [<index> [<options>]]]`` Load the given file or URL and play it. Technically, this is just a playlist - manipulation command (which either replaces the playlist or appends an entry + manipulation command (which either replaces the playlist or adds an entry to it). Actual file loading happens independently. For example, a ``loadfile`` command that replaces the current file with a new one returns before the current file is stopped, and the new file even begins loading. @@ -469,15 +469,34 @@ Remember to quote string arguments in input.conf (see `Flat command syntax`_). Append the file, and if nothing is currently playing, start playback. (Always starts with the added file, even if the playlist was not empty before running this command.) - - The third argument is a list of options and values which should be set + <insert-next> + Insert the file into the playlist, directly after the current entry. + <insert-next-play> + Insert the file next, and if nothing is currently playing, start playback. + (Always starts with the added file, even if the playlist was not empty + before running this command.) + <insert-at> + Insert the file into the playlist, at the index given in the third + argument. + <insert-at-play> + Insert the file at the index given in the third argument, and if nothing + is currently playing, start playback. (Always starts with the added + file, even if the playlist was not empty before running this command.) + + The third argument is an insertion index, used only by the ``insert-at`` and + ``insert-at-play`` actions. When used with those actions, the new item will + be inserted at the index position in the playlist, or appended to the end if + index is less than 0 or greater than the size of the playlist. This argument + will be ignored for all other actions. + + The fourth argument is a list of options and values which should be set while the file is playing. It is of the form ``opt1=value1,opt2=value2,..``. When using the client API, this can be a ``MPV_FORMAT_NODE_MAP`` (or a Lua table), however the values themselves must be strings currently. These options are set during playback, and restored to the previous value at end of playback (see `Per-File Options`_). -``loadlist <url> [<flags>]`` +``loadlist <url> [<flags> [<index>]]`` Load the given playlist file or URL (like ``--playlist``). Second argument: @@ -490,6 +509,26 @@ Remember to quote string arguments in input.conf (see `Flat command syntax`_). Append the new playlist, and if nothing is currently playing, start playback. (Always starts with the new playlist, even if the internal playlist was not empty before running this command.) + <insert-next> + Insert the new playlist into the current internal playlist, directly + after the current entry. + <insert-next-play> + Insert the new playlist, and if nothing is currently playing, start + playback. (Always starts with the new playlist, even if the internal + playlist was not empty before running this command.) + <insert-at> + Insert the new playlist at the index given in the third argument. + <insert-at-play> + Insert the new playlist at the index given in the third argument, and if + nothing is currently playing, start playback. (Always starts with the + new playlist, even if the internal playlist was not empty before running + this command.) + + The third argument is an insertion index, used only by the ``insert-at`` and + ``insert-at-play`` actions. When used with those actions, the new playlist + will be inserted at the index position in the internal playlist, or appended + to the end if index is less than 0 or greater than the size of the internal + playlist. This argument will be ignored for all other actions. ``playlist-clear`` Clear the playlist, except the currently played file. @@ -655,7 +694,7 @@ Remember to quote string arguments in input.conf (see `Flat command syntax`_). wasn't started in detached mode, even if ``playback_only`` is false. - .. admonition:: Warning + .. warning:: Don't forget to set the ``playback_only`` field to false if you want the command to run while the player is in idle mode, or if you don't @@ -827,11 +866,13 @@ Remember to quote string arguments in input.conf (see `Flat command syntax`_). <double> The mouse event represents double-click. -``keypress <name>`` +``keypress <name> [<scale>]`` Send a key event through mpv's input handler, triggering whatever behavior is configured to that key. ``name`` uses the ``input.conf`` - naming scheme for keys and modifiers. Useful for the client API: key events - can be sent to libmpv to handle internally. + naming scheme for keys and modifiers. ``scale`` is used to scale numerical + change effected by the bound command (same mechanism as precise scrolling). + Useful for the client API: key events can be sent to libmpv to handle + internally. ``keydown <name>`` Similar to ``keypress``, but sets the ``KEYDOWN`` flag so that if the key is @@ -886,6 +927,9 @@ Remember to quote string arguments in input.conf (see `Flat command syntax`_). <keep-selection> Do not change current track selections. +``context-menu`` + Show context menu on the video window. See `Context Menu`_ section for details. + Input Commands that are Possibly Subject to Change -------------------------------------------------- @@ -1033,7 +1077,7 @@ Input Commands that are Possibly Subject to Change information about the key state. The special key name ``unmapped`` can be used to match any unmapped key. -``overlay-add <id> <x> <y> <file> <offset> <fmt> <w> <h> <stride>`` +``overlay-add <id> <x> <y> <file> <offset> <fmt> <w> <h> <stride> <dw> <dh>`` Add an OSD overlay sourced from raw data. This might be useful for scripts and applications controlling mpv, and which want to display things on top of the video window. @@ -1091,6 +1135,11 @@ Input Commands that are Possibly Subject to Change (Technically, the minimum size would be ``stride * (h - 1) + w * 4``, but for simplicity, the player will access all ``stride * h`` bytes.) + ``dw`` and ``dh`` specify the (optional) display size of the overlay. + The overlay visible portion of the overlay (``w`` and ``h``) is scaled to + in display to ``dw`` and ``dh``. If parameters are not present, the + values for ``w`` and ``h`` are used. + .. note:: Before mpv 0.18.1, you had to do manual "double buffering" when updating @@ -1213,6 +1262,18 @@ Input Commands that are Possibly Subject to Change use the ``mp.create_osd_overlay()`` helper instead of invoking this command directly. +``escape-ass <text>`` + Modify ``text`` so that commands and functions that interpret ASS tags, + such as ``osd-overlay`` and ``mp.create_osd_overlay``, will display it + verbatim, and return it. This can only be used through the client API or + from a script using ``mp.command_native``. + + .. admonition:: Example + + ``mp.osd_message(mp.command_native({"escape-ass", "foo {bar}"}))`` + + This line of Lua prints "foo \\{bar}" on the OSD. + ``script-message [<arg1> [<arg2> [...]]]`` Send a message to all clients, and pass it the following list of arguments. What this message means, how many arguments it takes, and what the arguments @@ -1329,6 +1390,16 @@ Input Commands that are Possibly Subject to Change relevant mode. Prints a warning if nothing could be done. See `Runtime profiles`_ for details. +``load-config-file <filename>`` + Load a configuration file, similar to the ``--include`` option. If the file + was already included, its previous options are not reset before it is + reparsed. + +``load-input-conf <filename>`` + Load an input configuration file, similar to the ``--input-conf`` option. If + the file was already included, its previous bindings are not reset before it + is reparsed. + ``load-script <filename>`` Load a script, similar to the ``--script`` option. Whether this waits for the script to finish initialization or not changed multiple times, and the @@ -1422,6 +1493,13 @@ Input Commands that are Possibly Subject to Change This command has an even more uncertain future than ``ab-loop-dump-cache`` and might disappear without replacement if the author decides it's useless. +``begin-vo-dragging`` + Begin window dragging if supported by the current VO. This command should + only be called while a mouse button is being pressed, otherwise it will + be ignored. The exact effect of this command depends on the VO implementation + of window dragging. For example, on Windows only the left mouse button can + begin window dragging, while X11 and Wayland allow other mouse buttons. + Undocumented commands: ``ao-reload`` (experimental/internal). List of events @@ -2157,6 +2235,11 @@ Property list ``af-metadata/<filter-label>`` Equivalent to ``vf-metadata/<filter-label>``, but for audio filters. +``deinterlace-active`` + Returns ``yes``/true if mpv's deinterlacing filter is active. Note that it + will not detect any manually inserted deinterlacing filters done via + ``--vf``. + ``idle-active`` Returns ``yes``/true if no file is loaded, but the player is staying around because of the ``--idle`` option. @@ -2318,12 +2401,6 @@ Property list Similar to ``ao-volume``, but controls the mute state. May be unimplemented even if ``ao-volume`` works. -``audio-codec`` - Audio codec selected for decoding. - -``audio-codec-name`` - Audio codec. - ``audio-params`` Audio format as output by the audio decoder. This has a number of sub-properties: @@ -2407,12 +2484,6 @@ Property list multiple interop drivers for the same hardware decoder, depending on platform and VO. -``video-format`` - Video format as string. - -``video-codec`` - Video codec selected for decoding. - ``width``, ``height`` Video size. This uses the size of the video as decoded, or if no video frame has been decoded yet, the (possibly incorrect) container indicated @@ -2579,6 +2650,11 @@ Property list Has the same sub-properties as ``video-params``. +``video-target-params`` + Same as ``video-params``, but with the target properties that VO outputs to. + + Has the same sub-properties as ``video-params``. + ``video-frame-info`` Approximate information of the current frame. Note that if any of these are used on OSD, the information might be off by a few frames due to OSD @@ -2661,7 +2737,10 @@ Property list in the list will be the one that Windows considers associated with the window (as determined by the MonitorFromWindow API.) On macOS these are the Display Product Names as used in the System Information and only one display - name is returned since a window can only be on one screen. + name is returned since a window can only be on one screen. On Wayland, these + are the wl_output names if protocol version >= 4 is used + (LVDS-1, HDMI-A-1, X11-1, etc.), or the wl_output model reported by the + geometry event if protocol version < 4 is used. ``display-fps`` The refresh rate of the current display. Currently, this is the lowest FPS @@ -2728,6 +2807,19 @@ Property list Any of these properties may be unavailable or set to dummy values if the VO window is not created or visible. +``term-size`` + The current terminal size. + + This has two sub-properties. + + ``term-size/w`` + width of the terminal in cells + ``term-size/h`` + height of the terminal in cells + + This property is not observable. Reacting to size changes requires + polling. + ``window-id`` Read-only - mpv's window id. May not always be available, i.e due to window not being opened yet or not being supported by the VO. @@ -2953,6 +3045,13 @@ Property list The codec name used by this track, for example ``h264``. Unavailable in some rare cases. + ``track-list/N/codec-desc`` + The codec descriptive name used by this track. + + ``track-list/N/codec-profile`` + The codec profile used by this track. Available only if the track has + been already decoded. + ``track-list/N/external`` ``yes``/true if the track is an external file, ``no``/false or unavailable otherwise. This is set for separate subtitle files. @@ -3013,6 +3112,11 @@ Property list ``track-list/N/demux-par`` Pixel aspect ratio. + ``track-list/N/format-name`` + Short name for format from ffmpeg. If the track is audio, this will be + the name of the sample format. If the track is video, this will be the + name of the pixel format. + ``track-list/N/audio-channels`` (deprecated) Deprecated alias for ``track-list/N/demux-channel-count``. @@ -3048,6 +3152,8 @@ Property list "external" MPV_FORMAT_FLAG "external-filename" MPV_FORMAT_STRING "codec" MPV_FORMAT_STRING + "codec-desc" MPV_FORMAT_STRING + "codec-profile" MPV_FORMAT_STRING "ff-index" MPV_FORMAT_INT64 "decoder-desc" MPV_FORMAT_STRING "demux-w" MPV_FORMAT_INT64 @@ -3325,39 +3431,19 @@ Property list ``current-vo`` Current video output driver (name as used with ``--vo``). +``current-gpu-context`` + Current GPU context of video output driver (name as used with ``--gpu-context``). + Valid for ``--vo=gpu`` and ``--vo=gpu-next``. + ``current-ao`` Current audio output driver (name as used with ``--ao``). -``shared-script-properties`` (RW) - This is a key/value map of arbitrary strings shared between scripts for - general use. The player itself does not use any data in it (although some - builtin scripts may). The property is not preserved across player restarts. - - This is very primitive, inefficient, and annoying to use. It's a makeshift - solution which could go away any time (for example, when a better solution - becomes available). This is also why this property has an annoying name. You - should avoid using it, unless you absolutely have to. - - Lua scripting has helpers starting with ``utils.shared_script_property_``. - They are undocumented because you should not use this property. If you still - think you must, you should use the helpers instead of the property directly. - - You are supposed to use the ``change-list`` command to modify the contents. - Reading, modifying, and writing the property manually could data loss if two - scripts update different keys at the same time due to lack of - synchronization. The Lua helpers take care of this. - - (There is no way to ensure synchronization if two scripts try to update the - same key at the same time.) - ``user-data`` (RW) This is a recursive key/value map of arbitrary nodes shared between clients for general use (i.e. scripts, IPC clients, host applications, etc). The player itself does not use any data in it (although some builtin scripts may). The property is not preserved across player restarts. - This is a more powerful replacement for ``shared-script-properties``. - Sub-paths can be accessed directly; e.g. ``user-data/my-script/state/a`` can be read, written, or observed. @@ -3368,6 +3454,48 @@ Property list and not using raw mode, the underlying content will be given (e.g. strings will be printed directly, rather than quoted and JSON-escaped). +``menu-data`` (RW) + This property stores the raw menu definition. See `Context Menu`_ section for details. + + ``type`` + Menu item type. Can be: ``separator``, ``submenu``, or empty. + + ``title`` + Menu item title. Required if type is not ``separator``. + + ``cmd`` + Command to execute when the menu item is clicked. + + ``shortcut`` + Menu item shortcut key which appears to the right of the menu item. + A shortcut key does not have to be functional; it's just a visual hint. + + ``state`` + Menu item state. Can be: ``checked``, ``disabled``, ``hidden``, or empty. + + ``submenu`` + Submenu items, which is required if type is ``submenu``. + + When querying the property with the client API using ``MPV_FORMAT_NODE``, or with + Lua ``mp.get_property_native``, this will return a mpv_node with the following + contents: + + :: + + MPV_FORMAT_NODE_ARRAY + MPV_FORMAT_NODE_MAP (menu item) + "type" MPV_FORMAT_STRING + "title" MPV_FORMAT_STRING + "cmd" MPV_FORMAT_STRING + "shortcut" MPV_FORMAT_STRING + "state" MPV_FORMAT_NODE_ARRAY[MPV_FORMAT_STRING] + "submenu" MPV_FORMAT_NODE_ARRAY[menu item] + + Writing to this property with the client API using ``MPV_FORMAT_NODE`` or with + Lua ``mp.set_property_native`` will trigger an immediate update of the menu if + mpv video output is currently active. You may observe the ``current-vo`` + property to check if this is the case. + ``working-directory`` The working directory of the mpv process. Can be useful for JSON IPC users, because the command line player usually works with relative paths. @@ -3430,9 +3558,8 @@ Property list ``ffmpeg-version`` The contents of the ``av_version_info()`` API call. This is a string which identifies the build in some way, either through a release version number, - or a git hash. This applies to Libav as well (the property is still named - the same.) This property is unavailable if mpv is linked against older - FFmpeg and Libav versions. + or a git hash. This property is unavailable if mpv is linked against older + FFmpeg versions. ``libass-version`` The value of ``ass_library_version()``. This is an integer, encoded in a @@ -3681,7 +3808,9 @@ Normally, properties are formatted as human-readable text, meant to be displayed on OSD or on the terminal. It is possible to retrieve an unformatted (raw) value from a property by prefixing its name with ``=``. These raw values can be parsed by other programs and follow the same conventions as the options -associated with the properties. +associated with the properties. Additionally, there is a ``>`` prefix to format +human-readable text, with fixed precision for floating-point values. This is +useful for printing values where a constant width is important. .. admonition:: Examples @@ -3690,6 +3819,10 @@ associated with the properties. - ``${=time-pos}`` expands to ``863.4`` (same time, plus 400 milliseconds - milliseconds are normally not shown in the formatted case) + - ``${avsync}`` expands to ``+0.003`` + - ``${>avsync}`` expands to ``+0.0030`` + - ``${=avsync}`` expands to ``0.003028`` + Sometimes, the difference in amount of information carried by raw and formatted property values can be rather big. In some cases, raw values have more information, like higher precision than seconds with ``time-pos``. Sometimes diff --git a/DOCS/man/javascript.rst b/DOCS/man/javascript.rst index bdbb04b..0edb01f 100644 --- a/DOCS/man/javascript.rst +++ b/DOCS/man/javascript.rst @@ -27,16 +27,17 @@ otherwise, the documented Lua options, script directories, loading, etc apply to JavaScript files too. Script initialization and lifecycle is the same as with Lua, and most of the Lua -functions at the modules ``mp``, ``mp.utils``, ``mp.msg`` and ``mp.options`` are -available to JavaScript with identical APIs - including running commands, -getting/setting properties, registering events/key-bindings/hooks, etc. +functions in the modules ``mp``, ``mp.utils``, ``mp.msg``, ``mp.options`` and +``mp.input`` are available to JavaScript with identical APIs - including running +commands, getting/setting properties, registering events/key-bindings/hooks, +etc. Differences from Lua -------------------- -No need to load modules. ``mp``, ``mp.utils``, ``mp.msg`` and ``mp.options`` -are preloaded, and you can use e.g. ``var cwd = mp.utils.getcwd();`` without -prior setup. +No need to load modules. ``mp``, ``mp.utils``, ``mp.msg``, ``mp.options`` and +``mp.input`` are preloaded, and you can use e.g. ``var cwd = +mp.utils.getcwd();`` without prior setup. Errors are slightly different. Where the Lua APIs return ``nil`` for error, the JavaScript ones return ``undefined``. Where Lua returns ``something, error`` @@ -195,6 +196,16 @@ meta-paths like ``~~/foo`` (other JS file functions do expand meta paths). ``mp.options.read_options(obj [, identifier [, on_update]])`` (types: string/boolean/number) +``mp.input.get(obj)`` (LE) + +``mp.input.terminate()`` + +``mp.input.log(message, style)`` + +``mp.input.log_error(message)`` + +``mp.input.set_log(log)`` + Additional utilities -------------------- diff --git a/DOCS/man/lua.rst b/DOCS/man/lua.rst index 5708e19..7377696 100644 --- a/DOCS/man/lua.rst +++ b/DOCS/man/lua.rst @@ -308,16 +308,17 @@ The ``mp`` module is preloaded, although it can be loaded manually with ``repeatable`` If set to ``true``, enables key repeat for this specific binding. + This option only makes sense when ``complex`` is not set to ``true``. ``complex`` - If set to ``true``, then ``fn`` is called on both key up and down - events (as well as key repeat, if enabled), with the first - argument being a table. This table has the following entries (and - may contain undocumented ones): + If set to ``true``, then ``fn`` is called on key down, repeat and up + events, with the first argument being a table. This table has the + following entries (and may contain undocumented ones): ``event`` Set to one of the strings ``down``, ``repeat``, ``up`` or - ``press`` (the latter if key up/down can't be tracked). + ``press`` (the latter if key up/down/repeat can't be + tracked). ``is_mouse`` Boolean Whether the event was caused by a mouse button. @@ -426,9 +427,9 @@ The ``mp`` module is preloaded, although it can be loaded manually with This depends on the property, and it's a valid feature request to ask for better update handling of a specific property. - If the ``type`` is ``none`` or ``nil``, sporadic property change events are - possible. This means the change function ``fn`` can be called even if the - property doesn't actually change. + If the ``type`` is ``none`` or ``nil``, the change function ``fn`` will be + called sporadically even if the property doesn't actually change. You should + therefore avoid using these types. You always get an initial change notification. This is meant to initialize the user's state to the current value of the property. @@ -862,6 +863,94 @@ strictly part of the guaranteed API. Turn the given value into a string. Formats tables and their contents. This doesn't do anything special; it is only needed because Lua is terrible. +mp.input functions +-------------------- + +This module lets scripts get textual input from the user using the console +REPL. + +``input.get(table)`` + Show the console to let the user enter text. + + The following entries of ``table`` are read: + + ``prompt`` + The string to be displayed before the input field. + + ``submit`` + A callback invoked when the user presses Enter. The first argument is + the text in the console. You can close the console from within the + callback by calling ``input.terminate()``. If you don't, the console + stays open and the user can input more text. + + ``opened`` + A callback invoked when the console is shown. This can be used to + present a list of options with ``input.set_log()``. + + ``edited`` + A callback invoked when the text changes. This can be used to filter a + list of options based on what the user typed with ``input.set_log()``, + like dmenu does. The first argument is the text in the console. + + ``complete`` + A callback invoked when the user presses TAB. The first argument is the + text before the cursor. The callback should return a table of the string + candidate completion values and the 1-based cursor position from which + the completion starts. console.lua will filter the suggestions beginning + with the the text between this position and the cursor, sort them + alphabetically, insert their longest common prefix, and show them when + there are multiple ones. + + ``closed`` + A callback invoked when the console is hidden, either because + ``input.terminate()`` was invoked from the other callbacks, or because + the user closed it with a key binding. The first argument is the text in + the console, and the second argument is the cursor position. + + ``default_text`` + A string to pre-fill the input field with. + + ``cursor_position`` + The initial cursor position, starting from 1. + + ``id`` + An identifier that determines which input history and log buffer to use + among the ones stored for ``input.get()`` calls. The input histories + and logs are stored in memory and do not persist across different mpv + invocations. Defaults to the calling script name with ``prompt`` + appended. + +``input.terminate()`` + Close the console. + +``input.log(message, style, terminal_style)`` + Add a line to the log buffer. ``style`` can contain additional ASS tags to + apply to ``message``, and ``terminal_style`` can contain escape sequences + that are used when the console is displayed in the terminal. + +``input.log_error(message)`` + Helper to add a line to the log buffer with the same color as the one the + console uses for errors. Useful when the user submits invalid input. + +``input.set_log(log)`` + Replace the entire log buffer. + + ``log`` is a table of strings, or tables with ``text``, ``style`` and + ``terminal_style`` keys. + + Example: + + :: + + input.set_log({ + "regular text", + { + text = "error text", + style = "{\\c&H7a77f2&}", + terminal_style = "\027[31m", + } + }) + Events ------ diff --git a/DOCS/man/mpv.rst b/DOCS/man/mpv.rst index e97422d..479458b 100644 --- a/DOCS/man/mpv.rst +++ b/DOCS/man/mpv.rst @@ -205,8 +205,11 @@ Shift+PGUP and Shift+PGDWN Seek backward or forward by 10 minutes. (This used to be mapped to PGUP/PGDWN without Shift.) +b + Activate/deactivate debanding. + d - Activate/deactivate deinterlacer. + Cycle the deinterlacing filter. A Cycle aspect ratio override. @@ -218,18 +221,16 @@ Alt+LEFT, Alt+RIGHT, Alt+UP, Alt+DOWN Move the video rectangle (panning). Alt++ and Alt+- - Combining ``Alt`` with the ``+`` or ``-`` keys changes video zoom. + Change video zoom. Alt+BACKSPACE Reset the pan/zoom settings. F8 - Show the playlist and the current position in it (useful only if a UI window - is used, broken on the terminal). + Show the playlist and the current position in it. F9 - Show the list of audio and subtitle streams (useful only if a UI window is - used, broken on the terminal). + Show the list of audio and subtitle streams. i and I Show/toggle an overlay displaying statistics about the currently playing @@ -281,7 +282,7 @@ PREVIOUS and NEXT Seek backward/forward 1 minute. ZOOMIN and ZOOMOUT - Changes video zoom. + Change video zoom. If you miss some older key bindings, look at ``etc/restore-old-bindings.conf`` in the mpv git repository. @@ -304,6 +305,23 @@ Wheel up/down Wheel left/right Seek forward/backward 10 seconds. +Ctrl+Wheel up/down + Change video zoom. + +Context Menu +------------- + +.. warning:: + + This feature is experimental. It may not work with all VOs. A libass based + fallback may be implemented in the future. + +Context Menu is a menu that pops up on the video window on user interaction +(mouse right click, etc.). + +To use this feature, you need to fill the ``menu-data`` property with menu +definition data, and add a keybinding to run the ``context-menu`` command, +which can be done with a user script. USAGE ===== @@ -651,8 +669,9 @@ before. This is like passing the exact contents of the quoted string as a command line option. C-style escapes are currently _not_ interpreted on this level, although some options do this manually (this is a mess and should probably be changed at some point). The shell is not involved here, so option -values only need to be quoted to escape ``#`` and ``\``, ``"``, ``'`` or ``%`` -at the beginning of the value, and leading and trailing whitespace. +values only need to be quoted to escape ``#`` anywhere in the value, ``"``, +``'`` or ``%`` at the beginning of the value, and leading and trailing +whitespace. Putting Command Line Options into the Configuration File -------------------------------------------------------- @@ -710,22 +729,24 @@ or at runtime with the ``apply-profile <name>`` command. # a profile that can be enabled with --profile=big-cache [big-cache] cache=yes - demuxer-max-bytes=123400KiB + demuxer-max-bytes=512MiB demuxer-readahead-secs=20 - [slow] - profile-desc="some profile name" - # reference a builtin profile - profile=high-quality + [network] + profile-desc="profile for content over network" + force-window=immediate + # you can also include other profiles + profile=big-cache - [fast] - vo=vdpau + [reduce-judder] + video-sync=display-resample + interpolation=yes # using a profile again extends it - [slow] - framedrop=no - # you can also include other profiles - profile=big-cache + [network] + demuxer-max-back-bytes=512MiB + # reference a builtin profile + profile=fast Runtime profiles ---------------- @@ -968,7 +989,7 @@ There are three choices for using mpv from other programs or scripts: addition, terminal behavior itself may change any time. Compatibility cannot be guaranteed. - Your code should work even if you pass ``--no-terminal``. Do not attempt + Your code should work even if you pass ``--terminal=no``. Do not attempt to simulate user input by sending terminal control codes to mpv's stdin. If you need interactive control, using ``--input-ipc-server`` is recommended. This gives you access to the `JSON IPC`_ over unix domain @@ -1092,7 +1113,7 @@ this with ``--untimed``, but it will likely break, unless the stream has no audio, and the input feeds data to the player at a constant rate. Another common problem is with MJPEG streams. These do not signal the correct -framerate. Using ``--untimed`` or ``--no-correct-pts --container-fps-override=60`` +framerate. Using ``--untimed`` or ``--correct-pts=no --container-fps-override=60`` might help. For livestreams, data can build up due to pausing the stream, due to slightly @@ -1121,10 +1142,10 @@ commands ``quit-watch-later`` (bound to Shift+Q by default) and The difference between always quitting with a key bound to ``quit-watch-later`` and using ``--save-position-on-quit`` is that the latter will save the playback -position even when mpv is closed with a method other than a keybinding, for -example if you shutdown your system without closing mpv beforehand, unless of -course mpv is terminated abruptly and doesn't have the time to save (e.g. with -the KILL Unix signal). +position even when mpv is closed with a method other than a keybinding, such as +clicking the close button in the window title bar. However if mpv is terminated +abruptly and doesn't have the time to save, then the position will not be saved. +For example, if you shutdown your system without closing mpv beforehand. mpv also stores options other than the playback position when they have been modified after playback began, for example the volume and selected audio/subtitles, @@ -1152,8 +1173,8 @@ PROTOCOLS either aliases to documented protocols, or are just redirections to protocols implemented and documented in FFmpeg. - ``data:`` is supported in FFmpeg (not in Libav), but needs to be in the - format ``data://``. This is done to avoid ambiguity with filenames. You + ``data:`` is supported, but needs to be in the format ``data://``. + This is done to avoid ambiguity with filenames. You can also prefix it with ``lavf://`` or ``ffmpeg://``. ``ytdl://...`` @@ -1199,17 +1220,28 @@ PROTOCOLS Digital TV via DVB. (Linux only.) -``mf://[filemask|@listfile]`` ``--mf-...`` +``mf://[@listfile|filemask|glob|printf-format]`` ``--mf-...`` Play a series of images as video. + If the URL path begins with ``@``, it is interpreted as the path to a file + containing a list of image paths separated by newlines. If the URL path + contains ``,``, it is interpreted as a list of image paths separated by + ``,``. If the URL path does not contain ``%`` and if on POSIX platforms, is + interpreted as a glob, and ``*`` is automatically appended if it was not + specified. Otherwise, the printf sequences ``%[.][NUM]d``, where ``NUM`` is + one, two, or three decimal digits, and ``%%`` and are interpreted. For + example, ``mf://image-%d.jpg`` plays files like ``image-1.jpg``, + ``image-2.jpg`` and ``image-10.jpg``, provided that there are no big gaps + between the files. + ``cdda://[device]`` ``--cdrom-device=PATH`` ``--cdda-...`` Play CD. ``lavf://...`` - Access any FFmpeg/Libav libavformat protocol. Basically, this passed the + Access any FFmpeg libavformat protocol. Basically, this passed the string after the ``//`` directly to libavformat. ``av://type:options`` @@ -1357,27 +1389,6 @@ works like in older mpv releases: change, and not apply your additional settings, and/or use a different profile name. -Linux desktop issues -==================== - -This subsection describes common problems on the Linux desktop. None of these -problems exist on systems like Windows or macOS. - -Disabling Screensaver ---------------------- - -By default, mpv tries to disable the OS screensaver during playback (only if -a VO using the OS GUI API is active). ``--stop-screensaver=no`` disables this. - -A common problem is that Linux desktop environments ignore the standard -screensaver APIs on which mpv relies. In particular, mpv uses the Screen Saver -extension (XSS) on X11, and the idle-inhibit protocol on Wayland. - -Before mpv 0.33.0, the X11 backend ran ``xdg-screensaver reset`` in 10 second -intervals when not paused in order to support screensaver inhibition in these -environments. This functionality was removed in 0.33.0, but it is possible to -call the ``xdg-screensaver`` command line program from a user script instead. - .. include:: options.rst @@ -1445,7 +1456,7 @@ behavior of mpv. ``DISPLAY`` Standard X11 display name to use. -FFmpeg/Libav: +FFmpeg: This library accesses various environment variables. However, they are not centrally documented, and documenting them is not our job. Therefore, this list is incomplete. diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst index e0445d4..9f17345 100644 --- a/DOCS/man/options.rst +++ b/DOCS/man/options.rst @@ -46,7 +46,7 @@ Track Selection ``--audio`` is an alias for ``--aid``. - ``--aid=no`` or ``--audio=no`` or ``--no-audio`` disables audio playback. + ``--aid=no`` or ``--audio=no`` disables audio playback. (The latter variant does not work with the client API.) .. note:: @@ -102,7 +102,7 @@ Track Selection ``--sub`` is an alias for ``--sid``. - ``--sid=no`` or ``--sub=no`` or ``--no-sub`` disables subtitle decoding. + ``--sid=no`` or ``--sub=no`` disables subtitle decoding. (The latter variant does not work with the client API.) ``--vid=<ID|auto|no>`` @@ -110,7 +110,7 @@ Track Selection ``--video`` is an alias for ``--vid``. - ``--vid=no`` or ``--video=no`` or ``--no-video`` disables video playback. + ``--vid=no`` or ``--video=no`` disables video playback. (The latter variant does not work with the client API.) If video is disabled, mpv will try to download the audio only if media is @@ -135,11 +135,13 @@ Track Selection Note that if ``--lavfi-complex`` is set before playback is started, the referenced tracks are always selected. -``--subs-with-matching-audio=<yes|no>`` - When autoselecting a subtitle track, select a full/non-forced one even if the selected - audio stream matches your preferred subtitle language (default: yes). If this option is - set to ``no``, a non-forced subtitle track that matches the audio language will never be - autoselected by mpv regardless of the value of ``--slang`` or ``--subs-fallback``. +``--subs-with-matching-audio=<yes|forced|no>`` + When autoselecting a subtitle track, select it even if the selected audio + stream matches you preferred subtitle language (default: yes). If this + option is set to ``no``, then no subtitle track that matches the audio + language will ever be autoselected by mpv regardless of ``--slang`` or + ``subs-fallback``. If set to ``forced``, then only forced subtitles + will be selected. ``--subs-match-os-language=<yes|no>`` When autoselecting a subtitle track, select the track that matches the language of your OS @@ -445,11 +447,10 @@ Playback Control will not enable looping again (the command will show ``(disabled)`` on the OSD message if both loop points are set, but ``ab-loop-count`` is 0). -``--ordered-chapters``, ``--no-ordered-chapters`` - Enabled by default. - Disable support for Matroska ordered chapters. mpv will not load or - search for video segments from other files, and will also ignore any - chapter order specified for the main file. +``--ordered-chapters=<yes|no>`` + Enable support for Matroska ordered chapters. mpv will load and + search for video segments from other files, and will also respect any + chapter order specified for the main file (default: yes). ``--ordered-chapters-files=<playlist-file>`` Loads the given file as playlist, and tries to use the files contained in @@ -853,11 +854,11 @@ Program Behavior May be dangerous if playing from untrusted media. -``--ytdl``, ``--no-ytdl`` +``--ytdl=<yes|no>`` Enable the youtube-dl hook-script. It will look at the input URL, and will play the video located on the website. This works with many streaming sites, not just the one that the script is named after. This requires a recent - version of youtube-dl to be installed on the system. (Enabled by default.) + version of youtube-dl to be installed on the system (default: yes). If the script can't do anything with an URL, it will do nothing. @@ -1053,11 +1054,11 @@ Watch Later named "watch_later" underneath the local state directory (usually ``~/.local/state/mpv/``). -``--no-resume-playback`` - Do not restore playback position from the ``watch_later`` configuration - subdirectory (usually ``~/.config/mpv/watch_later/``). +``--resume-playback=<yes|no>`` + Restore playback position from the ``watch_later`` configuration + subdirectory, usually ``~/.config/mpv/watch_later/`` (default: yes). -``--resume-playback-check-mtime`` +``--resume-playback-check-mtime=<yes|no>`` Only restore the playback position from the ``watch_later`` configuration subdirectory (usually ``~/.config/mpv/watch_later/``) if the file's modification time is the same as at the time of saving. This may prevent @@ -1129,7 +1130,7 @@ Video ``--untimed`` Do not sleep when outputting video frames. Useful for benchmarks when used - with ``--no-audio.`` + with ``--audio=no``. ``--framedrop=<mode>`` Skip displaying some frames to maintain A/V sync on slow systems, or @@ -1293,7 +1294,7 @@ Video or ``--vo=libmpv`` (iOS 9.0 and up) :videotoolbox-copy: copies video back into system RAM (macOS 10.8 or iOS 9.0 and up) :vaapi: requires ``--vo=gpu``, ``--vo=vaapi`` or ``--vo=dmabuf-wayland`` (Linux only) - :vaapi-copy: copies video back into system RAM (Linux with some GPUs only) + :vaapi-copy: copies video back into system RAM (Linux with some GPUs or Windows) :nvdec: requires ``--vo=gpu`` (Any platform CUDA is available) :nvdec-copy: copies video back to system RAM (Any platform CUDA is available) :drm: requires ``--vo=gpu`` (Linux only) @@ -1313,8 +1314,6 @@ Video :mediacodec: requires ``--vo=gpu --gpu-context=android`` or ``--vo=mediacodec_embed`` (Android only) :mediacodec-copy: copies video back to system RAM (Android only) - :mmal: requires ``--vo=gpu`` (Raspberry Pi only - default if available) - :mmal-copy: copies video back to system RAM (Raspberry Pi only) :cuda: requires ``--vo=gpu`` (Any platform CUDA is available) :cuda-copy: copies video back to system RAM (Any platform CUDA is available) :crystalhd: copies video back to system RAM (Any platform supported by hardware) @@ -1409,9 +1408,6 @@ Video affect this additionally. This can give incorrect results even with completely ordinary video sources. - ``rpi`` always uses the hardware overlay renderer, even with - ``--vo=gpu``. - ``mediacodec`` is not safe. It forces RGB conversion (not with ``-copy``) and how well it handles non-standard colorspaces is not known. In the rare cases where 10-bit is supported the bit depth of the output @@ -1497,10 +1493,13 @@ Video For the Vulkan GPU backend, decoding must always happen on the display device, and this option has no effect. -``--vaapi-device=<device file>`` +``--vaapi-device=<device file|adapter name>`` Choose the DRM device for ``vaapi-copy``. This should be the path to a DRM device file. (Default: ``/dev/dri/renderD128``) + On Windows this takes adapter name as an input. Will pick the default adapter + if unset. Alternatives are listed when the name "help" is given. + ``--panscan=<0.0-1.0>`` Enables pan-and-scan functionality (cropping the sides of e.g. a 16:9 video to make it fit a 4:3 display without black bands). The range @@ -1557,7 +1556,7 @@ Video also still be scaled in one dimension if the source uses non-square pixels (e.g. anamorphic widescreen DVDs). - This option is disabled if the ``--no-keepaspect`` option is used. + This option is disabled if ``--keepaspect=no`` is used. ``--video-pan-x=<value>``, ``--video-pan-y=<value>`` Moves the displayed video rectangle by the given value in the X or Y @@ -1569,7 +1568,7 @@ Video ``--video-pan-x=-0.1`` would move the video 192 pixels to the left and ``--video-pan-y=-0.1`` would move the video 108 pixels up. - This option is disabled if the ``--no-keepaspect`` option is used. + This option is disabled if ``--keepaspect=no`` is used. ``--video-rotate=<0-359|no>`` Rotate the video clockwise, in degrees. If ``no`` is given, the video is @@ -1597,7 +1596,7 @@ Video ``--video-zoom=1`` is twice the size, ``--video-zoom=-2`` is one fourth of the size, and so on. - This option is disabled if the ``--no-keepaspect`` option is used. + This option is disabled if ``--keepaspect=no`` is used. ``--video-scale-x=<value>``, ``--video-scale-y=<value>`` Multiply the video display size with the given value (default: 1.0). If a @@ -1605,8 +1604,8 @@ Video video will be either cut off, or black bars are added. This value is multiplied with the value derived from ``--video-zoom`` and - the normal video aspect ratio. This option is disabled if the - ``--no-keepaspect`` option is used. + the normal video aspect ratio. This option is disabled if + ``--keepaspect=no`` is used. ``--video-align-x=<-1-1>``, ``--video-align-y=<-1-1>`` Moves the video rectangle within the black borders, which are usually added @@ -1618,7 +1617,7 @@ Video If video and screen aspect match perfectly, these options do nothing. - This option is disabled if the ``--no-keepaspect`` option is used. + This option is disabled if ``--keepaspect=no`` is used. ``--video-margin-ratio-left=<val>``, ``--video-margin-ratio-right=<val>``, ``--video-margin-ratio-top=<val>``, ``--video-margin-ratio-bottom=<val>`` Set extra video margins on each border (default: 0). Each value is a ratio @@ -1633,7 +1632,7 @@ Video The margins are applied after 90° video rotation, but before any other video transformations. - This option is disabled if the ``--no-keepaspect`` option is used. + This option is disabled if ``--keepaspect=no`` is used. Subtitles still may use the margins, depending on ``--sub-use-margins`` and similar options. @@ -1644,8 +1643,8 @@ Video more generally useful. The behavior of these options may change to fit OSC requirements better, too. -``--correct-pts``, ``--no-correct-pts`` - ``--no-correct-pts`` switches mpv to a mode where video timing is +``--correct-pts=<yes|no>`` + ``--correct-pts=no`` switches mpv to a mode where video timing is determined using a fixed framerate value (either using the ``--container-fps-override`` option, or using file information). Sometimes, files with very broken timestamps can be played somewhat well in this mode. @@ -1657,25 +1656,35 @@ Video .. note:: - Works in ``--no-correct-pts`` mode only. + Works in ``--correct-pts=no`` mode only. -``--deinterlace=<yes|no>`` +``--deinterlace=<yes|no|auto>`` Enable or disable interlacing (default: no). Interlaced video shows ugly comb-like artifacts, which are visible on - fast movement. Enabling this typically inserts the yadif video filter in + fast movement. Enabling this typically inserts the bwdif video filter in order to deinterlace the video, or lets the video output apply deinterlacing if supported. - This behaves exactly like the ``deinterlace`` input property (usually - mapped to ``d``). + When using ``auto``, mpv will insert a deinterlacing filter if ffmpeg + detects that the video frame is interlaced. Be aware that there can be false + positives in certain cases, such as when files are encoded as interlaced + despite the video not actually being so. This is why ``auto`` is not the + default value. + + Keep in mind that using this filter **will** conflict with any manually + inserted deinterlacing filters, and that this will make video look worse if + it's not actually interlaced. - Keep in mind that this **will** conflict with manually inserted - deinterlacing filters, unless you take care. (Since mpv 0.27.0, even the - hardware deinterlace filters will conflict. Also since that version, - ``--deinterlace=auto`` was removed, which used to mean that the default - interlacing option of possibly inserted video filters was used.) +``--deinterlace-field-parity=<tff|bff|auto>`` + Specify the field parity/order when deinterlacing(default: auto) + Each frame of an interlaced video is divided into two fields, which are + then separately transmitted. Top field represents even lines while bottom + field represents odd lines. When deinterlacing the deinterlacer needs to + know the correct temporal order of the fields else the video will appear + jittery. - Note that this will make video look worse if it's not actually interlaced. + ``auto`` will automatically try to detect the field order of the video, + ``tff`` forces top field first while ``bff`` forces bottom field first. ``--frames=<number>`` Play/convert only first ``<number>`` video frames, then quit. @@ -1716,14 +1725,21 @@ Video By default, this is set to ``h264,vc1,hevc,vp8,vp9,av1``. Note that the hardware acceleration special codecs like ``h264_vdpau`` are not - relevant anymore, and in fact have been removed from Libav in this form. + relevant anymore, and in fact have been removed from FFmpeg in this form. This is usually only needed with broken GPUs, where a codec is reported as supported, but decoding causes more problems than it solves. + .. note:: + + On some broken drivers (e.g. NVIDIA on Linux), probing for codecs which + the GPU does not support can unnecessarily slow down video playback + initialization. To alleviate this, explicitly specify a list which + only includes the codecs supported on the setup. + .. admonition:: Example - ``mpv --hwdec=vdpau --vo=vdpau --hwdec-codecs=h264,mpeg2video`` + ``mpv --hwdec=vdpau --hwdec-codecs=h264,mpeg2video`` Enable vdpau decoding for h264 and mpeg2 only. ``--vd-lavc-check-hw-profile=<yes|no>`` @@ -1958,7 +1974,7 @@ Audio In earlier mpv versions you could use ``--ad`` to force the spdif wrapper. This does not work anymore. - .. admonition:: Warning + .. warning:: There is not much reason to use this. HDMI supports uncompressed multichannel PCM, and mpv supports lossless DTS-HD decoding via @@ -1979,13 +1995,13 @@ Audio .. admonition:: Examples ``--ad=mp3float`` - Prefer the FFmpeg/Libav ``mp3float`` decoder over all other MP3 + Prefer the FFmpeg ``mp3float`` decoder over all other MP3 decoders. ``--ad=help`` List all available decoders. - .. admonition:: Warning + .. warning:: Enabling compressed audio passthrough (AC3 and DTS via SPDIF/HDMI) with this option is not possible. Use ``--audio-spdif`` instead. @@ -1995,7 +2011,19 @@ Audio amplification. Negative values can be passed for compatibility, but are treated as 0. - Since mpv 0.18.1, this always controls the internal mixer (aka "softvol"). + Since mpv 0.18.1, this always controls the internal mixer (aka software + volume). + +``--volume-max=<100.0-1000.0>`` + Set the maximum amplification level in percent (default: 130). A value of + 130 will allow you to adjust the volume up to about double the normal level. + +``--volume-gain=<db>`` + Set the volume gain in dB. This is applied on top of other volume and gain + settings. + +``--volume-gain-max=<0.0-150.0>``, ``--volume-gain-min=<-150.0-0.0>`` + Set the volume gain range in dB (default: -96 dB min, 12 dB max). ``--replaygain=<no|track|album>`` Adjust volume gain according to replaygain values stored in the file @@ -2008,8 +2036,8 @@ Audio (default: 0). ``--replaygain-clip=<yes|no>`` - Prevent clipping caused by replaygain by automatically lowering the - gain (default). Use ``--replaygain-clip=no`` to disable this. + Allow the volume gain to clip (default: no). If this option is not + enabled, mpv automatically will prevent clipping by lowering the gain. ``--replaygain-fallback=<db>`` Gain in dB to apply if the file has no replay gain tags. This option @@ -2027,18 +2055,6 @@ Audio See also: ``--volume``. -``--softvol=<no|yes|auto>`` - Deprecated/unfunctional. Before mpv 0.18.1, this used to control whether - to use the volume controls of the audio output driver or the internal mpv - volume filter. - - The current behavior is that softvol is always enabled, i.e. as if this - option is set to ``yes``. The other behaviors are not available anymore, - although ``auto`` almost matches current behavior in most cases. - - The ``no`` behavior is still partially available through the ``ao-volume`` - and ``ao-mute`` properties. But there are no options to reset these. - ``--audio-demuxer=<[+]name>`` Use this audio demuxer type when using ``--audio-file``. Use a '+' before the name to force it; this will skip some checks. Give the demuxer name as @@ -2074,10 +2090,10 @@ Audio This is a key/value list option. See `List Options`_ for details. -``--ad-spdif-dtshd=<yes|no>``, ``--dtshd``, ``--no-dtshd`` +``--ad-spdif-dtshd=<yes|no>``, ``--dtshd=<yes|no>`` If DTS is passed through, use DTS-HD. - .. admonition:: Warning + .. warning:: This and enabling passthrough via ``--ad`` are deprecated in favor of using ``--audio-spdif=dts-hd``. @@ -2135,7 +2151,7 @@ Audio work-around for this on some AOs is to use ``--audio-exclusive=yes`` to circumvent the system mixer entirely. - .. admonition:: Warning + .. warning:: Using ``auto`` can cause issues when using audio over HDMI. The OS will typically report all channel layouts that _can_ go over HDMI, even if @@ -2179,8 +2195,8 @@ Audio ``--audio-samplerate=<Hz>`` Select the output sample rate to be used (of course sound cards have limits on this). If the sample frequency selected is different from that - of the current media, the lavrresample audio filter will be inserted into - the audio filter layer to compensate for the difference. + of the current media, the internal swresample audio filter will be inserted + into the audio filter layer to compensate for the difference. ``--gapless-audio=<no|yes|weak>`` Try to play consecutive audio files with no silence or disruption at the @@ -2217,7 +2233,7 @@ Audio then the buffered audio may run out before playback of the new file can start. -``--initial-audio-sync``, ``--no-initial-audio-sync`` +``--initial-audio-sync=<yes|no>`` When starting a video file or after events such as seeking, mpv will by default modify the audio stream to make it start from the same timestamp as video, by either inserting silence at the start or cutting away the @@ -2226,11 +2242,7 @@ Audio their start timestamps differ, and then video timing is gradually adjusted if necessary to reach correct synchronization later. -``--volume-max=<100.0-1000.0>`` - Set the maximum amplification level in percent (default: 130). A value of - 130 will allow you to adjust the volume up to about double the normal level. - -``--audio-file-auto=<no|exact|fuzzy|all>``, ``--no-audio-file-auto`` +``--audio-file-auto=<no|exact|fuzzy|all>`` Load additional audio files matching the video filename. The parameter specifies how external audio files are matched. @@ -2260,7 +2272,7 @@ Audio a larger buffer if it pleases. If the device creates a smaller buffer, additional audio is buffered in an additional software buffer. - Making this larger will make soft-volume and other filters react slower, + Making this larger may make soft-volume and other filters react slower, introduce additional issues on playback speed change, and block the player on audio format changes. A smaller buffer might lead to audio dropouts. @@ -2281,7 +2293,7 @@ Audio Not all AOs support this. - .. admonition:: Warning + .. warning:: This modifies certain subtle player behavior, like A/V-sync and underrun handling. Enabling this option is strongly discouraged. @@ -2308,8 +2320,18 @@ Subtitles Force subtitle demuxer type for ``--sub-file``. Give the demuxer name as printed by ``--sub-demuxer=help``. +``--sub-lavc-o=<key>=<value>[,<key>=<value>[,...]]`` + Pass AVOptions to libavcodec decoder. Note, a patch to make the o= + unneeded and pass all unknown options through the AVOption system is + welcome. A full list of AVOptions can be found in the FFmpeg manual. + + This is a key/value list option. See `List Options`_ for details. + ``--sub-delay=<sec>`` - Delays subtitles by ``<sec>`` seconds. Can be negative. + Delays primary subtitles by ``<sec>`` seconds. Can be negative. + +``--secondary-sub-delay=<sec>`` + Delays secondary subtitles by ``<sec>`` seconds. Can be negative. ``--sub-files=<file-list>``, ``--sub-file=<filename>`` Add a subtitle file to the list of external subtitles. @@ -2332,8 +2354,8 @@ Subtitles ``--secondary-sid=<ID|auto|no>`` Select a secondary subtitle stream. This is similar to ``--sid``. If a secondary subtitle is selected, it will be rendered as toptitle (i.e. on - the top of the screen) alongside the normal subtitle, and provides a way - to render two subtitles at once. + the top of the screen) alongside the normal subtitle by default, and + provides a way to render two subtitles at once. There are some caveats associated with this feature. For example, bitmap subtitles will always be rendered in their usual position, so selecting a @@ -2343,14 +2365,14 @@ Subtitles .. note:: Styling and interpretation of any formatting tags is disabled for the - secondary subtitle. Internally, the same mechanism as ``--no-sub-ass`` + secondary subtitle. Internally, the same mechanism as ``--sub-ass=no`` is used to strip the styling. .. note:: If the main subtitle stream contains formatting tags which display the subtitle at the top of the screen, it will overlap with the secondary - subtitle. To prevent this, you could use ``--no-sub-ass`` to disable + subtitle. To prevent this, you could use ``--sub-ass=no`` to disable styling in the main subtitle stream. ``--sub-scale=<0-100>`` @@ -2400,7 +2422,7 @@ Subtitles some margin between the bottom and the subtitle. Values above 100 move the subtitle further down. - .. admonition:: Warning + .. warning:: Text subtitles (as opposed to image subtitles) may be cut off if the value of the option is above 100. This is a libass restriction. @@ -2410,6 +2432,10 @@ Subtitles Using ``--sub-margin-y`` can achieve this in a better way. +``--secondary-sub-pos=<0-150>`` + Specify the position of secondary subtitles on the screen. This is similar + to ``--sub-pos`` but for secondary subtitles. + ``--sub-speed=<0.1-10.0>`` Multiply the subtitle event timestamps with the given value. Can be used to fix the playback speed for frame-based subtitle formats. Affects text @@ -2442,7 +2468,7 @@ Subtitles :normal: FreeType autohinter, normal mode :native: font native hinter - .. admonition:: Warning + .. warning:: Enabling hinting can lead to mispositioned text (in situations it's supposed to match up video background), or reduce the smoothness @@ -2489,6 +2515,12 @@ Subtitles This also controls some bitmap subtitle overrides, as well as HTML tags in formats like SRT, despite the name of the option. +``--secondary-sub-ass-override=<yes|no|force|scale|strip>`` + Control whether user secondary substyle overrides should be applied. This + works exactly like ``--sub-ass-override``. + + Default: strip. + ``--sub-ass-force-margins`` Enables placing toptitles and subtitles in black borders when they are available, if the subtitles are in the ASS format. @@ -2585,8 +2617,8 @@ Subtitles canvas size. Can be useful to test broken subtitles, which often happen when the video was trancoded, while attempting to keep the old subtitles. -``--sub-ass``, ``--no-sub-ass`` - Render ASS subtitles natively (enabled by default). +``--sub-ass=<yes|no>`` + Render ASS subtitles natively (default: yes). .. note:: @@ -2595,17 +2627,17 @@ Subtitles using ``--sub-ass-override=style`` should give better results without breaking subtitles too much. - If ``--no-sub-ass`` is specified, all tags and style declarations are + If ``--sub-ass=no`` is specified, all tags and style declarations are stripped and ignored on display. The subtitle renderer uses the font style as specified by the ``--sub-`` options instead. .. note:: - Using ``--no-sub-ass`` may lead to incorrect or completely broken + Using ``--sub-ass=no`` may lead to incorrect or completely broken rendering of ASS/SSA subtitles. It can sometimes be useful to forcibly override the styling of ASS subtitles, but should be avoided in general. -``--sub-auto=<no|exact|fuzzy|all>``, ``--no-sub-auto`` +``--sub-auto=<no|exact|fuzzy|all>`` Load additional subtitle files matching the video filename. The parameter specifies how external subtitle files are matched. ``exact`` is enabled by default. @@ -2721,11 +2753,11 @@ Subtitles This is a path list option. See `List Options`_ for details. -``--sub-visibility``, ``--no-sub-visibility`` +``--sub-visibility=<yes|no>`` Can be used to disable display of subtitles, but still select and decode them. -``--secondary-sub-visibility``, ``--no-secondary-sub-visibility`` +``--secondary-sub-visibility=<yes|no>`` Can be used to disable display of secondary subtitles, but still select and decode them. @@ -2735,12 +2767,26 @@ Subtitles Matroska-style ASS subtitle packets. It should be unique, and libass uses it for fast elimination of duplicates. This option disables caching of subtitles across seeks, so after a seek libass can't eliminate subtitle - packets with the same ReadOrder as earlier packets. + packets with the same ReadOrder as earlier packets. Note that enabling this + option can result in broken subtitle behavior if you are not actually + playing one of the aforementioned broken mkv files. + +``--teletext-page=<-1-999>`` + Select a teletext page number to decode. -``--teletext-page=<1-999>`` This works for ``dvb_teletext`` subtitle streams, and if FFmpeg has been compiled with support for it. + Values ``1-999`` are for individual pages. Special value ``0`` (default) + matches all subtitle pages. Special value ``-1`` matches all pages. + + Note that page ``100`` is the default start page of actual teletext. It is + also the former default value of this option. + + See the ``libzvbi-teletext`` section in FFmpeg documentation for details. + + Default: 0 + ``--sub-past-video-end`` After the last frame of video, if this option is enabled, subtitles will continue to update based on audio timestamps. Otherwise, the subtitles @@ -2760,8 +2806,8 @@ Subtitles .. note:: The ``--sub-font`` option (and many other style related ``--sub-`` - options) are ignored when ASS-subtitles are rendered, unless the - ``--no-sub-ass`` option is specified. + options) are ignored when ASS-subtitles are rendered, unless + ``--sub-ass=no`` is specified. This used to support fontconfig patterns. Starting with libass 0.13.0, this stopped working. @@ -2779,7 +2825,8 @@ Subtitles ``--sub-shadow-offset`` to change its size relative to the text. ``--sub-blur=<0..20.0>`` - Gaussian blur factor. 0 means no blur applied (default). + Gaussian blur factor applied to the sub font border. + 0 means no blur applied (default). ``--sub-bold=<yes|no>`` Format text on bold. @@ -2846,7 +2893,7 @@ Subtitles Control to which corner of the screen text subtitles should be aligned to (default: ``center``). - Never applied to ASS subtitles, except in ``--no-sub-ass`` mode. Likewise, + Never applied to ASS subtitles, except in ``--sub-ass=no`` mode. Likewise, this does not apply to image subtitles. ``--sub-align-y=<top|center|bottom>`` @@ -2892,8 +2939,12 @@ Subtitles This is intended for English, but may in part work for other languages too. The intention is that it can be always enabled so may not remove all parts added. - It removes speaker labels (like MAN:), upper case text in parentheses and - any text in brackets. + + It removes speaker labels (like MAN:) and any text enclosed within symbols like + parentheses or brackets as specified by the ``--sub-filter-sdh-enclosures`` option. + Note that parenthesis (full width parenthesis and the normal variant) are a special + case and only upper case text is removed. For more filtering, you can use the + ``--sub-filter-sdh-harder`` option. Default: ``no``. @@ -2904,6 +2955,15 @@ Subtitles Default: ``no``. +``--sub-filter-sdh-enclosures=<string>`` + Specify a string of characters that ``--sub-filter-sdh`` will use to potentially + remove text. Text that is enclosed within characters specified by this string will + be removed. Note that bracket characters with known pairs (such as ``(`` or ``[``) + will be mapped internally to their matching right hand character, so you only need + to specify left hand characters. + + Default: ``([(``. + ``--sub-filter-regex-...=...`` Set a list of regular expressions to match on text subtitles, and remove any lines that match (default: empty). This is a string list option. See @@ -3116,17 +3176,17 @@ Window This option does not affect the framerate used for ``mf://`` or ``--merge-files``. For that, use ``--mf-fps`` instead. - Setting ``--image-display-duration`` hides the OSC and does not track - playback time on the command-line output, and also does not duplicate - the image frame when encoding. To force the player into "dumb mode" - and actually count out seconds, or to duplicate the image when - encoding, you need to use ``--demuxer=lavf --demuxer-lavf-o=loop=1``, - and use ``--length`` or ``--frames`` to stop after a particular time. + When viewing images, the playback time is not tracked on the command line + output, and the image frame is not duplicated when encoding. To force the + player into "dumb mode" and actually count out seconds, or to duplicate the + image when encoding, you need to use ``--demuxer=lavf + --demuxer-lavf-o=loop=1``, and use ``--length`` or ``--frames`` to stop + after a particular time. ``--force-window=<yes|no|immediate>`` Create a video output window even if there is no video. This can be useful when pretending that mpv is a GUI application. Currently, the window - always has the size 640x480, and is subject to ``--geometry``, + always has the size 960x540, and is subject to ``--geometry``, ``--autofit``, and similar options. .. warning:: @@ -3139,7 +3199,7 @@ Window mode can be used to create the window always on program start, but this may cause other issues. -``--taskbar-progress``, ``--no-taskbar-progress`` +``--taskbar-progress=<yes|no>`` (Windows only) Enable/disable playback progress rendering in taskbar (Windows 7 and above). @@ -3148,14 +3208,14 @@ Window ``--snap-window`` (Windows only) Snap the player window to screen edges. -``--drag-and-drop=<no|auto|replace|append>`` - (X11, Wayland and Windows only) - Controls the default behavior of drag and drop on platforms that support this. - ``auto`` will obey what the underlying os/platform gives mpv. Typically, holding - shift during the drag and drop will append the item to the playlist. Otherwise, - it will completely replace it. ``replace`` and ``append`` always force replacing - and appending to the playlist respectively. ``no`` disables all drag and drop - behavior. +``--drag-and-drop=<no|auto|replace|append|insert-next>`` + Controls the default behavior of drag and drop on platforms that support + this. ``auto`` will obey what the underlying os/platform gives mpv. + Typically, holding shift during the drag and drop will append the item to + the playlist. Otherwise, it will completely replace it. ``replace``, + ``append``, and ``insert-next`` always force replacing, appending to, and + inserting next into the playlist respectively. ``no`` disables all drag and + drop behavior. ``--ontop`` Makes the player window stay on top of other windows. @@ -3173,10 +3233,13 @@ Window :desktop: On top of the Desktop behind windows and Desktop icons. :level: A level as integer. -``--focus-on-open``, ``--no-focus-on-open`` +``--focus-on=<never|open|all>``, (macOS only) - Focus the video window on creation and makes it the front most window. This - is on by default. + Focus the video window and make it the front most window on specific events (default: open). + + :never: Never focus the window on open or new file load events. + :open: Focus the window on creation, eg when a vo is initialised. + :all: Focus the window on open and new file load event. ``--window-corners=<default|donotround|round|roundsmall>`` (Windows only) @@ -3187,14 +3250,14 @@ Window :round: Round the corners if appropriate :roundsmall: Round the corners if appropriate, with a small radius -``--border``, ``--no-border`` +``--border=<yes|no>`` Play video with window border and decorations. Since this is on by default, use ``--no-border`` to disable the standard window decorations. -``--title-bar``, ``--no-title-bar`` - (Windows only) +``--title-bar=<yes|no>`` + (Windows and X11 only) Play video with the window title bar. Since this is on by default, - use --no-title-bar to hide the title bar. The --no-border option takes + use ``--title-bar=no`` to hide the title bar. The ``--border`` option takes precedence. ``--on-all-workspaces`` @@ -3234,6 +3297,11 @@ Window This option does not work properly with all window managers. + .. admonition:: Note (Wayland) + + Wayland does not allow a client to position itself so this option will + only affect the window size. + .. admonition:: Examples ``50:40`` @@ -3248,7 +3316,7 @@ Window ``50%x50%`` Forces the window width and height to half the screen width and height. Will show black borders to compensate for the video aspect - ratio (with most VOs and without ``--no-keepaspect``). + ratio (with most VOs and with ``--keepaspect=yes``). ``50%+10+10/2`` Sets the window to half the screen widths, and positions it 10 pixels below/left of the top left corner of the screen, on the @@ -3296,8 +3364,8 @@ Window of the screen width, or higher than 60% of the screen height. ``--autofit-larger=<[W[xH]]>`` - This option behaves exactly like ``--autofit``, except the window size is - only changed if the window would be larger than the specified size. + This option behaves exactly like ``--autofit``, except that it sets the + maximum size of the window. .. admonition:: Example @@ -3372,20 +3440,19 @@ Window be the default behavior. Currently only affects X11 and SDL VOs. ``--auto-window-resize=<yes|no>`` - (Wayland, Win32, and X11) By default, mpv will automatically resize itself if the video's size changes (i.e. advancing forward in a playlist). Setting this to ``no`` disables this behavior so the window size never changes automatically. This option does not have any impact on the ``--autofit`` or ``--geometry`` options. -``--no-keepaspect``, ``--keepaspect`` - ``--no-keepaspect`` will always stretch the video to window size, and will +``--keepaspect=<yes|no>`` + ``--keepaspect=no`` will always stretch the video to window size, and will disable the window manager hints that force the window aspect ratio. (Ignored in fullscreen mode.) -``--no-keepaspect-window``, ``--keepaspect-window`` - ``--keepaspect-window`` (the default) will lock the window size to the - video aspect. ``--no-keepaspect-window`` disables this behavior, and will +``--keepaspect-window=<yes|no>`` + ``--keepaspect-window=yes`` (the default) will lock the window size to the + video aspect. ``--keepaspect-window=no`` disables this behavior, and will instead add black bars if window aspect and video aspect mismatch. Whether this actually works depends on the VO backend. (Ignored in fullscreen mode.) @@ -3402,13 +3469,12 @@ Window - ``--monitoraspect=4:3`` or ``--monitoraspect=1.3333`` - ``--monitoraspect=16:9`` or ``--monitoraspect=1.7777`` -``--hidpi-window-scale``, ``--no-hidpi-window-scale`` - (macOS, Windows, X11, and Wayland only) - Scale the window size according to the backing scale factor (default: yes). - On regular HiDPI resolutions the window opens with double the size but appears - as having the same size as on non-HiDPI resolutions. +``--hidpi-window-scale=<yes|no>`` + Scale the window size according to the backing DPI scale factor from the OS + (default: no). For example, if the OS DPI scaling is set to 200%, mpv's window + size will be multiplied by 2. -``--native-fs``, ``--no-native-fs`` +``--native-fs=<yes|no>`` (macOS only) Uses the native fullscreen mechanism of the OS (default: yes). @@ -3423,10 +3489,12 @@ Window the screensaver will re-enable when playback is not active. ``always`` will always disable the screensaver. Note that stopping the screensaver is only possible if a video output is available (i.e. there is an open mpv window). + This is not supported on all video outputs, platforms, or desktop environments. - This is not supported on all video outputs or platforms. Sometimes it is - implemented, but does not work (especially with Linux "desktops"). Read the - `Disabling Screensaver`_ section very carefully. + Before mpv 0.33.0, the X11 backend ran ``xdg-screensaver reset`` in 10 second + intervals when not paused in order to support screensaver inhibition in some + environments. This functionality was removed in 0.33.0, but it is possible to + call the ``xdg-screensaver`` command line program from a user script instead. ``--wid=<ID>`` This tells mpv to attach to an existing window. If a VO is selected that @@ -3455,11 +3523,11 @@ Window ``--hwdec=mediacodec`` for direct rendering using MediaCodec, or with ``--vo=gpu --gpu-context=android`` (with or without ``--hwdec=mediacodec``). -``--no-window-dragging`` - Don't move the window when clicking on it and moving the mouse pointer. +``--window-dragging=<yes|no>`` + Move the window when clicking on it and moving the mouse pointer (default: yes). ``--x11-name=<string>`` - Set the window class name for X11-based video output methods. + Set the window instance name for X11-based video output methods. ``--x11-netwm=<yes|no|auto>`` (X11 only) @@ -3511,7 +3579,7 @@ Window mechanism in case there is good/bad behavior with whatever your combination of hardware/drivers/etc. happens to be. -``--x11-wid-title`` ``--no-x11-wid-title`` +``--x11-wid-title=<yes|no>`` Whether or not to set the window title when mpv is embedded on X11 (default: ``no``). @@ -4009,36 +4077,52 @@ Demuxer Input ----- -``--native-keyrepeat`` +``--native-keyrepeat=<yes|no>`` Use system settings for keyrepeat delay and rate, instead of - ``--input-ar-delay`` and ``--input-ar-rate``. (Whether this applies - depends on the VO backend and how it handles keyboard input. Does not - apply to terminal input.) + ``--input-ar-delay`` and ``--input-ar-rate`` (default: no). + Whether this applies depends on the VO backend and how it handles + keyboard input. Does not apply to terminal input. ``--input-ar-delay`` - Delay in milliseconds before we start to autorepeat a key (0 to disable). + Delay in milliseconds before we start to autorepeat a key (default: 200). + Set it to 0 to disable. ``--input-ar-rate`` - Number of key presses to generate per second on autorepeat. + Number of key presses to generate per second on autorepeat (default: 40). ``--input-conf=<filename>`` Specify input configuration file other than the default location in the mpv configuration directory (usually ``~/.config/mpv/input.conf``). -``--no-input-default-bindings`` - Disable default-level ("weak") key bindings. These are bindings which config - files like ``input.conf`` can override. It currently affects the builtin key - bindings, and keys which scripts bind using ``mp.add_key_binding`` (but not - ``mp.add_forced_key_binding`` because this overrides ``input.conf``). +``--input-default-bindings=<yes|no>`` + Enable default-level ("weak") key bindings (default: yes). These are bindings + which config files like ``input.conf`` can override. It currently affects the + builtin key bindings, and keys which scripts bind using ``mp.add_key_binding`` + (but not ``mp.add_forced_key_binding`` because this overrides ``input.conf``). -``--no-input-builtin-bindings`` - Disable loading of built-in key bindings during start-up. This option is - applied only during (lib)mpv initialization, and if used then it will not - be not possible to enable them later. May be useful to libmpv clients. +``--input-builtin-bindings=<yes|no>`` + Enable loading of built-in key bindings during start-up (default: yes). This + option is applied only during (lib)mpv initialization, and if disabled then it + will not be not possible to enable them later. May be useful to libmpv clients. ``--input-cmdlist`` Prints all commands that can be bound to keys. +``--input-commands=<cmd1,cmd2,...>`` + Define a list of commands for mpv to run. The syntax is the same as format + as ``input.conf`` but without the key binding argument at the beginning. + When this option is set at startup, the commands will run after audio and + video playback are about to begin if applicable (in idle mode with no file, + it will run immediately). When changing values at runtime, the commands will + also run as soon as possible. + + This is a string list option. See `List Options`_ for details. + + .. admonition:: Example + + ``--input-commands="playlist-play-index 1,set ao-volume 40"`` + sets the playlist index to 1 and the ao-volume to 40 + ``--input-doubleclick-time=<milliseconds>`` Time in milliseconds to recognize two consecutive button presses as a double-click (default: 300). @@ -4060,8 +4144,8 @@ Input work (key bindings that normally quit will be shown on OSD only, just like any other binding). See `INPUT.CONF`_. -``--input-terminal``, ``--no-input-terminal`` - ``--no-input-terminal`` prevents the player from reading key events from +``--input-terminal=<yes|no>`` + ``--input-terminal=no`` prevents the player from reading key events from standard input. Useful when reading data from standard input. This is automatically enabled when ``-`` is found on the command line. There are situations where you have to set it manually, e.g. if you open @@ -4116,13 +4200,12 @@ Input ``--input-gamepad=<yes|no>`` Enable/disable SDL2 Gamepad support. Disabled by default. -``--input-cursor``, ``--no-input-cursor`` +``--input-cursor=<yes|no>`` Permit mpv to receive pointer events reported by the video output driver. Necessary to use the OSC, or to select the buttons in DVD menus. Support depends on the VO in use. -``--input-cursor-passthrough``, ``--no-input-cursor-passthrough`` - (X11 and Wayland only) +``--input-cursor-passthrough=<yes|no>`` Tell the backend windowing system to allow pointer events to passthrough the mpv window. This allows windows under mpv to instead receive pointer events as if the mpv window was never there. @@ -4138,8 +4221,22 @@ Input implemented. Whether media keys work when the mpv window is focused is implementation-defined. -``--input-right-alt-gr``, ``--no-input-right-alt-gr`` - (Cocoa and Windows only) +``--input-preprocess-wheel=<yes|no>`` + Preprocess ``WHEEL_*`` events so that while scrolling on the horizontal + or vertical direction, the events aren't generated for another direction + even when the two directions are scrolled together (default: yes). + + This preprocessing can be beneficial for preventing accidentally seeking + while changing the volume by scrolling on a touchpad with the default + keybind. Due to the deadzone mechanism used, disabling the preprocessing + allows for diagonal scrolling (such as panning) and potentially reduces + input latency. + + Note that disabling the preprocessing does not affect any filtering done + by the OS/driver before these events are delivered to mpv, if any. + +``--input-right-alt-gr=<yes|no>`` + (macOS and Windows only) Use the right Alt key as Alt Gr to produce special characters. If disabled, count the right Alt as an Alt modifier key. Enabled by default. @@ -4169,11 +4266,11 @@ Input OSD --- -``--osc``, ``--no-osc`` +``--osc=<yes|no>`` Whether to load the on-screen-controller (default: yes). -``--no-osd-bar``, ``--osd-bar`` - Disable display of the OSD bar. +``--osd-bar=<yes|no>`` + Enable display of the OSD bar (default: yes). You can configure this on a per-command basis in input.conf using ``osd-`` prefixes, see ``Input Command Prefixes``. If you want to disable the OSD @@ -4259,11 +4356,18 @@ OSD ``--osd-bar-h=<0.1-50>`` Height of the OSD bar, in percentage of the screen height (default: 3.125). +``--osd-bar-border-size=<size>`` + Size of the border of the OSD bar in scaled pixels (see ``--sub-font-size`` + for details). + + Default: 0.5. + ``--osd-back-color=<color>`` See ``--sub-color``. Color used for OSD text background. ``--osd-blur=<0..20.0>`` - Gaussian blur factor. 0 means no blur applied (default). + Gaussian blur factor applied to the OSD font border. + 0 means no blur applied (default). ``--osd-bold=<yes|no>`` Format text on bold. @@ -4357,7 +4461,7 @@ OSD all OSD rendering, use ``--osd-level=0``. It does not affect subtitles or overlays created by scripts (in particular, - the OSC needs to be disabled with ``--no-osc``). + the OSC needs to be disabled with ``--osc=no``). This option is somewhat experimental and could be replaced by another mechanism in the future. @@ -4547,15 +4651,14 @@ Screenshot Default: ``libaom-av1`` ``--screenshot-avif-pixfmt=<format>`` - Specify the pixel format to the libavcodec encoder. - - Default: ``yuv420p`` + Specify the pixel format for the libavcodec encoder. Defaults to empty, + which lets mpv pick one close to the source format. ``--screenshot-avif-opts=key1=value1,key2=value2,...`` Specifies libavcodec options for selected encoder. For more information, consult the FFmpeg documentation. - Default: ``usage=allintra,crf=32,cpu-used=8,tune=ssim`` + Default: ``usage=allintra,crf=0,cpu-used=8`` Note: the default is only guaranteed to work with the libaom-av1 encoder. Above options may not be valid and or optimal for other encoders. @@ -4564,8 +4667,8 @@ Screenshot .. admonition:: Example - "``--screenshot-avif-opts=crf=32,aq-mode=complexity``" - sets the crf to 32 and quantization (aq-mode) to complexity based. + "``--screenshot-avif-opts=crf=23,aq-mode=complexity``" + sets the crf to 23 and quantization (aq-mode) to complexity based. ``--screenshot-sw=<yes|no>`` Whether to use software rendering for screenshots (default: no). @@ -4696,8 +4799,6 @@ Audio Resampler This controls the default options of any resampling done by mpv (but not within libavfilter, within the system audio API resampler, or any other places). -It also sets the defaults for the ``lavrresample`` audio filter. - ``--audio-resample-filter-size=<length>`` Length of the filter with respect to the lower sampling rate. (default: 16) @@ -4718,9 +4819,6 @@ It also sets the defaults for the ``lavrresample`` audio filter. (default: no). If this is disabled, downmix can cause clipping. If it's enabled, the output might be too quiet. It depends on the source audio. - Technically, this changes the ``normalize`` suboption of the - ``lavrresample`` audio filter, which performs the downmixing. - If downmix happens outside of mpv for some reason, or in the decoder (decoder downmixing), or in the audio output (system mixer), this has no effect. @@ -4736,7 +4834,7 @@ It also sets the defaults for the ``lavrresample`` audio filter. ``--audio-swresample-o=<string>`` Set AVOptions on the SwrContext or AVAudioResampleContext. These should - be documented by FFmpeg or Libav. + be documented by FFmpeg. This is a key/value list option. See `List Options`_ for details. @@ -4754,15 +4852,15 @@ Terminal ``--really-quiet`` Display even less output and status messages than with ``--quiet``. -``--no-terminal``, ``--terminal`` - Disable any use of the terminal and stdin/stdout/stderr. This completely - silences any message output. +``--terminal=<yes|no>`` + ``--terminal=no`` disables any use of the terminal and stdin/stdout/stderr. + This completely silences any message output. Unlike ``--really-quiet``, this disables input and terminal initialization as well. -``--no-msg-color`` - Disable colorful console output on terminals. +``--msg-color=<yes|no>`` + Enable colorful console output on terminals (default: yes). ``--msg-level=<module1=level1,module2=level2,...>`` Control verbosity directly for each module. The ``all`` module changes the @@ -4821,7 +4919,7 @@ Terminal The ``auto`` mode also enables terminal OSD if ``--video-osd=no`` was set. -``--term-osd-bar``, ``--no-term-osd-bar`` +``--term-osd-bar=<yes|no>`` Enable printing a progress bar under the status line on the terminal. (Disabled by default.) @@ -4840,10 +4938,6 @@ Terminal See `Property Expansion`_. -``--term-remaining-playtime``, ``--no-term-remaining-playtime`` - When printing out the time on the terminal, show the remaining time adjusted by - playback speed. Default: ``yes`` - ``--term-status-msg=<string>`` Print out a custom string during playback instead of the standard status line. Expands properties. See `Property Expansion`_. @@ -4884,9 +4978,6 @@ Cache of the cache in kilobytes. Use e.g. ``--cache --demuxer-max-bytes=123k`` instead. -``--no-cache`` - Turn off input stream caching. See ``--cache``. - ``--cache-secs=<seconds>`` 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 @@ -5055,7 +5146,7 @@ Network ``--user-agent=<string>`` Use ``<string>`` as user agent for HTTP streaming. -``--cookies``, ``--no-cookies`` +``--cookies=<yes|no>`` Support cookies when making HTTP requests. Disabled by default. ``--cookies-file=<filename>`` @@ -5093,11 +5184,11 @@ Network ``--tls-ca-file=<filename>`` Certificate authority database file for use with TLS. (Silently fails with - older FFmpeg or Libav versions.) + older FFmpeg versions.) ``--tls-verify`` Verify peer certificates when using TLS (e.g. with ``https://...``). - (Silently fails with older FFmpeg or Libav versions.) + (Silently fails with older FFmpeg versions.) ``--tls-cert-file`` A file containing a certificate to use in the handshake with the @@ -5111,7 +5202,7 @@ Network ``--network-timeout=<seconds>`` Specify the network timeout in seconds (default: 60 seconds). This affects - at least HTTP. The special value 0 uses the FFmpeg/Libav defaults. If a + at least HTTP. The special value 0 uses the FFmpeg defaults. If a protocol is used which does not support timeouts, this option is silently ignored. @@ -5205,57 +5296,6 @@ DVB An example ``input.conf`` could contain: ``H cycle dvbin-channel-switch-offset up``, ``K cycle dvbin-channel-switch-offset down`` -ALSA audio output options -------------------------- - - -``--alsa-device=<device>`` - Deprecated, use ``--audio-device`` (requires ``alsa/`` prefix). - -``--alsa-resample=yes`` - Enable ALSA resampling plugin. (This is disabled by default, because - some drivers report incorrect audio delay in some cases.) - -``--alsa-mixer-device=<device>`` - Set the mixer device used with ``ao-volume`` (default: ``default``). - -``--alsa-mixer-name=<name>`` - Set the name of the mixer element (default: ``Master``). This is for - example ``PCM`` or ``Master``. - -``--alsa-mixer-index=<number>`` - Set the index of the mixer channel (default: 0). Consider the output of - "``amixer scontrols``", then the index is the number that follows the - name of the element. - -``--alsa-non-interleaved`` - Allow output of non-interleaved formats (if the audio decoder uses - this format). Currently disabled by default, because some popular - ALSA plugins are utterly broken with non-interleaved formats. - -``--alsa-ignore-chmap`` - Don't read or set the channel map of the ALSA device - only request the - required number of channels, and then pass the audio as-is to it. This - option most likely should not be used. It can be useful for debugging, - or for static setups with a specially engineered ALSA configuration (in - this case you should always force the same layout with ``--audio-channels``, - or it will work only for files which use the layout implicit to your - ALSA device). - -``--alsa-buffer-time=<microseconds>`` - Set the requested buffer time in microseconds. A value of 0 skips requesting - anything from the ALSA API. This and the ``--alsa-periods`` option uses the - ALSA ``near`` functions to set the requested parameters. If doing so results - in an empty configuration set, setting these parameters is skipped. - - Both options control the buffer size. A low buffer size can lead to higher - CPU usage and audio dropouts, while a high buffer size can lead to higher - latency in volume changes and other filtering. - -``--alsa-periods=<number>`` - Number of periods requested from the ALSA API. See ``--alsa-buffer-time`` - for further remarks. - GPU renderer options ----------------------- @@ -5287,8 +5327,8 @@ them. (This filter is an alias for ``jinc``-windowed ``jinc``) ``ewa_lanczossharp`` - A slightly sharpened version of ewa_lanczos. This is the default when - using the ``high-quality`` profile. + A slightly sharpened version of ``ewa_lanczos``. This is the default + when using the ``high-quality`` profile. ``ewa_lanczos4sharpest`` Very sharp scaler, but also slightly slower than ``ewa_lanczossharp``. @@ -5297,8 +5337,10 @@ them. built-in anti-ringing, so no extra action needs to be taken. ``mitchell`` - Mitchell-Netravali. The ``B`` and ``C`` parameters can be set with - ``--scale-param1`` and ``--scale-param2``. + Mitchell-Netravali. Piecewise cubic filter with a support of radius 2.0. + Provides a balanced compromise of all scaling artifacts. This filter has + both ``B`` and ``C`` set to ``1/3``. The ``B`` and ``C`` parameters can + be controlled with ``--scale-param1`` and ``--scale-param2``. ``hermite`` Hermite spline. Similar to ``bicubic`` but with ``B`` set to ``0.0``. @@ -5307,10 +5349,9 @@ them. default for ``--dscale``. ``catmull_rom`` - Catmull-Rom. A Cubic filter in the same vein as ``mitchell``, where - the ``B`` and ``C`` parameters are ``0.0`` and ``0.5`` respectively. - This filter is sharper than ``mitchell``, but it results in more - ringing. + Catmull-Rom spline. Similar to ``mitchell``, but with ``B`` and ``C`` + set to ``0.0`` and ``0.5`` respectively. This filter is sharper than + ``mitchell``, but prone to ringing. ``oversample`` A version of nearest neighbour that (naively) oversamples pixels, so @@ -5532,14 +5573,16 @@ them. no Disable any dithering done by mpv. auto - Automatic selection. If output bit depth cannot be detected, 8 bits per - component are assumed. + Automatic selection. + On ``--vo=gpu``: detected depth or 8 bpc otherwise + On ``--vo=gpu-next``: detected depth or 8 bpc (for SDR target) 8 Dither to 8 bit output. - Note that the depth of the connected video display device cannot be - detected. Often, LCD panels will do dithering on their own, which conflicts - with this option and leads to ugly output. + Note that the on-the-wire bit depth cannot be detected except when using + ``gpu-api=d3d11``. Explicitly setting the value to your display's bit depth + is recommended, as dithering performed by some LCD panels can be of low + quality. ``--dither-size-fruit=<2-8>`` Set the size of the dither matrix (default: 6). The actual size of the @@ -5729,6 +5772,11 @@ them. from Windows 10. Thus on older systems it will only automatically utilize the rgba8 output format. + .. note:: + + For ``--vo=gpu-next``, this is used as a best-effort hint and + libplacebo has the last say on which format is utilized. + ``--d3d11-output-csp=<auto|srgb|linear|pq|bt.2020>`` Select a specific D3D11 output color space to utilize for D3D11 rendering. "auto" is the default, which will select the color space of the desktop @@ -5774,7 +5822,7 @@ them. ``--wayland-disable-vsync=<yes|no>`` Disable mpv's internal vsync for Wayland-based video output (default: no). This is mainly useful for benchmarking wayland VOs when combined with - ``video-sync=display-desync``, ``--no-audio``, and ``--untimed=yes``. + ``video-sync=display-desync``, ``--audio=no``, and ``--untimed=yes``. ``--wayland-edge-pixels-pointer=<value>`` Defines the size of an edge border (default: 16) to initiate client side @@ -5787,8 +5835,8 @@ them. ``--spirv-compiler=<compiler>`` Controls which compiler is used to translate GLSL to SPIR-V. This is - (currently) only relevant for ``--gpu-api=vulkan`` and `--gpu-api=d3d11`. - The possible choices are currently only: + only relevant for ``--gpu-api=d3d11`` with ``--vo=gpu``. + The possible choices are currently: auto Use the first available compiler. (Default) @@ -5798,7 +5846,7 @@ them. .. note:: - This option is deprecated, since there is only one reasonable value. + This option is deprecated, since there is only one usable value. It may be removed in the future. ``--glsl-shader=<file>``, ``--glsl-shaders=<file-list>`` @@ -5811,7 +5859,7 @@ them. and overwrites the internal list with it. The latter is a path list option (see `List Options`_ for details). - .. admonition:: Warning + .. warning:: The syntax is not stable yet and may change any time. @@ -6192,21 +6240,21 @@ them. software renderer, and ``auto`` only falls back to the software renderer when the usual pixel format couldn't be created. - macOS only. + macOS and cocoa-cb only. ``--cocoa-cb-10bit-context=<yes|no>`` Creates a 10bit capable pixel format for the context creation (default: yes). Instead of 8bit integer framebuffer a 16bit half-float framebuffer is requested. - macOS only. + macOS and cocoa-cb only. ``--macos-title-bar-appearance=<appearance>`` Sets the appearance of the title bar (default: auto). Not all combinations of appearances and ``--macos-title-bar-material`` materials make sense or are unique. Appearances that are not supported by you current macOS version fall back to the default value. - macOS and cocoa-cb only + macOS only ``<appearance>`` can be one of the following: @@ -6231,7 +6279,7 @@ them. ``--macos-title-bar-appearance`` appearances make sense or are unique. Materials that are not supported by you current macOS version fall back to the default value. - macOS and cocoa-cb only + macOS only ``<material>`` can be one of the following: @@ -6280,7 +6328,7 @@ them. 1000ms since it's possible that Apple or the user changes the system defaults. Anything higher than 1000ms though seems too long and shouldn't be set anyway. - (macOS and cocoa-cb only) + (macOS) ``--macos-app-activation-policy=<regular|accessory|prohibited>`` @@ -6310,6 +6358,7 @@ them. :precise: Syncs to the time of the next vertical display refresh reported by the CVDisplayLink callback provided information :system: No manual syncing, depend on the layer mechanic and the next drawable + :feedback: Same as precise but uses the presentation feedback core mechanism ``--android-surface-size=<WxH>`` Set dimensions of the rendering surface used by the Android gpu context. @@ -6328,8 +6377,6 @@ them. auto auto-select (default) - cocoa - Cocoa/macOS (deprecated, use --vo=libmpv instead) win Win32/WGL winvk @@ -6355,7 +6402,7 @@ them. drm DRM/EGL displayvk - VK_KHR_display. This backend is roughly the Vukan equivalent of + VK_KHR_display. This backend is roughly the Vulkan equivalent of DRM/EGL, allowing for direct rendering via Vulkan without a display manager. x11egl @@ -6816,8 +6863,11 @@ them. files contain uncompressed LUTs. Their size depends on the ``--icc-3dlut-size``, and can be very big. - NOTE: On ``--vo=gpu``, this is not cleaned automatically, so old, unused - cache files may stick around indefinitely. + On `--vo=gpu-next`, files that have not been accessed in the last 24 hours + may be cleared if the cache limit (1.5 GiB) is exceeded. + + On ``--vo=gpu``, this is not cleaned automatically, so old, unused cache + files may stick around indefinitely. ``--icc-cache-dir`` The directory where icc cache is stored. Cache is stored in the system's @@ -6902,35 +6952,35 @@ them. softsubbed ASS signs to match the video colors, but may cause SRT subtitles or similar to look slightly off. -``--alpha=<blend-tiles|blend|yes|no>`` - Decides what to do if the input has an alpha component. +``--background=<none|color|tiles>`` + If the frame has an alpha component, decide what kind of background, if any, + to blend it with. This does nothing if there is no alpha component. - blend-tiles + color + Blend the frame against the background color (``--background-color``, + normally black). + tiles Blend the frame against a 16x16 gray/white tiles background (default). - blend - Blend the frame against the background color (``--background``, normally - black). - yes - Try to create a framebuffer with alpha component. This only makes sense - if the video contains alpha information (which is extremely rare) or if - you make the background color transparent. May not be supported on all - platforms. If alpha framebuffers are unavailable, it silently falls - back on a normal framebuffer. Note that if you set the ``--fbo-format`` - option to a non-default value, a format with alpha must be specified, - or this won't work. Whether this really works depends on the windowing - system and desktop environment. - no - Ignore alpha component. + none + Do not blend the frame and leave the alpha as is. + + Before mpv 0.38.0, this option used to accept a color value specifying the + background color. This is now done by the ``--background-color`` option. + Use that instead. + +``--background-color=<color>`` + Color used to draw parts of the mpv window not covered by video. See the + ``--sub-color`` option for how colors are defined. + +``--border-background=<none|color|tiles>`` + Same as ``--background`` but only applies to the black bar/border area of + the window. ``vo=gpu-next`` only. Defaults to ``color``. ``--opengl-rectangle-textures`` Force use of rectangle textures (default: no). Normally this shouldn't have any advantages over normal textures. Note that hardware decoding overrides this flag. Could be removed any time. -``--background=<color>`` - Color used to draw parts of the mpv window not covered by video. See the - ``--sub-color`` option for how colors are defined. - ``--gpu-tex-pad-x``, ``--gpu-tex-pad-y`` Enlarge the video source textures by this many pixels. For debugging only (normally textures are sized exactly, but due to hardware decoding interop @@ -6965,11 +7015,14 @@ them. ``--gpu-shader-cache`` Store and load compiled GLSL shaders in the cache directory (Default: ``yes``). Normally, shader compilation is very fast, so this is not usually - needed. It mostly matters for anything based on D3D11 (including ANGLE), as - well as on some other proprietary drivers. Enabling this can improve startup - performance on these platforms. + needed. It mostly matters for anything involving GLSL to SPIR-V conversion, + that is: D3D11, ANGLE or Vulkan, as well as on some other proprietary + drivers. Enabling this can improve startup performance on these platforms. + + On `--vo=gpu-next`, files that have not been accessed in the last 24 hours + may be cleared if the cache limit (128 MiB) is exceeded. - NOTE: On ``--vo=gpu``, is not cleaned automatically, so old, unused cache + On ``--vo=gpu``, this is not cleaned automatically, so old, unused cache files may stick around indefinitely. ``--gpu-shader-cache-dir`` @@ -7006,9 +7059,9 @@ Miscellaneous Specifying ``--autosync=0``, the default, will cause frame timing to be based entirely on audio delay measurements. Specifying ``--autosync=1`` will do the same, but will subtly change the A/V correction algorithm. An - uneven video framerate in a video which plays fine with ``--no-audio`` can + uneven video framerate in a video which plays fine with ``--audio=no`` can often be helped by setting this to an integer value greater than 1. The - higher the value, the closer the timing will be to ``--no-audio``. Try + higher the value, the closer the timing will be to ``--audio=no``. Try ``--autosync=30`` to smooth out problems with sound drivers which do not implement a perfect audio delay measurement. With this value, if large A/V sync offsets occur, they will only take about 1 or 2 seconds to settle diff --git a/DOCS/man/osc.rst b/DOCS/man/osc.rst index c791d75..9ed338a 100644 --- a/DOCS/man/osc.rst +++ b/DOCS/man/osc.rst @@ -211,6 +211,13 @@ Configurable Options Alpha of the seekable ranges, 0 (opaque) to 255 (fully transparent). +``scrollcontrols`` + Default: yes + + By default, going up or down with the mouse wheel can trigger certain + actions (such as seeking) if the mouse is hovering an OSC element. + Set to ``no`` to disable any special mouse wheel behavior. + ``deadzonesize`` Default: 0.5 @@ -300,7 +307,7 @@ Configurable Options String that supports property expansion that will be displayed as OSC title. - ASS tags are escaped, and newlines and trailing slashes are stripped. + ASS tags are escaped and newlines are converted to spaces. ``tooltipborder`` Default: 1 @@ -393,6 +400,13 @@ Configurable Options Supports ``left`` and ``right`` which will place the controls on those respective sides. +``windowcontrols_title`` + Default: ${media-title} + + String that supports property expansion that will be displayed as the + windowcontrols title. + ASS tags are escaped, and newlines and trailing slashes are stripped. + ``greenandgrumpy`` Default: no diff --git a/DOCS/man/stats.rst b/DOCS/man/stats.rst index 88238bc..7ddf851 100644 --- a/DOCS/man/stats.rst +++ b/DOCS/man/stats.rst @@ -85,6 +85,18 @@ Configurable Options respective duration. This can result in overlapping text when multiple scripts decide to print text at the same time. +``term_width_limit`` + Default: -1 + + Sets the terminal width. + A value of 0 means the width is infinite, -1 means it's automatic. + +``term_height_limit`` + Default: -1 + + Sets the terminal height. + A value of 0 means the height is infinite, -1 means it's automatic. + ``plot_perfdata`` Default: yes diff --git a/DOCS/man/vf.rst b/DOCS/man/vf.rst index 1423e4c..59175e7 100644 --- a/DOCS/man/vf.rst +++ b/DOCS/man/vf.rst @@ -204,10 +204,13 @@ Available mpv-only filters are: Available color spaces are: :auto: automatic selection (default) - :bt.601: ITU-R BT.601 (SD) - :bt.709: ITU-R BT.709 (HD) - :bt.2020-ncl: ITU-R BT.2020 non-constant luminance system - :bt.2020-cl: ITU-R BT.2020 constant luminance system + :bt.601: ITU-R Rec. BT.601 (SD) + :bt.709: ITU-R Rec. BT.709 (HD) + :bt.2020-ncl: ITU-R Rec. BT.2020 (non-constant luminance) + :bt.2020-cl: ITU-R Rec. BT.2020 (constant luminance) + :bt.2100-pq: ITU-R Rec. BT.2100 ICtCp PQ variant + :bt.2100-hlg: ITU-R Rec. BT.2100 ICtCp HLG variant + :dolbyvision: Dolby Vision :smpte-240m: SMPTE-240M ``<colorlevels>`` @@ -313,6 +316,10 @@ Available mpv-only filters are: Whether or not to include Dolby Vision metadata (default: yes). If disabled, any Dolby Vision metadata will be stripped from frames. + ``<hdr10plus=yes|no>`` + Whether or not to include HDR10+ metadata (default: yes). If + disabled, any HDR10+ metadata will be stripped from frames. + ``<film-grain=yes|no>`` Whether or not to include film grain metadata (default: yes). If disabled, any film grain metadata will be stripped from frames. diff --git a/DOCS/man/vo.rst b/DOCS/man/vo.rst index 5ee4eaa..41fcb71 100644 --- a/DOCS/man/vo.rst +++ b/DOCS/man/vo.rst @@ -325,7 +325,7 @@ Available video output drivers are: ``null`` Produces no video output. Useful for benchmarking. - Usually, it's better to disable video with ``--no-video`` instead. + Usually, it's better to disable video with ``--video=no`` instead. The following global options are supported by this video output: @@ -336,6 +336,21 @@ Available video output drivers are: ``caca`` Color ASCII art video output driver that works on a text console. + This driver reserves some keys for runtime configuration. These keys are + hardcoded and cannot be bound: + + d and D + Toggle dithering algorithm. + + a and A + Toggle antialiasing method. + + h and H + Toggle charset method. + + c and C + Toggle color method. + .. note:: This driver is a joke. ``tct`` @@ -348,7 +363,7 @@ Available video output drivers are: performance. Note: the TCT image output is not synchronized with other terminal output - from mpv, which can lead to broken images. The options ``--no-terminal`` or + from mpv, which can lead to broken images. The options ``--terminal=no`` or ``--really-quiet`` can help with that. ``--vo-tct-algo=<algo>`` @@ -361,6 +376,25 @@ Available video output drivers are: Uses spaces. Causes vertical resolution to drop twofolds, but in theory works in more places. + ``--vo-tct-buffering=<pixel|line|frame>`` + Specifies the size of data batches buffered before being sent to the + terminal. + + TCT image output is not synchronized with other terminal output from mpv, + which can lead to broken images. Sending data to the terminal in small + batches may improve parallelism between terminal processing and mpv + processing but incurs a static overhead of generating tens of thousands + of small writes. Also, depending on the terminal used, sending frames in + one chunk might help with tearing of the output, especially if not used + with ``--really-quiet`` and other logs interrupt the data stream. + + pixel + Send data to terminal for each pixel. + line + Send data to terminal for each line. (Default) + frame + Send data to terminal for each frame. + ``--vo-tct-width=<width>`` ``--vo-tct-height=<height>`` Assume the terminal has the specified character width and/or height. These default to 80x25 if the terminal size cannot be determined. @@ -567,30 +601,6 @@ Available video output drivers are: This also supports many of the options the ``gpu`` VO has, depending on the backend. -``rpi`` (Raspberry Pi) - Native video output on the Raspberry Pi using the MMAL API. - - The following global options are supported by this video output: - - ``--rpi-display=<number>`` - Select the display number on which the video overlay should be shown - (default: 0). - - ``--rpi-layer=<number>`` - Select the dispmanx layer on which the video overlay should be shown - (default: -10). Note that mpv will also use the 2 layers above the - selected layer, to handle the window background and OSD. Actual video - rendering will happen on the layer above the selected layer. - - ``--rpi-background=<yes|no>`` - Whether to render a black background behind the video (default: no). - Normally it's better to kill the console framebuffer instead, which - gives better performance. - - ``--rpi-osd=<yes|no>`` - Enabled by default. If disabled with ``no``, no OSD layer is created. - This also means there will be no subtitles rendered. - ``drm`` (Direct Rendering Manager) Video output driver using Kernel Mode Setting / Direct Rendering Manager. Should be used when one doesn't want to install full-blown graphical @@ -651,17 +661,20 @@ Available video output drivers are: lower resolution (the video when handled by the hwdec will be on the drmprime-video plane and at full 4K resolution) - ``--drm-format=<xrgb8888|xrgb2101010>`` + ``--drm-format=<xrgb8888|xbgr8888|xrgb2101010|xbgr2101010|yuyv>`` Select the DRM format to use (default: xrgb8888). This allows you to - choose the bit depth of the DRM mode. xrgb8888 is your usual 24 bit per - pixel/8 bits per channel packed RGB format with 8 bits of padding. - xrgb2101010 is a packed 30 bits per pixel/10 bits per channel packed RGB - format with 2 bits of padding. + choose the bit depth and color type of the DRM mode. + + xrgb8888 is your usual 24bpp packed RGB format with 8 bits of padding. + xrgb2101010 is a 30bpp packed RGB format with 2 bits of padding. + yuyv is a 32bpp packed YUV 4:2:2 format. No planar formats are currently + supported. There are cases when xrgb2101010 will work with the ``drm`` VO, but not with the ``drm`` backend for the ``gpu`` VO. This is because with the ``gpu`` VO, in addition to requiring support in your DRM driver, - requires support for xrgb2101010 in your EGL driver + requires support for xrgb2101010 in your EGL driver. + yuyv only ever works with the ``drm`` VO. ``--drm-draw-surface-size=<[WxH]>`` Sets the size of the surface used on the draw plane. The surface will diff --git a/DOCS/mplayer-changes.rst b/DOCS/mplayer-changes.rst index 6ecb1d0..35bf501 100644 --- a/DOCS/mplayer-changes.rst +++ b/DOCS/mplayer-changes.rst @@ -135,10 +135,10 @@ Miscellaneous * Support for precise scrolling which scales the parameter of commands. If the input doesn't support precise scrolling the scale factor stays 1. * Allow changing/adjusting video filters at runtime. (This is also used to make - the ``D`` key insert vf_yadif if deinterlacing is not supported otherwise). + the ``D`` key insert vf_bwdif if deinterlacing is not supported otherwise). * Improved support for .cue files. -Mac OS X +macOS ~~~~~~~~ * Native OpenGL backend. @@ -177,7 +177,7 @@ Internal changes * General code cleanups (including refactoring or rewrites of many parts). * New build system. * Many bug fixes and removal of long-standing issues. -* Generally preferring FFmpeg/Libav over internal demuxers, decoders, and +* Generally preferring FFmpeg over internal demuxers, decoders, and filters. Detailed Listing of User-visible Changes diff --git a/DOCS/release-policy.md b/DOCS/release-policy.md index 2426ab4..23bc48b 100644 --- a/DOCS/release-policy.md +++ b/DOCS/release-policy.md @@ -26,8 +26,13 @@ While on master: - Update the `VERSION` file. -- Update `DOCS/client-api-changes.rst` and `DOCS/interface-changes.rst` - (in particular, update the last version numbers if necessary) +- Update `DOCS/client-api-changes.rst` (in particular, update the last version + number if necessary) + +- Run `TOOLS/gen-interface-changes.py` to refresh `DOCS/interface-changes.rst`, + edit manually as necessary. + +- Delete all `.txt` files in the `DOCS/interface-changes` directory except for `example.txt`. - Create signed commit with changes. diff --git a/DOCS/tech-overview.txt b/DOCS/tech-overview.txt index e723f78..ca565c7 100644 --- a/DOCS/tech-overview.txt +++ b/DOCS/tech-overview.txt @@ -11,7 +11,7 @@ player/*.c: * main(): * basic initializations (e.g. init_libav() and more) * pre-parse command line (verbosity level, config file locations) - * load config files (parse_cfgfiles()) + * load config files (mp_parse_cfgfiles()) * parse command line, add files from the command line to playlist (m_config_parse_mp_command_line()) * check help options etc. (call handle_help_options()), possibly exit @@ -45,7 +45,7 @@ player/*.c: Things worth saying about the playback core: - most state is in MPContext (core.h), which is not available to the subsystems (and should not be made available) - - the currently played tracks are in mpctx->current_tracks, and decoder + - the currently played tracks are in mpctx->current_track, and decoder state in track.dec/d_sub - the other subsystems rarely call back into the frontend, and the frontend polls them instead (probably a good thing) @@ -126,15 +126,16 @@ options/options.h, options/options.c ... to get a copy of its options. - See m_config.h (below) how to access options. + See m_config_core.h (below) how to access options. - The actual option parser is spread over m_option.c, m_config.c, and - parse_commandline.c, and uses the option table in options.c. + The actual option parser is spread over m_option.c, m_config_frontend.c, + and parse_commandline.c, and uses the option table in options.c. -options/m_config.h & m_config.c: - Code for querying and managing options. This (unfortunately) contains both - declarations for the "legacy-ish" global m_config struct, and ways to access - options in a threads-safe way anywhere, like m_config_cache_alloc(). +options/m_config_*.h & m_config_*.c: + Code for querying and managing options. m_config_frontend.h contains + declarations for the "legacy-ish" global m_config struct, while + m_config_core.h provides ways to access options in a threads-safe way + anywhere, like m_config_cache_alloc(). m_config_cache_alloc() lets anyone read, observe, and write options in any thread. The only state it needs is struct mpv_global, which is an opaque @@ -165,16 +166,15 @@ stream/*: E.g. if mpv sees "http://something" on the command line, it will pick stream_lavf.c based on the prefix, and pass the rest of the filename to it. - Some stream inputs are quite special: stream_dvd.c turns DVDs into mpeg + Some stream inputs are quite special: stream_dvdnav.c turns DVDs into mpeg streams (DVDs are actually a bunch of vob files etc. on a filesystem), - stream_tv.c provides TV input including channel switching. Some stream inputs are just there to invoke special demuxers, like stream_mf.c. (Basically to make the prefix "mf://" do something special.) demux/: Demuxers split data streams into audio/video/sub streams, which in turn - are split in packets. Packets (see demux_packet.h) are mostly byte chunks + are split in packets. Packets (see packet.h) are mostly byte chunks tagged with a playback time (PTS). These packets are passed to the decoders. Most demuxers have been removed from this fork, and the only important and @@ -189,6 +189,14 @@ demux/: cache, which is implemented as a list of packets. The cache is complex because it support seeking, multiple ranges, prefetching, and so on. +filters/: + Filter related code. filter.c contains the generic filtering framework + which converts input frames to output frames (audio, video, or demux + packet data). f_decoder_wrapper.c is a source filter which connects the + frontend with the actual audio and video decoders. f_output_chain.c handles + VO/AO output conversions. f_autoconvert.c automatically inserts the + appropriate conversion filters if format conversion is needed. + video/: This contains several things related to audio/video decoding, as well as video filters. @@ -197,22 +205,18 @@ video/: internally. video/decode/: - vd_*.c are video decoders. (There's only vd_lavc.c left.) dec_video.c - handles most of connecting the frontend with the actual decoder. + vd_*.c are video decoders. (There's only vd_lavc.c left.) video/filter/: - vf_*.c and vf.c form the video filter chain. They are fed by the video - decoder, and output the filtered images to the VOs though vf_vo.c. By - default, no video filters (except vf_vo) are used. vf_scale is automatically - inserted if the video output can't handle the video format used by the - decoder. + vf_*.c are video filters. They are fed by the video decoder, and output the + filtered images to the VOs. By default, no video filters are used. video/out/: Video output. They also create GUI windows and handle user input. In most cases, the windowing code is shared among VOs, like x11_common.c for X11 and w32_common.c for Windows. The VOs stand between frontend and windowing code. - vo_gpu can pick a windowing system at runtime, e.g. the same binary can - provide both X11 and Cocoa support on OSX. + vo_gpu and vo_gpu_next can pick a windowing system at runtime, e.g. the same + binary can provide both X11 and Cocoa support on macOS. VOs can be reconfigured at runtime. A vo_reconfig() call can change the video resolution and format, without destroying the window. @@ -224,13 +228,13 @@ audio/: compressed formats used for spdif.) audio/decode/: - ad_*.c and dec_audio.c handle audio decoding. ad_lavc.c is the - decoder using ffmpeg. ad_spdif.c is not really a decoder, but is used for - compressed audio passthrough. + ad_*.c handle audio decoding. ad_lavc.c is the decoder using ffmpeg. + ad_spdif.c is not really a decoder, but is used for compressed audio + passthrough. audio/filter/: - Audio filter chain. af_lavrresample is inserted if any form of conversion - between audio formats is needed. + Audio filters. af_scaletempo2 is inserted by default if playback is different + from normal speed. audio/out/: Audio outputs. @@ -238,9 +242,9 @@ audio/out/: Unlike VOs, AOs can't be reconfigured on a format change. On audio format changes, the AO will simply be closed and re-opened. - There are wrappers to support for two types of audio APIs: push.c and - pull.c. ao.c calls into one of these. They contain generic code to deal - with the data flow these APIs impose. + buffer.c is the wrapper to support for two types of audio APIs: push and + pull. ao.c calls into that. It contains generic code to deal with the data + flow these APIs impose. Note that mpv synchronizes the video to the audio. That's the reason why buggy audio drivers can have a bad influence on playback quality. @@ -257,10 +261,7 @@ sub/: in turn asks dec_sub.c for subtitle overlay bitmaps, which relays the request to one of the sd_*.c subtitle decoders/renderers. - Subtitle loading is in demux/. The MPlayer subreader.c is mostly gone - parts - of it survive in demux_subreader.c. It's used as last fallback, or to handle - some text subtitle types on Libav. It should go away eventually. Normally, - subtitles are loaded via demux_lavf.c. + Subtitle loading is in demux/. Normally, subtitles are loaded via demux_lavf.c. The subtitles are passed to dec_sub.c and the subtitle decoders in sd_*.c as they are demuxed. All text subtitles are rendered by sd_ass.c. If text @@ -272,8 +273,8 @@ sub/: sd_ass.c's internal state. etc/: - The file input.conf is actually integrated into the mpv binary by the - build system. It contains the default keybindings. + The files input.conf and builtin.conf are actually integrated into the mpv + binary by the build system. They contain the default configs and keybindings. Best practices and Concepts within mpv ====================================== @@ -36,14 +36,16 @@ Releases can be found on the [release list][releases]. ## System requirements -- A not too ancient Linux, Windows 10 or later, or macOS 10.15 or later. +- A not too ancient Linux (usually, only the latest releases of distributions + are actively supported), Windows 10 or later, or macOS 10.15 or later. - A somewhat capable CPU. Hardware decoding might help if the CPU is too slow to decode video in realtime, but must be explicitly enabled with the `--hwdec` option. - A not too crappy GPU. mpv's focus is not on power-efficient playback on embedded or integrated GPUs (for example, hardware decoding is not even enabled by default). Low power GPUs may cause issues like tearing, stutter, - etc. The main video output uses shaders for video rendering and scaling, + etc. On such GPUs, it's recommended to use `--profile=fast` for smooth playback. + The main video output uses shaders for video rendering and scaling, rather than GPU fixed function hardware. On Windows, you might want to make sure the graphics drivers are current. In some cases, ancient fallback video output methods can help (such as `--vo=xv` on Linux), but this use is not @@ -92,6 +94,13 @@ Example: meson compile -C build meson install -C build +For libplacebo, meson can use a git check out as a subproject for a convenient +way to compile mpv if a sufficient libplacebo version is not easily available +in the build environment. It will be statically linked with mpv. Example: + + mkdir -p subprojects + git clone https://code.videolan.org/videolan/libplacebo.git --depth=1 --recursive subprojects/libplacebo + Essential dependencies (incomplete list): - gcc or clang @@ -114,7 +123,7 @@ Libass dependencies (when building libass): - gcc or clang, yasm on x86 and x86_64 - fribidi, freetype, fontconfig development headers (for libass) - harfbuzz (required for correct rendering of combining characters, particularly - for correct rendering of non-English text on OSX, and Arabic/Indic scripts on + for correct rendering of non-English text on macOS, and Arabic/Indic scripts on any platform) FFmpeg dependencies (when building FFmpeg): @@ -190,7 +199,7 @@ see the [FAQ][FAQ]. ## Contact -Most activity happens on the IRC channel and the github issue tracker. +Most activity happens on the IRC channel and the GitHub issue tracker. - **GitHub issue tracker**: [issue tracker][issue-tracker] (report bugs here) - **User IRC Channel**: `#mpv` on `irc.libera.chat` diff --git a/RELEASE_NOTES b/RELEASE_NOTES index fe2efb7..5e5a83b 100644 --- a/RELEASE_NOTES +++ b/RELEASE_NOTES @@ -1,10 +1,9 @@ -Release 0.37.0 +Release 0.38.0 ============== -This release requires FFmpeg 4.4 or newer and libplacebo 6.338.0 or newer. +This release requires FFmpeg 4.4 or newer and libplacebo 6.338.2 or newer. -This is the first release to unconditionally require libplacebo, but note that -the new improved renderer (vo_gpu_next) is not yet the default. +The new improved renderer (vo_gpu_next) is being worked on and not the default yet. Features @@ -13,31 +12,30 @@ Features New ~~~ -- ao_oss: add SPDIF passthrough support -- hwtransfer: implement support for HW->HW format conversions -- stream/dvbin: add support for delivery system ISDB-T -- audio/chmap: support up to 64 channels (including 22.2 layout) -- libmpv: add mpv_time_ns() -- vo_gpu, vo_gpu_next: add Vulkan support for macOS -- meson: make libplacebo a required dependency -- hwdec: support videotoolbox hwdec with libplacebo +- scripting: add mp.input() +- player: add an option to automatically turn on deinterlacing +- mac: add more Now Playing information and functionality +- osxbundle: automatically include MoltenVK to enable Vulkan support +- input.conf: bind Ctrl+WHEEL_UP/DOWN to video-zoom +- ao_avfoundation: initial support for AVFoundation audio driver (macOS) +- vo: add context menu support on win32 Changed ~~~~~~~ -- msg: print warning and error messages to stderr -- options: restore old default subtitle selection behavior -- input.conf: swap wheel up/down with wheel left/right +- osc: allow disabling special mouse wheel behavior +- mac: add support for dynamic change of geometry, autofit +- x11_common: allow DPI scale in unit of 0.5 and prefer the value from Xft.dpi +- vo_gpu_next: save cache to separate files +- mac: large amounts of refactoring, internal improvements and cleanups +- builtin.conf: remove debanding from the high-quality profile Removed ~~~~~~~ -- waf: remove waf as a build system -- osc.lua: remove toggle for forced only subpictures (appeared as [F]) -- mac: remove runtime checks and compatibility for macOS older than 10.15 -- cocoa: remove deprecated OpenGL cocoa backend +- player: remove legacy, non-standard Raspberry Pi-specific support code Options and Commands @@ -46,169 +44,125 @@ Options and Commands Added ~~~~~ -- vo_gpu_next: add --hdr-peak-percentile -- player: add --term-remaining-playtime option -- x11: add --x11-wid-title option -- vo_gpu_next: add --libplacebo-opts -- player: add --subs-match-os-language option (replaces 'auto' option) -- vo: add --video-crop -- win32: add --window-corners, --window-affinity, --title-bar, --backdrop-type -- sub: add --sub-stretch-durations option +- options: add secondary-sub-delay +- options: add --osd-bar-border-size +- filter_sdh: add --sub-filter-sdh-enclosures option +- options: add --secondary-sub-ass-override +- audio: add --volume-gain option to control volume in decibels +- command: export current-gpu-context property +- sub: add --sub-lavc-o option +- command: add load-config-file and load-input-conf +- input: add --input-preprocess-wheel option +- player: add loadfile/loadlist insert-next commands +- input: rework window dragging to be command controllable +- options: add --deinterlace-field-parity option +- vo: add video-target-params property +- options: add --input-commands that runs commands at startup Changed ~~~~~~~ -- builtin.conf: add --hdr-peak-percentile=99.995 to gpu-hq profile -- player: add 'always' option to --subs-fallback-forced -- demux_playlist: default to --directory-mode=lazy -- builtin.conf: add --allow-delayed-peak-detect=no to gpu-hq profile -- vo_gpu, vo_gpu_next: support --icc-3dlut-size=auto -- demux: prepend some cache options with --demuxer- -- builtin.conf: modernize internal profiles for higher quality rendering by default, - rename 'gpu-hq' profile to 'high-quality', add 'fast' profile -- vo_gpu, vo_gpu_next: default to dscale=hermite -- builtin.conf: remove deprecated 'opengl-hq' profile -- options: remove a bunch of old option fallbacks/deprecated ones -- vo_gpu: allow --deband-iterations to be 0 -- stream_cdda: deprecate --cdda-toc-bias and always check for offsets -- options: disable --allow-delayed-peak-detect by default -- options: adjust default of --watch-later-options - - -Deprecated -~~~~~~~~~~ - -- command: deprecate shared-script-properties -- demux_cue: deprecate --demuxer-cue-codepage for --metadata-codepage +- player/command: add ability to scale overlay +- mac: add support for window-id property and --drag-and-drop +- command: support passing scale to `keypress` +- mac: add support for --auto-window-resize, --input-cursor-passthrough options +- win32: add support for --input-cursor-passthrough option +- x11_common: support --title-bar +- sub: allow setting special page numbers via --teletext_page +- player: set --hidpi-window-scale to no by default +- osd_libass: update the OSD bar's dent and border size +- player: remove --alpha and rename --background to --background-color +- options: remove --focus-on-open and add --focus-on Removed ~~~~~~~ -- player: remove special 'auto' option from alang/slang/vlang (previous default) -- vo_gpu: remove --tone-mapping-mode -- vo_gpu: remove --scale-wblur, --scale-cutoff etc. -- vo_gpu: remove --scaler-lut-size -- m_option: drop support for -del for list options +- player: remove --term-remaining-playtime option Fixes and Minor Enhancements ---------------------------- -- build: remove unneeded libdl requirement for vaapi -- zimg: fix abort on subsampled input with odd heights -- video_writer: fix gamma for YUV screenshots -- player/video: fix possible crash when changing lavfi-complex -- ad_spdif: fix segfault due to early deallocation -- ao_pipewire: fix race conditon with setting the media role -- draw_bmp: fix overflowing coordinates in mark_rcs -- ao_sndio: use sio_flush() to improve controls responsiveness -- vo_vdpau: fix hwdec for this vo -- vo_gpu, vo_gpu_next: fix setting an empty dscale -- vd_lavc: repeatedly attempt to fallback if hwdec fails in reinit -- options: fix relative time parsing on negative input -- win32: signal DPI changes correctly -- mp_image: properly infer color levels for some pixfmts -- vo_gpu_next: add ability to use named hook params -- vo_gpu_next: take into account PAR when taking screenshots -- ao_audiotrack: support more channel layouts -- osc.lua: support speed-independent time-remaining display -- sub: fix switching tracks while paused -- audio: fix clipping with gapless audio enabled -- player/video: avoid spamming logs with EOF -- player/command: detect aspect ratio name and show it in stats.lua -- wayland: keyboard input fixes -- demux_playlist: remove len restriction on headerless m3u -- win32: fix display resolution calculation on mulitple monitors -- vo_gpu_next: multiple adjustments and fixes to interpolation -- loadfile: avoid infinite playlist loading loops -- context_drm_egl: don't free EGL properties if they are null -- x11: require xrandr 1.4, remove xinerama support -- drm_common: skip cards that don't have connected outputs -- win32_common: fixes minimized window being focused on launch -- ao/jack: set device_buffer to JACK buffer size -- meson: rename all features with underscores -- input: add new keys: Back, Tools, ZoomIn, ZoomOut -- win32: don't ignore --screen and --fs-screen -- input: add missing keypad key defines and Windows multimedia keys -- player: use audio pts corresponding to playing audio on EOF -- command: add sub-ass-extradata property -- vo_dmabuf_wayland: unmap osd surface when not needed -- player: always write redirect entries for resuming playback -- stats.lua: reorganize how video params are displayed -- stats.lua: display HDR metadata -- osc.lua: add scrolling to the seekbar -- demux_lavf: prefer track durations over container durations to determine total -- vo: vulkan: allow picking devices by UUID -- video: allow overriding container crop if it is present -- vo_gpu, vo_gpu_next, screenshot: support applying crop for screenshots -- sd_lavc: account for floating point inaccuracy, fix sub PTS -- stream: accept dav:// and davs:// urls -- filter_kernels: refine some constants -- filter_kernels: add ewa_lanczos4sharpest -- osc.lua: add scrolling to audio/sub buttons -- demux_mkv: support cropping and rotation -- vo_dmabuf_wayland: support 90 degree rotations -- filter_kernels: add hermite filter -- vo: avoid unnecessary redraws when the OSD shows -- scripting: support DLL cplugins -- af_scaletempo2: various bug fixes -- sdl_gamepad: fix button detection on modern controllers -- vo_dmabuf_wayland: support osd rendering when there's no video -- demux_playlist: add --directory-mode=auto -- vo_gpu_next: use proper color for subtitles -- win32: add an option to change window affinity and control title bar state -- win32: reduce top border thickness to imitate DWM invisible borders -- wayland: remove gnome-specific idle-inhibit warning -- win32: pass window handle to the window-id property -- osc.lua: fix calculation for slider's min-max average -- recorder: fix a couple of memory leaks -- af_scaletempo2: raise max playback rate to 8.0 -- osc.lua: move the idle logo behind other overlays -- hwdec_drmprime: add nv16 support -- various: change internal timing code to work in nanoseconds instead of microseconds -- vo: increase display refresh rate estimation limit from 99 Hz to 400 Hz -- external_files: base cover-art-whitelist on cover-art-auto-exts -- path: don't override cache and state paths with --config-dir -- codec_tags: map some more image mimetypes -- af/vf-command: add ability to target a specific lavfi filter -- win32: prevent white window flash on startup -- demux_playlist: use --metacode-codepage when parsing playlist files -- video: revert racey change that led to stutter and deadlocking -- console.lua: various improvements -- command: add playlist-next-playlist and playlist-prev-playlist -- ytdl_hook.lua: set metadata with single tracks -- defaults.lua: add a disabled parameter to timer constructors -- terminal-unix: race condition fixes -- af_scaletempo2: better defaults -- hwtransfer: handle hwcontexts that don't implement frame constraints -- stream_cdda: remove fallback for ancient libcdio versions -- osdep: drop support for C11 without atomics -- dvbin: do a big cleanup on code style and functions -- ytdl_hook.lua: parse the separate cookies field -- sub: update subtitles if current track is an image -- javascript: use --js-memory-report option instead of MPV_LEAK_REPORT -- ao_coreaudio: signal buffer underruns -- ytdl_hook.lua: support thumbnails -- demux: make hysteresis-secs respect cache-secs -- mp_image: pass rotation correctly to/from AVFrame correctly -- various: add new internal mp_thread abstraction and use it -- drm: use present_sync mechanism for presentation feedback -- vo_gpu: apply ICC profile and dithering only to window screenshots -- audio: introduce ao_read_data_nonblocking() and use it in relevant backends -- wayland: obey initial size hints set by the compositor -- command: export storage aspect ratio (sar) properties -- vo: delay vsync samples by at least 10 refreshes to improve convergence time -- vo_sdl: fix broken mouse wheel multiplier -- vo_gpu_next: simplify cache code and don't re-save when unmodified +- vaapi: add support for vaapi-win32 +- ao/coreaudio_exclusive: fix segfault when changing formats +- mac: fix libmpv usage without embedding +- build: only directly link shaderc and spirv-cross on windows +- build: enable dvbin by default again +- playloop: use a 16:9 ratio with --force-window +- mac: report modifier keys on precise scrolling +- input: fix double click handling +- input: add missing forward media key ('GO_FORWARD') +- sub: don't busy loop if the player is paused for cache +- playlist: correctly populate playlist-path with the --playlist option +- win32: opt in to the windows segment heap +- demux_lavf: remove fix_editlists hack for mp4 +- wayland: fix key modifier handling again +- win32: re-enable IME +- wayland_common: guard against negative configure sizes +- demux_lavf: detect ico as images +- bash-completion: parse the mpv options lazily +- f_auto_filters: use bwdif_cuda for deinterlacing with cuda hwdec +- command: do a normal seek instead of a refresh seek when switching vo +- ad_spdif: fix DTS 44.1khz passthrough playback +- vo_dmabuf_wayland: scale smarter in hidpi situations +- hwdec_drmprime: add P010 and P210 support +- wayland: look for "default" cursor as well as "left_ptr" +- wayland_common: properly handle high resolution scrolling +- vo_vdpau: fix timing for nanoseconds +- demux_mkv: add tags for A_MPEG/L1 (mp1) audio and FFV1 video +- osdep: fix infinite loop when cancelling subprocess +- dec_sub: expand sub packet caching +- player/sub: avoid wasteful subtitle redraws +- ao_pipewire: add support for SPDIF formats +- wayland_common: implement multi-seat support +- vo: make libmpv last in the auto-probing order for cocoa-cb only +- player/loadfile: rewrite sub autoselection logic +- meson: add custom target for macOS App bundling +- image_writer: cleanup leftover files if image writing fails +- sub: fix LRC lines with multiple timestamps +- ao: don't clip floating point formats at non-unity gain +- wayland: remove old sway/wlroots hack +- hwdec/dmabuf_interop_gl: migrate to EXT_EGL_image_storage on desktop GL +- sd_ass: don't wrongly recognize \pos as \p +- ao_audiotrack: fix missing check for passthrough support +- wayland_common: log if Drag-and-Drop fails +- vo_gpu_next: render subtitles at video colorspace +- swresample: stop using deprecated {in,out}_channel_layout options +- path: don't load any files if --no-config is passed (incl. cache and state) +- vo_gpu/vo_gpu_next: fix transparency with GLX +- wayland_common: require WAYLAND_DISPLAY to be set for initialization +- vo_drm: add support for BGR formats and YUYV +- stream: enable caching for sockets, pipes and FIFOs +- ao_coreaudio: stop audio unit after idle timeout +- vo_gpu_next: add support for --dither-depth=auto +- vo_gpu_next: don't render ASS subtitles at HDR colorspace +- video/image_writer: attach MDVC metadata and CLLI metadata +- win32: resolve dropped shell links (Windows shortcuts) +- af_lavcac3enc: fix memory leak on 2ch audio +- win32: increase hires timer resolution +- input: raise maximum key down limit to 16 +- input: remove max active section limit +- video/egl_helpers: fix fallback logic for EGL_KHR_create_context +- ao_pulse: reenable latency hacks by default +- hwdec/vulkan: enable the stable AV1 extension +- af_scaletempo2: fix false reporting of frame availability +- win32: fix native key repeat support +- ao_pipewire: fix some buffer size / timing calculations +- audio: pause handling improvements for pull-based AOs +- ad_spdif: specify missing media type and sample rate +- mac/vulkan: add support for frame timing via presentation feedback +- demux_mkv: corrected direction of ProjectionPoseRoll rotation +- demux_mkv: add support for BCP 47 language tags +- player: avoid busy looping during subtitle track switches This listing is not complete. Check DOCS/client-api-changes.rst for a history of changes to the client API, and DOCS/interface-changes.rst for a history of changes to other user-visible interfaces. -A complete changelog can be seen by running `git log v0.36.0..v0.37.0` +A complete changelog can be seen by running `git log v0.37.0..v0.38.0` in the git repository or by visiting either -https://github.com/mpv-player/mpv/compare/v0.36.0...v0.37.0 or -https://git.srsfckn.biz/mpv/log/?qt=range&q=v0.36.0..v0.37.0 +https://github.com/mpv-player/mpv/compare/v0.37.0...v0.38.0 or +https://git.srsfckn.biz/mpv/log/?qt=range&q=v0.37.0..v0.38.0 diff --git a/TOOLS/dylib-unhell.py b/TOOLS/dylib_unhell.py index c41d200..c885969 100755 --- a/TOOLS/dylib-unhell.py +++ b/TOOLS/dylib_unhell.py @@ -5,6 +5,7 @@ import os import sys import shutil import subprocess +import json from functools import partial sys_re = re.compile("^/System") @@ -41,7 +42,7 @@ def otool(objfile, rapths): def get_rapths(objfile): rpaths = [] command = "otool -l '%s' | grep -A2 LC_RPATH | grep path" % objfile - pathRe = re.compile("^\s*path (.*) \(offset \d*\)$") + pathRe = re.compile(r"^\s*path (.*) \(offset \d*\)$") try: result = subprocess.check_output(command, shell = True, universal_newlines=True) @@ -49,14 +50,19 @@ def get_rapths(objfile): return rpaths for line in result.splitlines(): - rpaths.append(pathRe.search(line).group(1).strip()) + line_clean = pathRe.search(line).group(1).strip() + # resolve @loader_path + if line_clean.startswith('@loader_path/'): + line_clean = line_clean[len('@loader_path/'):] + line_clean = os.path.normpath(os.path.join(os.path.dirname(objfile), line_clean)) + rpaths.append(line_clean) return rpaths def get_rpaths_dev_tools(binary): - command = "otool -l '%s' | grep -A2 LC_RPATH | grep path | grep \"Xcode\|CommandLineTools\"" % binary + command = "otool -l '%s' | grep -A2 LC_RPATH | grep path | grep \"Xcode\\|CommandLineTools\"" % binary result = subprocess.check_output(command, shell = True, universal_newlines=True) - pathRe = re.compile("^\s*path (.*) \(offset \d*\)$") + pathRe = re.compile(r"^\s*path (.*) \(offset \d*\)$") output = [] for line in result.splitlines(): @@ -82,6 +88,23 @@ def resolve_lib_path(objfile, lib, rapths): raise Exception('Could not resolve library: ' + lib) +def check_vulkan_max_version(version): + try: + result = subprocess.check_output("pkg-config vulkan --max-version=" + version, shell = True) + return True + except: + return False + +def get_homebrew_prefix(): + # set default to standard ARM path, intel path is already in the vulkan loader search array + result = "/opt/homebrew" + try: + result = subprocess.check_output("brew --prefix", universal_newlines=True, shell=True, stderr=subprocess.DEVNULL).strip() + except: + pass + + return result + def install_name_tool_change(old, new, objfile): subprocess.call(["install_name_tool", "-change", old, new, objfile], stderr=subprocess.DEVNULL) @@ -109,6 +132,9 @@ def libraries(objfile, result = dict(), result_relative = set(), rapths = []): def lib_path(binary): return os.path.join(os.path.dirname(binary), 'lib') +def resources_path(binary): + return os.path.join(os.path.dirname(binary), '../Resources') + def lib_name(lib): return os.path.join("@executable_path", "lib", os.path.basename(lib)) @@ -157,12 +183,68 @@ def process_swift_libraries(binary): print(">> setting additional rpath for swift libraries") install_name_tool_add_rpath("@executable_path/lib", binary) +def process_vulkan_loader(binary, loaderName, loaderRelativeFolder, libraryNode): + # https://github.com/KhronosGroup/Vulkan-Loader/blob/main/docs/LoaderDriverInterface.md#example-macos-driver-search-path + # https://github.com/KhronosGroup/Vulkan-Loader/blob/main/docs/LoaderLayerInterface.md#macos-layer-discovery + loaderSystemSearchFolders = [ + os.path.join(os.path.expanduser("~"), ".config", loaderRelativeFolder), + os.path.join("/etc/xdg", loaderRelativeFolder), + os.path.join("/usr/local/etc", loaderRelativeFolder), + os.path.join("/etc", loaderRelativeFolder), + os.path.join(os.path.expanduser("~"), ".local/share", loaderRelativeFolder), + os.path.join("/usr/local/share", loaderRelativeFolder), + os.path.join("/usr/share/vulkan", loaderRelativeFolder), + os.path.join(get_homebrew_prefix(), 'share', loaderRelativeFolder), + ] + + loaderSystemFolder = "" + for loaderSystemSearchFolder in loaderSystemSearchFolders: + if os.path.exists(loaderSystemSearchFolder): + loaderSystemFolder = loaderSystemSearchFolder + break + + if not loaderSystemFolder: + print(">>> could not find loader folder " + loaderRelativeFolder) + return + + loaderBundleFolder = os.path.join(resources_path(binary), loaderRelativeFolder) + loaderSystemPath = os.path.join(loaderSystemFolder, loaderName) + loaderBundlePath = os.path.join(loaderBundleFolder, loaderName) + libraryRelativeFolder = "../../../Frameworks/" + + if not os.path.exists(loaderSystemPath): + print(">>> could not find loader " + loaderName) + return + + if not os.path.exists(loaderBundleFolder): + os.makedirs(loaderBundleFolder) + + loaderSystemFile = open(loaderSystemPath, 'r') + loaderJsonData = json.load(loaderSystemFile) + librarySystemPath = os.path.join(loaderSystemFolder, loaderJsonData[libraryNode]["library_path"]) + + if not os.path.exists(librarySystemPath): + print(">>> could not find loader library " + librarySystemPath) + return + + print(">>> modifiying and writing loader json " + loaderName) + loaderBundleFile = open(loaderBundlePath, 'w') + loaderLibraryName = os.path.basename(librarySystemPath) + loaderJsonData[libraryNode]["library_path"] = os.path.join(libraryRelativeFolder, loaderLibraryName) + json.dump(loaderJsonData, loaderBundleFile, indent=4) + + print(">>> copying loader library " + loaderLibraryName) + frameworkBundleFolder = os.path.join(loaderBundleFolder, libraryRelativeFolder) + if not os.path.exists(frameworkBundleFolder): + os.makedirs(frameworkBundleFolder) + shutil.copy(librarySystemPath, os.path.join(frameworkBundleFolder, loaderLibraryName)) + def remove_dev_tools_rapths(binary): for path in get_rpaths_dev_tools(binary): install_name_tool_delete_rpath(path, binary) -def main(): - binary = os.path.abspath(sys.argv[1]) +def process(binary): + binary = os.path.abspath(binary) if not os.path.exists(lib_path(binary)): os.makedirs(lib_path(binary)) print(">> gathering all linked libraries") @@ -177,5 +259,10 @@ def main(): print(">> copying and processing swift libraries") process_swift_libraries(binary) + print(">> copying and processing vulkan loader") + process_vulkan_loader(binary, "MoltenVK_icd.json", "vulkan/icd.d", "ICD") + if check_vulkan_max_version("1.3.261.1"): + process_vulkan_loader(binary, "VkLayer_khronos_synchronization2.json", "vulkan/explicit_layer.d", "layer") + if __name__ == "__main__": - main() + process(sys.argv[1]) diff --git a/TOOLS/gen-interface-changes.py b/TOOLS/gen-interface-changes.py new file mode 100755 index 0000000..7f5435e --- /dev/null +++ b/TOOLS/gen-interface-changes.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 + +# Generate a new interface-changes.rst based on the entries in +# the interface-changes directory. + +# +# This file is part of mpv. +# +# mpv is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# mpv is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with mpv. If not, see <http://www.gnu.org/licenses/>. +# + +import pathlib +import sys +from shutil import which +from subprocess import check_output + +def add_new_entries(docs_dir, out, git): + changes_dir = pathlib.Path(docs_dir) / "interface-changes" + files = [] + for f in pathlib.Path(changes_dir).glob("*.txt"): + if f.is_file() and not f.name == "example.txt": + timestamp = check_output([git, "log", "--format=%ct", "-n", "1", "--", + f], encoding="UTF-8") + if timestamp: + files.append((f, timestamp)) + else: + print(f"Skipping file not tracked by git: {f.name}") + + files.sort(key=lambda x: x[1]) + for file in files: + with open(file[0].resolve(), "r") as f: + for line in f: + line = " - " + line.rstrip() + out.write(line + "\n") + +if __name__ == "__main__": + if len(sys.argv) < 2: + print(f"Usage: {sys.argv[0]} <version>") + sys.exit(1) + + git = which('git') + if not git: + print("Unable to find git binary") + sys.exit(1) + + # Accept passing only the major version number and the full 0 version. + major_version = -1 + if sys.argv[1].isdigit(): + major_version = sys.argv[1] + else: + ver_split = sys.argv[1].split(".") + if len(ver_split) == 3 and ver_split[1].isdigit(): + major_version = ver_split[1] + + if major_version == -1: + print(f"Invalid version number: {sys.argv[1]}") + sys.exit(1) + + docs_dir = pathlib.Path(sys.argv[0]).resolve().parents[1] / "DOCS" + interface_changes = docs_dir / "interface-changes.rst" + with open(interface_changes, "r") as f: + lines = [line.rstrip() for line in f] + + ver_line = " --- mpv 0." + major_version + ".0 ---" + next_ver_line = " --- mpv 0." + str(int(major_version) + 1) + ".0 ---" + with open(interface_changes, "w", newline="\n") as f: + for line in lines: + if line == ver_line: + f.write(next_ver_line + "\n") + f.write(line + "\n") + if line == ver_line: + add_new_entries(docs_dir, f, git) diff --git a/TOOLS/lua/autocrop.lua b/TOOLS/lua/autocrop.lua index b9e1120..ea57d15 100644 --- a/TOOLS/lua/autocrop.lua +++ b/TOOLS/lua/autocrop.lua @@ -177,7 +177,7 @@ function detect_end() else mp.msg.error("No crop data.") mp.msg.info("Was the cropdetect filter successfully inserted?") - mp.msg.info("Does your version of ffmpeg/libav support AVFrame metadata?") + mp.msg.info("Does your version of FFmpeg support AVFrame metadata?") return end diff --git a/TOOLS/lua/autodeint.lua b/TOOLS/lua/autodeint.lua index b891c9a..4e92960 100644 --- a/TOOLS/lua/autodeint.lua +++ b/TOOLS/lua/autodeint.lua @@ -8,7 +8,7 @@ -- telecined and the interlacing field dominance. -- -- Based on this information, it may set mpv's ``deinterlace`` property (which --- usually inserts the yadif filter), or insert the ``pullup`` filter if the +-- usually inserts the bwdif filter), or insert the ``pullup`` filter if the -- content is telecined. It also sets field dominance with lavfi setfield. -- -- OPTIONS: diff --git a/TOOLS/lua/autoload.lua b/TOOLS/lua/autoload.lua index 4003cbc..e0cfeb2 100644 --- a/TOOLS/lua/autoload.lua +++ b/TOOLS/lua/autoload.lua @@ -73,17 +73,17 @@ function Split (s) return set end -EXTENSIONS_VIDEO = Set { +EXTENSIONS_VIDEO_DEFAULT = Set { '3g2', '3gp', 'avi', 'flv', 'm2ts', 'm4v', 'mj2', 'mkv', 'mov', 'mp4', 'mpeg', 'mpg', 'ogv', 'rmvb', 'webm', 'wmv', 'y4m' } -EXTENSIONS_AUDIO = Set { +EXTENSIONS_AUDIO_DEFAULT = Set { 'aiff', 'ape', 'au', 'flac', 'm4a', 'mka', 'mp3', 'oga', 'ogg', 'ogm', 'opus', 'wav', 'wma' } -EXTENSIONS_IMAGES = Set { +EXTENSIONS_IMAGES_DEFAULT = Set { 'avif', 'bmp', 'gif', 'j2k', 'jp2', 'jpeg', 'jpg', 'jxl', 'png', 'svg', 'tga', 'tif', 'tiff', 'webp' } @@ -97,9 +97,21 @@ split_option_exts(true, true, true) function create_extensions() EXTENSIONS = {} - if o.videos then SetUnion(SetUnion(EXTENSIONS, EXTENSIONS_VIDEO), o.additional_video_exts) end - if o.audio then SetUnion(SetUnion(EXTENSIONS, EXTENSIONS_AUDIO), o.additional_audio_exts) end - if o.images then SetUnion(SetUnion(EXTENSIONS, EXTENSIONS_IMAGES), o.additional_image_exts) end + EXTENSIONS_VIDEO = {} + EXTENSIONS_AUDIO = {} + EXTENSIONS_IMAGES = {} + if o.videos then + SetUnion(SetUnion(EXTENSIONS_VIDEO, EXTENSIONS_VIDEO_DEFAULT), o.additional_video_exts) + SetUnion(EXTENSIONS, EXTENSIONS_VIDEO) + end + if o.audio then + SetUnion(SetUnion(EXTENSIONS_AUDIO, EXTENSIONS_AUDIO_DEFAULT), o.additional_audio_exts) + SetUnion(EXTENSIONS, EXTENSIONS_AUDIO) + end + if o.images then + SetUnion(SetUnion(EXTENSIONS_IMAGES, EXTENSIONS_IMAGES_DEFAULT), o.additional_image_exts) + SetUnion(EXTENSIONS, EXTENSIONS_IMAGES) + end end create_extensions() @@ -211,6 +223,12 @@ function scan_dir(path, current_file, dir_mode, separator, dir_depth, total_file end function find_and_add_entries() + local aborted = mp.get_property_native("playback-abort") + if aborted then + msg.debug("stopping: playback aborted") + return + end + local path = mp.get_property("path", "") local dir, filename = utils.split_path(path) msg.trace(("dir: %s, filename: %s"):format(dir, filename)) diff --git a/TOOLS/lua/skip-logo.lua b/TOOLS/lua/skip-logo.lua index 8e1f9da..ae66b22 100644 --- a/TOOLS/lua/skip-logo.lua +++ b/TOOLS/lua/skip-logo.lua @@ -232,7 +232,7 @@ local function read_frames() end end -mp.observe_property(meta_property, "none", function() +mp.observe_property(meta_property, "native", function() -- Ignore frames that are decoded/filtered during seeking. if seeking then return diff --git a/TOOLS/matroska.py b/TOOLS/matroska.py index 52bac48..61a33ff 100755 --- a/TOOLS/matroska.py +++ b/TOOLS/matroska.py @@ -92,6 +92,7 @@ elements_matroska = ( 'MaxBlockAdditionID, 55ee, uint', 'Name, 536e, str', 'Language, 22b59c, str', + 'LanguageBCP47, 22b59d, str', 'CodecID, 86, str', 'CodecPrivate, 63a2, binary', 'CodecName, 258688, str', @@ -206,6 +207,7 @@ elements_matroska = ( 'ChapterDisplay*, 80, sub', ( 'ChapString, 85, str', 'ChapLanguage*, 437c, str', + 'ChapLanguageBCP47*, 437d, str', 'ChapCountry*, 437e, str', ), ), @@ -224,6 +226,7 @@ elements_matroska = ( 'SimpleTag*, 67c8, sub', ( 'TagName, 45a3, str', 'TagLanguage, 447a, str', + 'TagLanguageBCP47, 447b, str', 'TagString, 4487, str', 'TagDefault, 4484, uint', ), diff --git a/TOOLS/osxbundle.py b/TOOLS/osxbundle.py index 98699e4..0e156a0 100755 --- a/TOOLS/osxbundle.py +++ b/TOOLS/osxbundle.py @@ -1,13 +1,11 @@ #!/usr/bin/env python3 import os import shutil -import sys import fileinput +import dylib_unhell +import subprocess from optparse import OptionParser -def sh(command): - return os.popen(command).read().strip() - def bundle_path(binary_name): return "%s.app" % binary_name @@ -24,11 +22,11 @@ def target_binary(binary_name): return os.path.join(target_directory(binary_name), os.path.basename(binary_name)) -def copy_bundle(binary_name): +def copy_bundle(binary_name, src_path): if os.path.isdir(bundle_path(binary_name)): shutil.rmtree(bundle_path(binary_name)) shutil.copytree( - os.path.join('TOOLS', 'osxbundle', bundle_name(binary_name)), + os.path.join(src_path, 'TOOLS', 'osxbundle', bundle_name(binary_name)), bundle_path(binary_name)) def copy_binary(binary_name): @@ -39,20 +37,24 @@ def apply_plist_template(plist_file, version): print(line.rstrip().replace('${VERSION}', version)) def sign_bundle(binary_name): - sh('codesign --force --deep -s - ' + bundle_path(binary_name)) - -def bundle_version(): - if os.path.exists('VERSION'): - x = open('VERSION') + sign_directories = ['Contents/Frameworks', 'Contents/MacOS'] + for dir in sign_directories: + resolved_dir = os.path.join(bundle_path(binary_name), dir) + for root, _dirs, files in os.walk(resolved_dir): + for f in files: + subprocess.run(['codesign', '--force', '-s', '-', os.path.join(root, f)]) + subprocess.run(['codesign', '--force', '-s', '-', bundle_path(binary_name)]) + +def bundle_version(src_path): + version = 'UNKNOWN' + version_path = os.path.join(src_path, 'VERSION') + if os.path.exists(version_path): + x = open(version_path) version = x.read() x.close() - else: - version = sh("./version.sh").strip() return version def main(): - version = bundle_version().rstrip() - usage = "usage: %prog [options] arg" parser = OptionParser(usage) parser.add_option("-s", "--skip-deps", action="store_false", dest="deps", @@ -61,14 +63,17 @@ def main(): (options, args) = parser.parse_args() - if len(args) != 1: + if len(args) < 1 or len(args) > 2: parser.error("incorrect number of arguments") else: binary_name = args[0] + src_path = args[1] if len(args) > 1 else "." + + version = bundle_version(src_path).rstrip() - print("Creating Mac OS X application bundle (version: %s)..." % version) + print("Creating macOS application bundle (version: %s)..." % version) print("> copying bundle skeleton") - copy_bundle(binary_name) + copy_bundle(binary_name, src_path) print("> copying binary") copy_binary(binary_name) print("> generating Info.plist") @@ -76,7 +81,7 @@ def main(): if options.deps: print("> bundling dependencies") - print(sh(" ".join(["TOOLS/dylib-unhell.py", target_binary(binary_name)]))) + dylib_unhell.process(target_binary(binary_name)) print("> signing bundle with ad-hoc pseudo identity") sign_bundle(binary_name) diff --git a/TOOLS/osxbundle/mpv.app/Contents/Info.plist b/TOOLS/osxbundle/mpv.app/Contents/Info.plist index e239dc7..eaa83ad 100644 --- a/TOOLS/osxbundle/mpv.app/Contents/Info.plist +++ b/TOOLS/osxbundle/mpv.app/Contents/Info.plist @@ -188,6 +188,8 @@ <string>${VERSION}</string> <key>NSHighResolutionCapable</key> <true/> + <key>LSApplicationCategoryType</key> + <string>public.app-category.games</string> <key>LSEnvironment</key> <dict> <key>MallocNanoZone</key> @@ -1 +1 @@ -0.37.0 +0.38.0 diff --git a/audio/chmap.c b/audio/chmap.c index e2b95f4..a56d78d 100644 --- a/audio/chmap.c +++ b/audio/chmap.c @@ -239,8 +239,8 @@ void mp_chmap_set_unknown(struct mp_chmap *dst, int num_channels) } } -// Return the ffmpeg/libav channel layout as in <libavutil/channel_layout.h>. -// Speakers not representable by ffmpeg/libav are dropped. +// Return the ffmpeg channel layout as in <libavutil/channel_layout.h>. +// Speakers not representable by ffmpeg are dropped. // Warning: this ignores the order of the channels, and will return a channel // mask even if the order is different from libavcodec's. // Also, "unknown" channel maps are translated to non-sense channel @@ -263,7 +263,7 @@ uint64_t mp_chmap_to_lavc_unchecked(const struct mp_chmap *src) return mask; } -// Return the ffmpeg/libav channel layout as in <libavutil/channel_layout.h>. +// Return the ffmpeg channel layout as in <libavutil/channel_layout.h>. // Returns 0 if the channel order doesn't match lavc's or if it's invalid. uint64_t mp_chmap_to_lavc(const struct mp_chmap *src) { @@ -272,7 +272,7 @@ uint64_t mp_chmap_to_lavc(const struct mp_chmap *src) return mp_chmap_to_lavc_unchecked(src); } -// Set channel map from the ffmpeg/libav channel layout as in +// Set channel map from the ffmpeg channel layout as in // <libavutil/channel_layout.h>. // If the number of channels exceed MP_NUM_CHANNELS, set dst to empty. void mp_chmap_from_lavc(struct mp_chmap *dst, uint64_t src) diff --git a/audio/decode/ad_lavc.c b/audio/decode/ad_lavc.c index 08b789a..9b5f1fb 100644 --- a/audio/decode/ad_lavc.c +++ b/audio/decode/ad_lavc.c @@ -44,6 +44,7 @@ #include "options/options.h" struct priv { + struct mp_codec_params *codec; AVCodecContext *avctx; AVFrame *avframe; AVPacket *avpkt; @@ -156,7 +157,7 @@ static bool init(struct mp_filter *da, struct mp_codec_params *codec, return true; } -static void destroy(struct mp_filter *da) +static void ad_lavc_destroy(struct mp_filter *da) { struct priv *ctx = da->priv; @@ -165,7 +166,7 @@ static void destroy(struct mp_filter *da) mp_free_av_packet(&ctx->avpkt); } -static void reset(struct mp_filter *da) +static void ad_lavc_reset(struct mp_filter *da) { struct priv *ctx = da->priv; @@ -219,6 +220,8 @@ static int receive_frame(struct mp_filter *da, struct mp_frame *out) if (!priv->avframe->buf[0]) return ret; + mp_codec_info_from_av(avctx, priv->codec); + double out_pts = mp_pts_from_av(priv->avframe->pts, &priv->codec_timebase); struct mp_aframe *mpframe = mp_aframe_from_avframe(priv->avframe); @@ -276,7 +279,7 @@ static int receive_frame(struct mp_filter *da, struct mp_frame *out) return ret; } -static void process(struct mp_filter *ad) +static void ad_lavc_process(struct mp_filter *ad) { struct priv *priv = ad->priv; @@ -286,9 +289,9 @@ static void process(struct mp_filter *ad) static const struct mp_filter_info ad_lavc_filter = { .name = "ad_lavc", .priv_size = sizeof(struct priv), - .process = process, - .reset = reset, - .destroy = destroy, + .process = ad_lavc_process, + .reset = ad_lavc_reset, + .destroy = ad_lavc_destroy, }; static struct mp_decoder *create(struct mp_filter *parent, @@ -305,12 +308,16 @@ static struct mp_decoder *create(struct mp_filter *parent, da->log = mp_log_new(da, parent->log, NULL); struct priv *priv = da->priv; + priv->codec = codec; priv->public.f = da; if (!init(da, codec, decoder)) { talloc_free(da); return NULL; } + + codec->codec_desc = priv->avctx->codec_descriptor->long_name; + return &priv->public; } diff --git a/audio/decode/ad_spdif.c b/audio/decode/ad_spdif.c index 393af8a..98a53f3 100644 --- a/audio/decode/ad_spdif.c +++ b/audio/decode/ad_spdif.c @@ -37,6 +37,12 @@ #define OUTBUF_SIZE 65536 +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(60, 26, 100) +#define AV_PROFILE_UNKNOWN FF_PROFILE_UNKNOWN +#define AV_PROFILE_DTS_HD_HRA FF_PROFILE_DTS_HD_HRA +#define AV_PROFILE_DTS_HD_MA FF_PROFILE_DTS_HD_MA +#endif + struct spdifContext { struct mp_log *log; enum AVCodecID codec_id; @@ -53,7 +59,11 @@ struct spdifContext { struct mp_decoder public; }; +#if LIBAVCODEC_VERSION_MAJOR < 61 static int write_packet(void *p, uint8_t *buf, int buf_size) +#else +static int write_packet(void *p, const uint8_t *buf, int buf_size) +#endif { struct spdifContext *ctx = p; @@ -69,7 +79,7 @@ static int write_packet(void *p, uint8_t *buf, int buf_size) } // (called on both filter destruction _and_ if lavf fails to init) -static void destroy(struct mp_filter *da) +static void ad_spdif_destroy(struct mp_filter *da) { struct spdifContext *spdif_ctx = da->priv; AVFormatContext *lavf_ctx = spdif_ctx->lavf_ctx; @@ -79,7 +89,7 @@ static void destroy(struct mp_filter *da) av_write_trailer(lavf_ctx); if (lavf_ctx->pb) av_freep(&lavf_ctx->pb->buffer); - av_freep(&lavf_ctx->pb); + avio_context_free(&lavf_ctx->pb); avformat_free_context(lavf_ctx); spdif_ctx->lavf_ctx = NULL; } @@ -90,7 +100,7 @@ static void determine_codec_params(struct mp_filter *da, AVPacket *pkt, int *out_profile, int *out_rate) { struct spdifContext *spdif_ctx = da->priv; - int profile = FF_PROFILE_UNKNOWN; + int profile = AV_PROFILE_UNKNOWN; AVCodecContext *ctx = NULL; AVFrame *frame = NULL; @@ -115,7 +125,7 @@ static void determine_codec_params(struct mp_filter *da, AVPacket *pkt, av_parser_close(parser); } - if (profile != FF_PROFILE_UNKNOWN || spdif_ctx->codec_id != AV_CODEC_ID_DTS) + if (profile != AV_PROFILE_UNKNOWN || spdif_ctx->codec_id != AV_CODEC_ID_DTS) return; const AVCodec *codec = avcodec_find_decoder(spdif_ctx->codec_id); @@ -145,7 +155,7 @@ done: av_frame_free(&frame); avcodec_free_context(&ctx); - if (profile == FF_PROFILE_UNKNOWN) + if (profile == AV_PROFILE_UNKNOWN) MP_WARN(da, "Failed to parse codec profile.\n"); } @@ -155,7 +165,7 @@ static int init_filter(struct mp_filter *da) AVPacket *pkt = spdif_ctx->avpkt; - int profile = FF_PROFILE_UNKNOWN; + int profile = AV_PROFILE_UNKNOWN; int c_rate = 0; determine_codec_params(da, pkt, &profile, &c_rate); MP_VERBOSE(da, "In: profile=%d samplerate=%d\n", profile, c_rate); @@ -186,7 +196,8 @@ static int init_filter(struct mp_filter *da) if (!stream) goto fail; - stream->codecpar->codec_id = spdif_ctx->codec_id; + stream->codecpar->codec_type = AVMEDIA_TYPE_AUDIO; + stream->codecpar->codec_id = spdif_ctx->codec_id; AVDictionary *format_opts = NULL; @@ -208,15 +219,15 @@ static int init_filter(struct mp_filter *da) num_channels = 2; break; case AV_CODEC_ID_DTS: { - bool is_hd = profile == FF_PROFILE_DTS_HD_HRA || - profile == FF_PROFILE_DTS_HD_MA || - profile == FF_PROFILE_UNKNOWN; + bool is_hd = profile == AV_PROFILE_DTS_HD_HRA || + profile == AV_PROFILE_DTS_HD_MA || + profile == AV_PROFILE_UNKNOWN; // Apparently, DTS-HD over SPDIF is specified to be 7.1 (8 channels) // for DTS-HD MA, and stereo (2 channels) for DTS-HD HRA. The bit // streaming rate as well as the signaled channel count are defined // based on this value. - int dts_hd_spdif_channel_count = profile == FF_PROFILE_DTS_HD_HRA ? + int dts_hd_spdif_channel_count = profile == AV_PROFILE_DTS_HD_HRA ? 2 : 8; if (spdif_ctx->use_dts_hd && is_hd) { av_dict_set_int(&format_opts, "dtshd_rate", @@ -226,7 +237,7 @@ static int init_filter(struct mp_filter *da) num_channels = dts_hd_spdif_channel_count; } else { sample_format = AF_FORMAT_S_DTS; - samplerate = 48000; + samplerate = c_rate > 44100 ? 48000 : 44100; num_channels = 2; } break; @@ -250,6 +261,8 @@ static int init_filter(struct mp_filter *da) abort(); } + stream->codecpar->sample_rate = samplerate; + struct mp_chmap chmap; mp_chmap_from_channels(&chmap, num_channels); mp_aframe_set_chmap(spdif_ctx->fmt, &chmap); @@ -270,12 +283,12 @@ static int init_filter(struct mp_filter *da) return 0; fail: - destroy(da); + ad_spdif_destroy(da); mp_filter_internal_mark_failed(da); return -1; } -static void process(struct mp_filter *da) +static void ad_spdif_process(struct mp_filter *da) { struct spdifContext *spdif_ctx = da->priv; @@ -400,8 +413,8 @@ struct mp_decoder_list *select_spdif_codec(const char *codec, const char *pref) static const struct mp_filter_info ad_spdif_filter = { .name = "ad_spdif", .priv_size = sizeof(struct spdifContext), - .process = process, - .destroy = destroy, + .process = ad_spdif_process, + .destroy = ad_spdif_destroy, }; static struct mp_decoder *create(struct mp_filter *parent, diff --git a/audio/filter/af_drop.c b/audio/filter/af_drop.c index 724c482..499389d 100644 --- a/audio/filter/af_drop.c +++ b/audio/filter/af_drop.c @@ -11,7 +11,7 @@ struct priv { struct mp_aframe *last; // for repeating }; -static void process(struct mp_filter *f) +static void af_drop_process(struct mp_filter *f) { struct priv *p = f->priv; @@ -52,7 +52,7 @@ static void process(struct mp_filter *f) mp_pin_in_write(f->ppins[1], frame); } -static bool command(struct mp_filter *f, struct mp_filter_command *cmd) +static bool af_drop_command(struct mp_filter *f, struct mp_filter_command *cmd) { struct priv *p = f->priv; @@ -65,7 +65,7 @@ static bool command(struct mp_filter *f, struct mp_filter_command *cmd) return false; } -static void reset(struct mp_filter *f) +static void af_drop_reset(struct mp_filter *f) { struct priv *p = f->priv; @@ -73,18 +73,18 @@ static void reset(struct mp_filter *f) p->diff = 0; } -static void destroy(struct mp_filter *f) +static void af_drop_destroy(struct mp_filter *f) { - reset(f); + af_drop_reset(f); } static const struct mp_filter_info af_drop_filter = { .name = "drop", .priv_size = sizeof(struct priv), - .process = process, - .command = command, - .reset = reset, - .destroy = destroy, + .process = af_drop_process, + .command = af_drop_command, + .reset = af_drop_reset, + .destroy = af_drop_destroy, }; static struct mp_filter *af_drop_create(struct mp_filter *parent, void *options) diff --git a/audio/filter/af_format.c b/audio/filter/af_format.c index 2d1c1cc..eddce64 100644 --- a/audio/filter/af_format.c +++ b/audio/filter/af_format.c @@ -38,7 +38,7 @@ struct priv { struct mp_pin *in_pin; }; -static void process(struct mp_filter *f) +static void af_format_process(struct mp_filter *f) { struct priv *p = f->priv; @@ -85,7 +85,7 @@ error: static const struct mp_filter_info af_format_filter = { .name = "format", .priv_size = sizeof(struct priv), - .process = process, + .process = af_format_process, }; static struct mp_filter *af_format_create(struct mp_filter *parent, diff --git a/audio/filter/af_lavcac3enc.c b/audio/filter/af_lavcac3enc.c index b4a1d59..def9700 100644 --- a/audio/filter/af_lavcac3enc.c +++ b/audio/filter/af_lavcac3enc.c @@ -50,7 +50,7 @@ #define AC3_MAX_CHANNELS 6 #define AC3_MAX_CODED_FRAME_SIZE 3840 #define AC3_FRAME_SIZE (6 * 256) -const static uint16_t ac3_bitrate_tab[19] = { +static const uint16_t ac3_bitrate_tab[19] = { 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 448, 512, 576, 640 }; @@ -103,7 +103,15 @@ static bool reinit(struct mp_filter *f) if (!bit_rate && chmap.num < AC3_MAX_CHANNELS + 1) bit_rate = default_bit_rate[chmap.num]; - avcodec_close(s->lavc_actx); + avcodec_free_context(&s->lavc_actx); + s->lavc_actx = avcodec_alloc_context3(s->lavc_acodec); + if (!s->lavc_actx) { + MP_ERR(f, "Audio LAVC, couldn't reallocate context!\n"); + return false; + } + + if (mp_set_avopts(f->log, s->lavc_actx, s->opts->avopts) < 0) + return false; // Put sample parameters s->lavc_actx->sample_fmt = af_to_avformat(format); @@ -131,18 +139,18 @@ static bool reinit(struct mp_filter *f) return true; } -static void reset(struct mp_filter *f) +static void af_lavcac3enc_reset(struct mp_filter *f) { struct priv *s = f->priv; TA_FREEP(&s->in_frame); } -static void destroy(struct mp_filter *f) +static void af_lavcac3enc_destroy(struct mp_filter *f) { struct priv *s = f->priv; - reset(f); + af_lavcac3enc_reset(f); av_packet_free(&s->lavc_pkt); avcodec_free_context(&s->lavc_actx); } @@ -153,7 +161,7 @@ static void swap_16(uint16_t *ptr, size_t size) ptr[n] = av_bswap16(ptr[n]); } -static void process(struct mp_filter *f) +static void af_lavcac3enc_process(struct mp_filter *f) { struct priv *s = f->priv; @@ -187,9 +195,6 @@ static void process(struct mp_filter *f) case MP_FRAME_AUDIO: TA_FREEP(&s->in_frame); s->in_frame = input.data; - frame = mp_frame_to_av(input, NULL); - if (!frame) - goto error; if (mp_aframe_get_channels(s->in_frame) < s->opts->min_channel_num) { // Just pass it through. s->in_frame = NULL; @@ -200,6 +205,9 @@ static void process(struct mp_filter *f) if (!reinit(f)) goto error; } + frame = mp_frame_to_av(input, NULL); + if (!frame) + goto error; break; default: goto error; // unexpected packet type } @@ -273,9 +281,9 @@ error: static const struct mp_filter_info af_lavcac3enc_filter = { .name = "lavcac3enc", .priv_size = sizeof(struct priv), - .process = process, - .reset = reset, - .destroy = destroy, + .process = af_lavcac3enc_process, + .reset = af_lavcac3enc_reset, + .destroy = af_lavcac3enc_destroy, }; static void add_chmaps_to_autoconv(struct mp_filter *f, diff --git a/audio/filter/af_rubberband.c b/audio/filter/af_rubberband.c index 48e5cc1..e71937f 100644 --- a/audio/filter/af_rubberband.c +++ b/audio/filter/af_rubberband.c @@ -105,7 +105,7 @@ static bool init_rubberband(struct mp_filter *f) return true; } -static void process(struct mp_filter *f) +static void af_rubberband_process(struct mp_filter *f) { struct priv *p = f->priv; @@ -233,7 +233,7 @@ error: mp_filter_internal_mark_failed(f); } -static bool command(struct mp_filter *f, struct mp_filter_command *cmd) +static bool af_rubberband_command(struct mp_filter *f, struct mp_filter_command *cmd) { struct priv *p = f->priv; @@ -263,7 +263,7 @@ static bool command(struct mp_filter *f, struct mp_filter_command *cmd) return false; } -static void reset(struct mp_filter *f) +static void af_rubberband_reset(struct mp_filter *f) { struct priv *p = f->priv; @@ -274,7 +274,7 @@ static void reset(struct mp_filter *f) TA_FREEP(&p->pending); } -static void destroy(struct mp_filter *f) +static void af_rubberband_destroy(struct mp_filter *f) { struct priv *p = f->priv; @@ -286,10 +286,10 @@ static void destroy(struct mp_filter *f) static const struct mp_filter_info af_rubberband_filter = { .name = "rubberband", .priv_size = sizeof(struct priv), - .process = process, - .command = command, - .reset = reset, - .destroy = destroy, + .process = af_rubberband_process, + .command = af_rubberband_command, + .reset = af_rubberband_reset, + .destroy = af_rubberband_destroy, }; static struct mp_filter *af_rubberband_create(struct mp_filter *parent, diff --git a/audio/filter/af_scaletempo.c b/audio/filter/af_scaletempo.c index f06478f..e7b101b 100644 --- a/audio/filter/af_scaletempo.c +++ b/audio/filter/af_scaletempo.c @@ -229,7 +229,7 @@ static void output_overlap_s16(struct priv *s, void *buf_out, } } -static void process(struct mp_filter *f) +static void af_scaletempo_process(struct mp_filter *f) { struct priv *s = f->priv; @@ -511,7 +511,7 @@ static bool reinit(struct mp_filter *f) return true; } -static bool command(struct mp_filter *f, struct mp_filter_command *cmd) +static bool af_scaletempo_command(struct mp_filter *f, struct mp_filter_command *cmd) { struct priv *s = f->priv; @@ -530,7 +530,7 @@ static bool command(struct mp_filter *f, struct mp_filter_command *cmd) return false; } -static void reset(struct mp_filter *f) +static void af_scaletempo_reset(struct mp_filter *f) { struct priv *s = f->priv; @@ -543,7 +543,7 @@ static void reset(struct mp_filter *f) TA_FREEP(&s->in); } -static void destroy(struct mp_filter *f) +static void af_scaletempo_destroy(struct mp_filter *f) { struct priv *s = f->priv; free(s->buf_queue); @@ -558,10 +558,10 @@ static void destroy(struct mp_filter *f) static const struct mp_filter_info af_scaletempo_filter = { .name = "scaletempo", .priv_size = sizeof(struct priv), - .process = process, - .command = command, - .reset = reset, - .destroy = destroy, + .process = af_scaletempo_process, + .command = af_scaletempo_command, + .reset = af_scaletempo_reset, + .destroy = af_scaletempo_destroy, }; static struct mp_filter *af_scaletempo_create(struct mp_filter *parent, diff --git a/audio/filter/af_scaletempo2.c b/audio/filter/af_scaletempo2.c index 7ad8e35..e43c29a 100644 --- a/audio/filter/af_scaletempo2.c +++ b/audio/filter/af_scaletempo2.c @@ -19,9 +19,9 @@ struct priv { }; static bool init_scaletempo2(struct mp_filter *f); -static void reset(struct mp_filter *f); +static void af_scaletempo2_reset(struct mp_filter *f); -static void process(struct mp_filter *f) +static void af_scaletempo2_process(struct mp_filter *f) { struct priv *p = f->priv; @@ -156,7 +156,7 @@ static bool init_scaletempo2(struct mp_filter *f) return true; } -static bool command(struct mp_filter *f, struct mp_filter_command *cmd) +static bool af_scaletempo2_command(struct mp_filter *f, struct mp_filter_command *cmd) { struct priv *p = f->priv; @@ -169,7 +169,7 @@ static bool command(struct mp_filter *f, struct mp_filter_command *cmd) return false; } -static void reset(struct mp_filter *f) +static void af_scaletempo2_reset(struct mp_filter *f) { struct priv *p = f->priv; mp_scaletempo2_reset(&p->data); @@ -177,7 +177,7 @@ static void reset(struct mp_filter *f) TA_FREEP(&p->pending); } -static void destroy(struct mp_filter *f) +static void af_scaletempo2_destroy(struct mp_filter *f) { struct priv *p = f->priv; mp_scaletempo2_destroy(&p->data); @@ -187,10 +187,10 @@ static void destroy(struct mp_filter *f) static const struct mp_filter_info af_scaletempo2_filter = { .name = "scaletempo2", .priv_size = sizeof(struct priv), - .process = process, - .command = command, - .reset = reset, - .destroy = destroy, + .process = af_scaletempo2_process, + .command = af_scaletempo2_command, + .reset = af_scaletempo2_reset, + .destroy = af_scaletempo2_destroy, }; static struct mp_filter *af_scaletempo2_create( diff --git a/audio/filter/af_scaletempo2_internals.c b/audio/filter/af_scaletempo2_internals.c index 534f4f6..924c091 100644 --- a/audio/filter/af_scaletempo2_internals.c +++ b/audio/filter/af_scaletempo2_internals.c @@ -93,15 +93,15 @@ static void multi_channel_moving_block_energies( } static float multi_channel_similarity_measure( - const float* dot_prod_a_b, - const float* energy_a, const float* energy_b, + const float* dot_prod, + const float* energy_target, const float* energy_candidate, int channels) { const float epsilon = 1e-12f; float similarity_measure = 0.0f; for (int n = 0; n < channels; ++n) { - similarity_measure += dot_prod_a_b[n] - / sqrtf(energy_a[n] * energy_b[n] + epsilon); + similarity_measure += dot_prod[n] * energy_target[n] + / sqrtf(energy_target[n] * energy_candidate[n] + epsilon); } return similarity_measure; } @@ -765,7 +765,8 @@ double mp_scaletempo2_get_latency(struct mp_scaletempo2 *p, double playback_rate bool mp_scaletempo2_frames_available(struct mp_scaletempo2 *p, double playback_rate) { - return p->input_buffer_final_frames > p->target_block_index + return (p->input_buffer_final_frames > p->target_block_index && + p->input_buffer_final_frames > 0) || can_perform_wsola(p, playback_rate) || p->num_complete_frames > 0; } diff --git a/audio/out/ao.c b/audio/out/ao.c index a5aa3a9..75fcbac 100644 --- a/audio/out/ao.c +++ b/audio/out/ao.c @@ -40,6 +40,7 @@ extern const struct ao_driver audio_out_audiotrack; extern const struct ao_driver audio_out_audiounit; extern const struct ao_driver audio_out_coreaudio; extern const struct ao_driver audio_out_coreaudio_exclusive; +extern const struct ao_driver audio_out_avfoundation; extern const struct ao_driver audio_out_rsound; extern const struct ao_driver audio_out_pipewire; extern const struct ao_driver audio_out_sndio; @@ -65,6 +66,9 @@ static const struct ao_driver * const audio_out_drivers[] = { #if HAVE_COREAUDIO &audio_out_coreaudio, #endif +#if HAVE_AVFOUNDATION + &audio_out_avfoundation, +#endif #if HAVE_PIPEWIRE &audio_out_pipewire, #endif @@ -612,7 +616,7 @@ void ao_set_gain(struct ao *ao, float gain) #define MUL_GAIN_f(d, num_samples, gain) \ for (int n = 0; n < (num_samples); n++) \ - (d)[n] = MPCLAMP(((d)[n]) * (gain), -1.0, 1.0) + (d)[n] = (d)[n] * (gain) static void process_plane(struct ao *ao, void *data, int num_samples) { diff --git a/audio/out/ao_alsa.c b/audio/out/ao_alsa.c index 75eda3b..92ea0db 100644 --- a/audio/out/ao_alsa.c +++ b/audio/out/ao_alsa.c @@ -623,7 +623,8 @@ static void uninit(struct ao *ao) CHECK_ALSA_ERROR("pcm close error"); } -alsa_error: ; +alsa_error: + snd_config_update_free_global(); } #define INIT_DEVICE_ERR_GENERIC -1 diff --git a/audio/out/ao_audiotrack.c b/audio/out/ao_audiotrack.c index 1392699..db1da9c 100644 --- a/audio/out/ao_audiotrack.c +++ b/audio/out/ao_audiotrack.c @@ -57,9 +57,6 @@ struct priv { bool cfg_pcm_float; int cfg_session_id; - bool needs_timestamp_offset; - int64_t timestamp_offset; - bool thread_terminate; bool thread_created; mp_thread thread; @@ -67,19 +64,19 @@ struct priv { mp_cond wakeup; }; -struct JNIByteBuffer { +static struct JNIByteBuffer { jclass clazz; jmethodID clear; - struct MPJniField mapping[]; -} ByteBuffer = {.mapping = { - #define OFFSET(member) offsetof(struct JNIByteBuffer, member) - {"java/nio/ByteBuffer", NULL, NULL, MP_JNI_CLASS, OFFSET(clazz), 1}, - {"java/nio/ByteBuffer", "clear", "()Ljava/nio/Buffer;", MP_JNI_METHOD, OFFSET(clear), 1}, +} ByteBuffer; +#define OFFSET(member) offsetof(struct JNIByteBuffer, member) +static const struct MPJniField ByteBuffer_mapping[] = { + {"java/nio/ByteBuffer", NULL, MP_JNI_CLASS, OFFSET(clazz), 1}, + {"clear", "()Ljava/nio/Buffer;", MP_JNI_METHOD, OFFSET(clear), 1}, {0}, - #undef OFFSET -}}; +}; +#undef OFFSET -struct JNIAudioTrack { +static struct JNIAudioTrack { jclass clazz; jmethodID ctor; jmethodID ctorV21; @@ -110,78 +107,78 @@ struct JNIAudioTrack { jint ERROR_INVALID_OPERATION; jint WRITE_BLOCKING; jint WRITE_NON_BLOCKING; - struct MPJniField mapping[]; -} AudioTrack = {.mapping = { - #define OFFSET(member) offsetof(struct JNIAudioTrack, member) - {"android/media/AudioTrack", NULL, NULL, MP_JNI_CLASS, OFFSET(clazz), 1}, - {"android/media/AudioTrack", "<init>", "(IIIIIII)V", MP_JNI_METHOD, OFFSET(ctor), 1}, - {"android/media/AudioTrack", "<init>", "(Landroid/media/AudioAttributes;Landroid/media/AudioFormat;III)V", MP_JNI_METHOD, OFFSET(ctorV21), 0}, - {"android/media/AudioTrack", "release", "()V", MP_JNI_METHOD, OFFSET(release), 1}, - {"android/media/AudioTrack", "getState", "()I", MP_JNI_METHOD, OFFSET(getState), 1}, - {"android/media/AudioTrack", "getPlayState", "()I", MP_JNI_METHOD, OFFSET(getPlayState), 1}, - {"android/media/AudioTrack", "play", "()V", MP_JNI_METHOD, OFFSET(play), 1}, - {"android/media/AudioTrack", "stop", "()V", MP_JNI_METHOD, OFFSET(stop), 1}, - {"android/media/AudioTrack", "flush", "()V", MP_JNI_METHOD, OFFSET(flush), 1}, - {"android/media/AudioTrack", "pause", "()V", MP_JNI_METHOD, OFFSET(pause), 1}, - {"android/media/AudioTrack", "write", "([BII)I", MP_JNI_METHOD, OFFSET(write), 1}, - {"android/media/AudioTrack", "write", "([FIII)I", MP_JNI_METHOD, OFFSET(writeFloat), 1}, - {"android/media/AudioTrack", "write", "([SIII)I", MP_JNI_METHOD, OFFSET(writeShortV23), 0}, - {"android/media/AudioTrack", "write", "(Ljava/nio/ByteBuffer;II)I", MP_JNI_METHOD, OFFSET(writeBufferV21), 1}, - {"android/media/AudioTrack", "getBufferSizeInFrames", "()I", MP_JNI_METHOD, OFFSET(getBufferSizeInFramesV23), 0}, - {"android/media/AudioTrack", "getTimestamp", "(Landroid/media/AudioTimestamp;)Z", MP_JNI_METHOD, OFFSET(getTimestamp), 1}, - {"android/media/AudioTrack", "getPlaybackHeadPosition", "()I", MP_JNI_METHOD, OFFSET(getPlaybackHeadPosition), 1}, - {"android/media/AudioTrack", "getLatency", "()I", MP_JNI_METHOD, OFFSET(getLatency), 1}, - {"android/media/AudioTrack", "getMinBufferSize", "(III)I", MP_JNI_STATIC_METHOD, OFFSET(getMinBufferSize), 1}, - {"android/media/AudioTrack", "getNativeOutputSampleRate", "(I)I", MP_JNI_STATIC_METHOD, OFFSET(getNativeOutputSampleRate), 1}, - {"android/media/AudioTrack", "WRITE_BLOCKING", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(WRITE_BLOCKING), 0}, - {"android/media/AudioTrack", "WRITE_NON_BLOCKING", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(WRITE_NON_BLOCKING), 0}, - {"android/media/AudioTrack", "STATE_INITIALIZED", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(STATE_INITIALIZED), 1}, - {"android/media/AudioTrack", "PLAYSTATE_STOPPED", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(PLAYSTATE_STOPPED), 1}, - {"android/media/AudioTrack", "PLAYSTATE_PAUSED", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(PLAYSTATE_PAUSED), 1}, - {"android/media/AudioTrack", "PLAYSTATE_PLAYING", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(PLAYSTATE_PLAYING), 1}, - {"android/media/AudioTrack", "MODE_STREAM", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(MODE_STREAM), 1}, - {"android/media/AudioTrack", "ERROR", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ERROR), 1}, - {"android/media/AudioTrack", "ERROR_BAD_VALUE", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ERROR_BAD_VALUE), 1}, - {"android/media/AudioTrack", "ERROR_INVALID_OPERATION", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ERROR_INVALID_OPERATION), 1}, +} AudioTrack; +#define OFFSET(member) offsetof(struct JNIAudioTrack, member) +static const struct MPJniField AudioTrack_mapping[] = { + {"android/media/AudioTrack", NULL, MP_JNI_CLASS, OFFSET(clazz), 1}, + {"<init>", "(IIIIIII)V", MP_JNI_METHOD, OFFSET(ctor), 1}, + {"<init>", "(Landroid/media/AudioAttributes;Landroid/media/AudioFormat;III)V", MP_JNI_METHOD, OFFSET(ctorV21), 0}, + {"release", "()V", MP_JNI_METHOD, OFFSET(release), 1}, + {"getState", "()I", MP_JNI_METHOD, OFFSET(getState), 1}, + {"getPlayState", "()I", MP_JNI_METHOD, OFFSET(getPlayState), 1}, + {"play", "()V", MP_JNI_METHOD, OFFSET(play), 1}, + {"stop", "()V", MP_JNI_METHOD, OFFSET(stop), 1}, + {"flush", "()V", MP_JNI_METHOD, OFFSET(flush), 1}, + {"pause", "()V", MP_JNI_METHOD, OFFSET(pause), 1}, + {"write", "([BII)I", MP_JNI_METHOD, OFFSET(write), 1}, + {"write", "([FIII)I", MP_JNI_METHOD, OFFSET(writeFloat), 1}, + {"write", "([SIII)I", MP_JNI_METHOD, OFFSET(writeShortV23), 0}, + {"write", "(Ljava/nio/ByteBuffer;II)I", MP_JNI_METHOD, OFFSET(writeBufferV21), 1}, + {"getBufferSizeInFrames", "()I", MP_JNI_METHOD, OFFSET(getBufferSizeInFramesV23), 0}, + {"getTimestamp", "(Landroid/media/AudioTimestamp;)Z", MP_JNI_METHOD, OFFSET(getTimestamp), 1}, + {"getPlaybackHeadPosition", "()I", MP_JNI_METHOD, OFFSET(getPlaybackHeadPosition), 1}, + {"getLatency", "()I", MP_JNI_METHOD, OFFSET(getLatency), 1}, + {"getMinBufferSize", "(III)I", MP_JNI_STATIC_METHOD, OFFSET(getMinBufferSize), 1}, + {"getNativeOutputSampleRate", "(I)I", MP_JNI_STATIC_METHOD, OFFSET(getNativeOutputSampleRate), 1}, + {"WRITE_BLOCKING", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(WRITE_BLOCKING), 0}, + {"WRITE_NON_BLOCKING", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(WRITE_NON_BLOCKING), 0}, + {"STATE_INITIALIZED", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(STATE_INITIALIZED), 1}, + {"PLAYSTATE_STOPPED", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(PLAYSTATE_STOPPED), 1}, + {"PLAYSTATE_PAUSED", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(PLAYSTATE_PAUSED), 1}, + {"PLAYSTATE_PLAYING", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(PLAYSTATE_PLAYING), 1}, + {"MODE_STREAM", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(MODE_STREAM), 1}, + {"ERROR", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ERROR), 1}, + {"ERROR_BAD_VALUE", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ERROR_BAD_VALUE), 1}, + {"ERROR_INVALID_OPERATION", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ERROR_INVALID_OPERATION), 1}, {0} - #undef OFFSET -}}; +}; +#undef OFFSET -struct JNIAudioAttributes { +static struct JNIAudioAttributes { jclass clazz; jint CONTENT_TYPE_MOVIE; jint CONTENT_TYPE_MUSIC; jint USAGE_MEDIA; - struct MPJniField mapping[]; -} AudioAttributes = {.mapping = { - #define OFFSET(member) offsetof(struct JNIAudioAttributes, member) - {"android/media/AudioAttributes", NULL, NULL, MP_JNI_CLASS, OFFSET(clazz), 0}, - {"android/media/AudioAttributes", "CONTENT_TYPE_MOVIE", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CONTENT_TYPE_MOVIE), 0}, - {"android/media/AudioAttributes", "CONTENT_TYPE_MUSIC", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CONTENT_TYPE_MUSIC), 0}, - {"android/media/AudioAttributes", "USAGE_MEDIA", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(USAGE_MEDIA), 0}, +} AudioAttributes; +#define OFFSET(member) offsetof(struct JNIAudioAttributes, member) +static const struct MPJniField AudioAttributes_mapping[] = { + {"android/media/AudioAttributes", NULL, MP_JNI_CLASS, OFFSET(clazz), 0}, + {"CONTENT_TYPE_MOVIE", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CONTENT_TYPE_MOVIE), 0}, + {"CONTENT_TYPE_MUSIC", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CONTENT_TYPE_MUSIC), 0}, + {"USAGE_MEDIA", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(USAGE_MEDIA), 0}, {0} - #undef OFFSET -}}; +}; +#undef OFFSET -struct JNIAudioAttributesBuilder { +static struct JNIAudioAttributesBuilder { jclass clazz; jmethodID ctor; jmethodID setUsage; jmethodID setContentType; jmethodID build; - struct MPJniField mapping[]; -} AudioAttributesBuilder = {.mapping = { - #define OFFSET(member) offsetof(struct JNIAudioAttributesBuilder, member) - {"android/media/AudioAttributes$Builder", NULL, NULL, MP_JNI_CLASS, OFFSET(clazz), 0}, - {"android/media/AudioAttributes$Builder", "<init>", "()V", MP_JNI_METHOD, OFFSET(ctor), 0}, - {"android/media/AudioAttributes$Builder", "setUsage", "(I)Landroid/media/AudioAttributes$Builder;", MP_JNI_METHOD, OFFSET(setUsage), 0}, - {"android/media/AudioAttributes$Builder", "setContentType", "(I)Landroid/media/AudioAttributes$Builder;", MP_JNI_METHOD, OFFSET(setContentType), 0}, - {"android/media/AudioAttributes$Builder", "build", "()Landroid/media/AudioAttributes;", MP_JNI_METHOD, OFFSET(build), 0}, +} AudioAttributesBuilder; +#define OFFSET(member) offsetof(struct JNIAudioAttributesBuilder, member) +static const struct MPJniField AudioAttributesBuilder_mapping[] = { + {"android/media/AudioAttributes$Builder", NULL, MP_JNI_CLASS, OFFSET(clazz), 0}, + {"<init>", "()V", MP_JNI_METHOD, OFFSET(ctor), 0}, + {"setUsage", "(I)Landroid/media/AudioAttributes$Builder;", MP_JNI_METHOD, OFFSET(setUsage), 0}, + {"setContentType", "(I)Landroid/media/AudioAttributes$Builder;", MP_JNI_METHOD, OFFSET(setContentType), 0}, + {"build", "()Landroid/media/AudioAttributes;", MP_JNI_METHOD, OFFSET(build), 0}, {0} - #undef OFFSET -}}; +}; +#undef OFFSET -struct JNIAudioFormat { +static struct JNIAudioFormat { jclass clazz; jint ENCODING_PCM_8BIT; jint ENCODING_PCM_16BIT; @@ -194,77 +191,90 @@ struct JNIAudioFormat { jint CHANNEL_OUT_5POINT1; jint CHANNEL_OUT_BACK_CENTER; jint CHANNEL_OUT_7POINT1_SURROUND; - struct MPJniField mapping[]; -} AudioFormat = {.mapping = { - #define OFFSET(member) offsetof(struct JNIAudioFormat, member) - {"android/media/AudioFormat", NULL, NULL, MP_JNI_CLASS, OFFSET(clazz), 1}, - {"android/media/AudioFormat", "ENCODING_PCM_8BIT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ENCODING_PCM_8BIT), 1}, - {"android/media/AudioFormat", "ENCODING_PCM_16BIT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ENCODING_PCM_16BIT), 1}, - {"android/media/AudioFormat", "ENCODING_PCM_FLOAT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ENCODING_PCM_FLOAT), 1}, - {"android/media/AudioFormat", "ENCODING_IEC61937", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ENCODING_IEC61937), 0}, - {"android/media/AudioFormat", "CHANNEL_OUT_MONO", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_MONO), 1}, - {"android/media/AudioFormat", "CHANNEL_OUT_STEREO", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_STEREO), 1}, - {"android/media/AudioFormat", "CHANNEL_OUT_FRONT_CENTER", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_FRONT_CENTER), 1}, - {"android/media/AudioFormat", "CHANNEL_OUT_QUAD", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_QUAD), 1}, - {"android/media/AudioFormat", "CHANNEL_OUT_5POINT1", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_5POINT1), 1}, - {"android/media/AudioFormat", "CHANNEL_OUT_BACK_CENTER", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_BACK_CENTER), 1}, - {"android/media/AudioFormat", "CHANNEL_OUT_7POINT1_SURROUND", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_7POINT1_SURROUND), 0}, +} AudioFormat; +#define OFFSET(member) offsetof(struct JNIAudioFormat, member) +static const struct MPJniField AudioFormat_mapping[] = { + {"android/media/AudioFormat", NULL, MP_JNI_CLASS, OFFSET(clazz), 1}, + {"ENCODING_PCM_8BIT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ENCODING_PCM_8BIT), 1}, + {"ENCODING_PCM_16BIT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ENCODING_PCM_16BIT), 1}, + {"ENCODING_PCM_FLOAT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ENCODING_PCM_FLOAT), 1}, + {"ENCODING_IEC61937", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ENCODING_IEC61937), 0}, + {"CHANNEL_OUT_MONO", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_MONO), 1}, + {"CHANNEL_OUT_STEREO", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_STEREO), 1}, + {"CHANNEL_OUT_FRONT_CENTER", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_FRONT_CENTER), 1}, + {"CHANNEL_OUT_QUAD", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_QUAD), 1}, + {"CHANNEL_OUT_5POINT1", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_5POINT1), 1}, + {"CHANNEL_OUT_BACK_CENTER", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_BACK_CENTER), 1}, + {"CHANNEL_OUT_7POINT1_SURROUND", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_7POINT1_SURROUND), 0}, {0} - #undef OFFSET -}}; +}; +#undef OFFSET -struct JNIAudioFormatBuilder { +static struct JNIAudioFormatBuilder { jclass clazz; jmethodID ctor; jmethodID setEncoding; jmethodID setSampleRate; jmethodID setChannelMask; jmethodID build; - struct MPJniField mapping[]; -} AudioFormatBuilder = {.mapping = { - #define OFFSET(member) offsetof(struct JNIAudioFormatBuilder, member) - {"android/media/AudioFormat$Builder", NULL, NULL, MP_JNI_CLASS, OFFSET(clazz), 0}, - {"android/media/AudioFormat$Builder", "<init>", "()V", MP_JNI_METHOD, OFFSET(ctor), 0}, - {"android/media/AudioFormat$Builder", "setEncoding", "(I)Landroid/media/AudioFormat$Builder;", MP_JNI_METHOD, OFFSET(setEncoding), 0}, - {"android/media/AudioFormat$Builder", "setSampleRate", "(I)Landroid/media/AudioFormat$Builder;", MP_JNI_METHOD, OFFSET(setSampleRate), 0}, - {"android/media/AudioFormat$Builder", "setChannelMask", "(I)Landroid/media/AudioFormat$Builder;", MP_JNI_METHOD, OFFSET(setChannelMask), 0}, - {"android/media/AudioFormat$Builder", "build", "()Landroid/media/AudioFormat;", MP_JNI_METHOD, OFFSET(build), 0}, +} AudioFormatBuilder; +#define OFFSET(member) offsetof(struct JNIAudioFormatBuilder, member) +static const struct MPJniField AudioFormatBuilder_mapping[] = { + {"android/media/AudioFormat$Builder", NULL, MP_JNI_CLASS, OFFSET(clazz), 0}, + {"<init>", "()V", MP_JNI_METHOD, OFFSET(ctor), 0}, + {"setEncoding", "(I)Landroid/media/AudioFormat$Builder;", MP_JNI_METHOD, OFFSET(setEncoding), 0}, + {"setSampleRate", "(I)Landroid/media/AudioFormat$Builder;", MP_JNI_METHOD, OFFSET(setSampleRate), 0}, + {"setChannelMask", "(I)Landroid/media/AudioFormat$Builder;", MP_JNI_METHOD, OFFSET(setChannelMask), 0}, + {"build", "()Landroid/media/AudioFormat;", MP_JNI_METHOD, OFFSET(build), 0}, {0} - #undef OFFSET -}}; - +}; +#undef OFFSET -struct JNIAudioManager { +static struct JNIAudioManager { jclass clazz; jint ERROR_DEAD_OBJECT; jint STREAM_MUSIC; - struct MPJniField mapping[]; -} AudioManager = {.mapping = { - #define OFFSET(member) offsetof(struct JNIAudioManager, member) - {"android/media/AudioManager", NULL, NULL, MP_JNI_CLASS, OFFSET(clazz), 1}, - {"android/media/AudioManager", "STREAM_MUSIC", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(STREAM_MUSIC), 1}, - {"android/media/AudioManager", "ERROR_DEAD_OBJECT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ERROR_DEAD_OBJECT), 0}, +} AudioManager; +#define OFFSET(member) offsetof(struct JNIAudioManager, member) +static const struct MPJniField AudioManager_mapping[] = { + {"android/media/AudioManager", NULL, MP_JNI_CLASS, OFFSET(clazz), 1}, + {"STREAM_MUSIC", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(STREAM_MUSIC), 1}, + {"ERROR_DEAD_OBJECT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ERROR_DEAD_OBJECT), 0}, {0} - #undef OFFSET -}}; +}; +#undef OFFSET -struct JNIAudioTimestamp { +static struct JNIAudioTimestamp { jclass clazz; jmethodID ctor; jfieldID framePosition; jfieldID nanoTime; - struct MPJniField mapping[]; -} AudioTimestamp = {.mapping = { - #define OFFSET(member) offsetof(struct JNIAudioTimestamp, member) - {"android/media/AudioTimestamp", NULL, NULL, MP_JNI_CLASS, OFFSET(clazz), 1}, - {"android/media/AudioTimestamp", "<init>", "()V", MP_JNI_METHOD, OFFSET(ctor), 1}, - {"android/media/AudioTimestamp", "framePosition", "J", MP_JNI_FIELD, OFFSET(framePosition), 1}, - {"android/media/AudioTimestamp", "nanoTime", "J", MP_JNI_FIELD, OFFSET(nanoTime), 1}, +} AudioTimestamp; +#define OFFSET(member) offsetof(struct JNIAudioTimestamp, member) +static const struct MPJniField AudioTimestamp_mapping[] = { + {"android/media/AudioTimestamp", NULL, MP_JNI_CLASS, OFFSET(clazz), 1}, + {"<init>", "()V", MP_JNI_METHOD, OFFSET(ctor), 1}, + {"framePosition", "J", MP_JNI_FIELD, OFFSET(framePosition), 1}, + {"nanoTime", "J", MP_JNI_FIELD, OFFSET(nanoTime), 1}, {0} - #undef OFFSET -}}; - -#define MP_JNI_DELETELOCAL(o) (*env)->DeleteLocalRef(env, o) +}; +#undef OFFSET + +#define ENTRY(name) { &name, name ## _mapping } +static const struct { + void *fields; + const struct MPJniField *mapping; +} jclass_list[] = { + ENTRY(ByteBuffer), + ENTRY(AudioTrack), + ENTRY(AudioAttributes), + ENTRY(AudioAttributesBuilder), + ENTRY(AudioFormat), + ENTRY(AudioFormatBuilder), + ENTRY(AudioManager), + ENTRY(AudioTimestamp), +}; +#undef ENTRY static int AudioTrack_New(struct ao *ao) { @@ -279,24 +289,24 @@ static int AudioTrack_New(struct ao *ao) jobject format_builder = MP_JNI_NEW(AudioFormatBuilder.clazz, AudioFormatBuilder.ctor); MP_JNI_EXCEPTION_LOG(ao); tmp = MP_JNI_CALL_OBJECT(format_builder, AudioFormatBuilder.setEncoding, p->format); - MP_JNI_DELETELOCAL(tmp); + MP_JNI_LOCAL_FREEP(&tmp); tmp = MP_JNI_CALL_OBJECT(format_builder, AudioFormatBuilder.setSampleRate, p->samplerate); - MP_JNI_DELETELOCAL(tmp); + MP_JNI_LOCAL_FREEP(&tmp); tmp = MP_JNI_CALL_OBJECT(format_builder, AudioFormatBuilder.setChannelMask, p->channel_config); - MP_JNI_DELETELOCAL(tmp); + MP_JNI_LOCAL_FREEP(&tmp); jobject format = MP_JNI_CALL_OBJECT(format_builder, AudioFormatBuilder.build); - MP_JNI_DELETELOCAL(format_builder); + MP_JNI_LOCAL_FREEP(&format_builder); jobject attr_builder = MP_JNI_NEW(AudioAttributesBuilder.clazz, AudioAttributesBuilder.ctor); MP_JNI_EXCEPTION_LOG(ao); tmp = MP_JNI_CALL_OBJECT(attr_builder, AudioAttributesBuilder.setUsage, AudioAttributes.USAGE_MEDIA); - MP_JNI_DELETELOCAL(tmp); + MP_JNI_LOCAL_FREEP(&tmp); jint content_type = (ao->init_flags & AO_INIT_MEDIA_ROLE_MUSIC) ? AudioAttributes.CONTENT_TYPE_MUSIC : AudioAttributes.CONTENT_TYPE_MOVIE; tmp = MP_JNI_CALL_OBJECT(attr_builder, AudioAttributesBuilder.setContentType, content_type); - MP_JNI_DELETELOCAL(tmp); + MP_JNI_LOCAL_FREEP(&tmp); jobject attr = MP_JNI_CALL_OBJECT(attr_builder, AudioAttributesBuilder.build); - MP_JNI_DELETELOCAL(attr_builder); + MP_JNI_LOCAL_FREEP(&attr_builder); audiotrack = MP_JNI_NEW( AudioTrack.clazz, @@ -308,8 +318,8 @@ static int AudioTrack_New(struct ao *ao) p->cfg_session_id ); - MP_JNI_DELETELOCAL(format); - MP_JNI_DELETELOCAL(attr); + MP_JNI_LOCAL_FREEP(&format); + MP_JNI_LOCAL_FREEP(&attr); } else { MP_VERBOSE(ao, "Using legacy initializer\n"); audiotrack = MP_JNI_NEW( @@ -332,7 +342,7 @@ static int AudioTrack_New(struct ao *ao) if (MP_JNI_CALL_INT(audiotrack, AudioTrack.getState) != AudioTrack.STATE_INITIALIZED) { MP_JNI_CALL_VOID(audiotrack, AudioTrack.release); MP_JNI_EXCEPTION_LOG(ao); - (*env)->DeleteLocalRef(env, audiotrack); + MP_JNI_LOCAL_FREEP(&audiotrack); MP_ERR(ao, "AudioTrack.getState failed\n"); return -1; } @@ -346,7 +356,7 @@ static int AudioTrack_New(struct ao *ao) } p->audiotrack = (*env)->NewGlobalRef(env, audiotrack); - (*env)->DeleteLocalRef(env, audiotrack); + MP_JNI_LOCAL_FREEP(&audiotrack); if (!p->audiotrack) return -1; @@ -360,8 +370,7 @@ static int AudioTrack_Recreate(struct ao *ao) MP_JNI_CALL_VOID(p->audiotrack, AudioTrack.release); MP_JNI_EXCEPTION_LOG(ao); - (*env)->DeleteGlobalRef(env, p->audiotrack); - p->audiotrack = NULL; + MP_JNI_GLOBAL_FREEP(&p->audiotrack); return AudioTrack_New(ao); } @@ -407,11 +416,6 @@ static uint32_t AudioTrack_getPlaybackHeadPosition(struct ao *ao) int64_t time = MP_JNI_GET_LONG(p->timestamp, AudioTimestamp.nanoTime); if (time == 0) fpos = pos = 0; - if (p->needs_timestamp_offset) { - if (time != 0 && !p->timestamp_offset) - p->timestamp_offset = now - time; - time += p->timestamp_offset; - } if (fpos != 0 && time != 0 && state == AudioTrack.PLAYSTATE_PLAYING) { double diff = (double)(now - time) / 1e9; pos += diff * ao->samplerate; @@ -497,7 +501,7 @@ static int AudioTrack_write(struct ao *ao, int len) // reset positions for reading jobject bbuf = MP_JNI_CALL_OBJECT(p->bbuf, ByteBuffer.clear); if (MP_JNI_EXCEPTION_LOG(ao) < 0) return -1; - (*env)->DeleteLocalRef(env, bbuf); + MP_JNI_LOCAL_FREEP(&bbuf); ret = MP_JNI_CALL_INT(p->audiotrack, AudioTrack.writeBufferV21, p->bbuf, len, AudioTrack.WRITE_BLOCKING); if (MP_JNI_EXCEPTION_LOG(ao) < 0) return -1; @@ -521,35 +525,29 @@ static int AudioTrack_write(struct ao *ao, int len) static void uninit_jni(struct ao *ao) { JNIEnv *env = MP_JNI_GET_ENV(ao); - mp_jni_reset_jfields(env, &AudioTrack, AudioTrack.mapping, 1, ao->log); - mp_jni_reset_jfields(env, &AudioTimestamp, AudioTimestamp.mapping, 1, ao->log); - mp_jni_reset_jfields(env, &AudioManager, AudioManager.mapping, 1, ao->log); - mp_jni_reset_jfields(env, &AudioFormat, AudioFormat.mapping, 1, ao->log); - mp_jni_reset_jfields(env, &AudioFormatBuilder, AudioFormatBuilder.mapping, 1, ao->log); - mp_jni_reset_jfields(env, &AudioAttributes, AudioAttributes.mapping, 1, ao->log); - mp_jni_reset_jfields(env, &AudioAttributesBuilder, AudioAttributesBuilder.mapping, 1, ao->log); - mp_jni_reset_jfields(env, &ByteBuffer, ByteBuffer.mapping, 1, ao->log); + for (int i = 0; i < MP_ARRAY_SIZE(jclass_list); i++) { + mp_jni_reset_jfields(env, jclass_list[i].fields, + jclass_list[i].mapping, 1, ao->log); + } } static int init_jni(struct ao *ao) { JNIEnv *env = MP_JNI_GET_ENV(ao); - if (mp_jni_init_jfields(env, &AudioTrack, AudioTrack.mapping, 1, ao->log) < 0 || - mp_jni_init_jfields(env, &ByteBuffer, ByteBuffer.mapping, 1, ao->log) < 0 || - mp_jni_init_jfields(env, &AudioTimestamp, AudioTimestamp.mapping, 1, ao->log) < 0 || - mp_jni_init_jfields(env, &AudioManager, AudioManager.mapping, 1, ao->log) < 0 || - mp_jni_init_jfields(env, &AudioAttributes, AudioAttributes.mapping, 1, ao->log) < 0 || - mp_jni_init_jfields(env, &AudioAttributesBuilder, AudioAttributesBuilder.mapping, 1, ao->log) < 0 || - mp_jni_init_jfields(env, &AudioFormatBuilder, AudioFormatBuilder.mapping, 1, ao->log) < 0 || - mp_jni_init_jfields(env, &AudioFormat, AudioFormat.mapping, 1, ao->log) < 0) { - uninit_jni(ao); - return -1; + for (int i = 0; i < MP_ARRAY_SIZE(jclass_list); i++) { + if (mp_jni_init_jfields(env, jclass_list[i].fields, + jclass_list[i].mapping, 1, ao->log) < 0) { + goto error; + } } - return 0; + +error: + uninit_jni(ao); + return -1; } -static MP_THREAD_VOID playthread(void *arg) +static MP_THREAD_VOID ao_thread(void *arg) { struct ao *ao = arg; struct priv *p = ao->priv; @@ -608,34 +606,18 @@ static void uninit(struct ao *ao) if (p->audiotrack) { MP_JNI_CALL_VOID(p->audiotrack, AudioTrack.release); MP_JNI_EXCEPTION_LOG(ao); - (*env)->DeleteGlobalRef(env, p->audiotrack); - p->audiotrack = NULL; + MP_JNI_GLOBAL_FREEP(&p->audiotrack); } - if (p->bytearray) { - (*env)->DeleteGlobalRef(env, p->bytearray); - p->bytearray = NULL; - } + MP_JNI_GLOBAL_FREEP(&p->bytearray); - if (p->shortarray) { - (*env)->DeleteGlobalRef(env, p->shortarray); - p->shortarray = NULL; - } + MP_JNI_GLOBAL_FREEP(&p->shortarray); - if (p->floatarray) { - (*env)->DeleteGlobalRef(env, p->floatarray); - p->floatarray = NULL; - } + MP_JNI_GLOBAL_FREEP(&p->floatarray); - if (p->bbuf) { - (*env)->DeleteGlobalRef(env, p->bbuf); - p->bbuf = NULL; - } + MP_JNI_GLOBAL_FREEP(&p->bbuf); - if (p->timestamp) { - (*env)->DeleteGlobalRef(env, p->timestamp); - p->timestamp = NULL; - } + MP_JNI_GLOBAL_FREEP(&p->timestamp); mp_cond_destroy(&p->wakeup); mp_mutex_destroy(&p->lock); @@ -658,6 +640,10 @@ static int init(struct ao *ao) if (af_fmt_is_spdif(ao->format)) { p->format = AudioFormat.ENCODING_IEC61937; + if (!p->format || !AudioTrack.writeShortV23) { + MP_ERR(ao, "spdif passthrough not supported by API\n"); + return -1; + } } else if (ao->format == AF_FORMAT_U8) { p->format = AudioFormat.ENCODING_PCM_8BIT; } else if (p->cfg_pcm_float && af_fmt_is_float(ao->format)) { @@ -752,26 +738,26 @@ static int init(struct ao *ao) return -1; } p->timestamp = (*env)->NewGlobalRef(env, timestamp); - (*env)->DeleteLocalRef(env, timestamp); + MP_JNI_LOCAL_FREEP(×tamp); // decide and create buffer of right type if (p->format == AudioFormat.ENCODING_IEC61937) { jshortArray shortarray = (*env)->NewShortArray(env, p->chunksize / 2); p->shortarray = (*env)->NewGlobalRef(env, shortarray); - (*env)->DeleteLocalRef(env, shortarray); + MP_JNI_LOCAL_FREEP(&shortarray); } else if (AudioTrack.writeBufferV21) { MP_VERBOSE(ao, "Using NIO ByteBuffer\n"); jobject bbuf = (*env)->NewDirectByteBuffer(env, p->chunk, p->chunksize); p->bbuf = (*env)->NewGlobalRef(env, bbuf); - (*env)->DeleteLocalRef(env, bbuf); + MP_JNI_LOCAL_FREEP(&bbuf); } else if (p->format == AudioFormat.ENCODING_PCM_FLOAT) { jfloatArray floatarray = (*env)->NewFloatArray(env, p->chunksize / sizeof(float)); p->floatarray = (*env)->NewGlobalRef(env, floatarray); - (*env)->DeleteLocalRef(env, floatarray); + MP_JNI_LOCAL_FREEP(&floatarray); } else { jbyteArray bytearray = (*env)->NewByteArray(env, p->chunksize); p->bytearray = (*env)->NewGlobalRef(env, bytearray); - (*env)->DeleteLocalRef(env, bytearray); + MP_JNI_LOCAL_FREEP(&bytearray); } /* create AudioTrack object */ @@ -780,7 +766,7 @@ static int init(struct ao *ao) goto error; } - if (mp_thread_create(&p->thread, playthread, ao)) { + if (mp_thread_create(&p->thread, ao_thread, ao)) { MP_ERR(ao, "pthread creation failed\n"); goto error; } @@ -812,7 +798,6 @@ static void stop(struct ao *ao) p->written_frames = 0; p->timestamp_fetched = 0; p->timestamp_set = false; - p->timestamp_offset = 0; } static void start(struct ao *ao) diff --git a/audio/out/ao_avfoundation.m b/audio/out/ao_avfoundation.m new file mode 100644 index 0000000..7654916 --- /dev/null +++ b/audio/out/ao_avfoundation.m @@ -0,0 +1,372 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "ao.h" +#include "audio/format.h" +#include "audio/out/ao_coreaudio_chmap.h" +#include "audio/out/ao_coreaudio_utils.h" +#include "common/common.h" +#include "common/msg.h" +#include "internal.h" +#include "osdep/timer.h" +#include "ta/ta_talloc.h" + +#import <AVFoundation/AVFoundation.h> +#import <Foundation/Foundation.h> +#import <CoreAudioTypes/CoreAudioTypes.h> +#import <CoreFoundation/CoreFoundation.h> +#import <CoreMedia/CoreMedia.h> + + +@interface AVObserver : NSObject { + struct ao *ao; +} +- (void)handleRestartNotification:(NSNotification*)notification; +@end + +struct priv { + AVSampleBufferAudioRenderer *renderer; + AVSampleBufferRenderSynchronizer *synchronizer; + dispatch_queue_t queue; + CMAudioFormatDescriptionRef format_description; + AVObserver *observer; + int64_t end_time_av; +}; + +static int64_t CMTimeGetNanoseconds(CMTime time) +{ + time = CMTimeConvertScale(time, 1000000000, kCMTimeRoundingMethod_Default); + return time.value; +} + +static CMTime CMTimeFromNanoseconds(int64_t time) +{ + return CMTimeMake(time, 1000000000); +} + +static void feed(struct ao *ao) +{ + struct priv *p = ao->priv; + int samplerate = ao->samplerate; + int sstride = ao->sstride; + + CMBlockBufferRef block_buffer = NULL; + CMSampleBufferRef sample_buffer = NULL; + OSStatus err; + + int request_sample_count = samplerate / 10; + int buffer_size = request_sample_count * sstride; + void *data[] = {CFAllocatorAllocate(NULL, buffer_size, 0)}; + + int64_t cur_time_av = CMTimeGetNanoseconds([p->synchronizer currentTime]); + int64_t cur_time_mp = mp_time_ns(); + int64_t end_time_av = MPMAX(p->end_time_av, cur_time_av); + int64_t time_delta = CMTimeGetNanoseconds(CMTimeMake(request_sample_count, samplerate)); + int real_sample_count = ao_read_data_nonblocking(ao, data, request_sample_count, end_time_av - cur_time_av + cur_time_mp + time_delta); + if (real_sample_count == 0) { + // avoid spinning by blocking the thread + mp_sleep_ns(10000000); + goto finish; + } + + if ((err = CMBlockBufferCreateWithMemoryBlock( + NULL, + data[0], + buffer_size, + NULL, + NULL, + 0, + real_sample_count * sstride, + 0, + &block_buffer + )) != noErr) { + MP_FATAL(ao, "failed to create block buffer\n"); + MP_VERBOSE(ao, "CMBlockBufferCreateWithMemoryBlock returned %d\n", err); + goto error; + } + data[0] = NULL; + + CMSampleTimingInfo sample_timing_into[] = {(CMSampleTimingInfo) { + .duration = CMTimeMake(1, samplerate), + .presentationTimeStamp = CMTimeFromNanoseconds(end_time_av), + .decodeTimeStamp = kCMTimeInvalid + }}; + size_t sample_size_array[] = {sstride}; + if ((err = CMSampleBufferCreateReady( + NULL, + block_buffer, + p->format_description, + real_sample_count, + 1, + sample_timing_into, + 1, + sample_size_array, + &sample_buffer + )) != noErr) { + MP_FATAL(ao, "failed to create sample buffer\n"); + MP_VERBOSE(ao, "CMSampleBufferCreateReady returned %d\n", err); + goto error; + } + + [p->renderer enqueueSampleBuffer:sample_buffer]; + + time_delta = CMTimeGetNanoseconds(CMTimeMake(real_sample_count, samplerate)); + p->end_time_av = end_time_av + time_delta; + + goto finish; + +error: + ao_request_reload(ao); +finish: + if (data[0]) CFAllocatorDeallocate(NULL, data[0]); + if (block_buffer) CFRelease(block_buffer); + if (sample_buffer) CFRelease(sample_buffer); +} + +static void start(struct ao *ao) +{ + struct priv *p = ao->priv; + + p->end_time_av = -1; + [p->synchronizer setRate:1]; + [p->renderer requestMediaDataWhenReadyOnQueue:p->queue usingBlock:^{ + feed(ao); + }]; +} + +static void stop(struct ao *ao) +{ + struct priv *p = ao->priv; + + dispatch_sync(p->queue, ^{ + [p->renderer stopRequestingMediaData]; + [p->renderer flush]; + [p->synchronizer setRate:0]; + }); +} + +static bool set_pause(struct ao *ao, bool paused) +{ + struct priv *p = ao->priv; + + if (paused) { + [p->synchronizer setRate:0]; + } else { + [p->synchronizer setRate:1]; + } + + return true; +} + +static int control(struct ao *ao, enum aocontrol cmd, void *arg) +{ + struct priv *p = ao->priv; + + switch (cmd) { + case AOCONTROL_GET_MUTE: + *(bool*)arg = [p->renderer isMuted]; + return CONTROL_OK; + case AOCONTROL_GET_VOLUME: + *(float*)arg = [p->renderer volume] * 100; + return CONTROL_OK; + case AOCONTROL_SET_MUTE: + [p->renderer setMuted:*(bool*)arg]; + return CONTROL_OK; + case AOCONTROL_SET_VOLUME: + [p->renderer setVolume:*(float*)arg / 100]; + return CONTROL_OK; + default: + return CONTROL_UNKNOWN; + } +} + +@implementation AVObserver +- (instancetype)initWithAO:(struct ao*)_ao { + self = [super init]; + if (self) { + ao = _ao; + } + return self; +} +- (void)handleRestartNotification:(NSNotification*)notification { + char *name = cfstr_get_cstr((CFStringRef)notification.name); + MP_WARN(ao, "restarting due to system notification; this will cause desync\n"); + MP_VERBOSE(ao, "notification name: %s\n", name); + talloc_free(name); + stop(ao); + start(ao); +} +@end + +static int init(struct ao *ao) +{ + struct priv *p = ao->priv; + AudioChannelLayout *layout = NULL; + +#if TARGET_OS_IPHONE + AVAudioSession *instance = AVAudioSession.sharedInstance; + NSInteger maxChannels = instance.maximumOutputNumberOfChannels; + NSInteger prefChannels = MIN(maxChannels, ao->channels.num); + [instance setCategory:AVAudioSessionCategoryPlayback error:nil]; + [instance setMode:AVAudioSessionModeMoviePlayback error:nil]; + [instance setActive:YES error:nil]; + [instance setPreferredOutputNumberOfChannels:prefChannels error:nil]; +#endif + + if ((p->renderer = [[AVSampleBufferAudioRenderer alloc] init]) == nil) { + MP_FATAL(ao, "failed to create audio renderer\n"); + MP_VERBOSE(ao, "AVSampleBufferAudioRenderer failed to initialize\n"); + goto error; + } + if ((p->synchronizer = [[AVSampleBufferRenderSynchronizer alloc] init]) == nil) { + MP_FATAL(ao, "failed to create rendering synchronizer\n"); + MP_VERBOSE(ao, "AVSampleBufferRenderSynchronizer failed to initialize\n"); + goto error; + } + if ((p->queue = dispatch_queue_create( + "avfoundation event", + dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INTERACTIVE, 0) + )) == NULL) { + MP_FATAL(ao, "failed to create dispatch queue\n"); + MP_VERBOSE(ao, "dispatch_queue_create failed\n"); + goto error; + } + + if (ao->device && ao->device[0]) { + [p->renderer setAudioOutputDeviceUniqueID:(NSString*)cfstr_from_cstr(ao->device)]; + } + + [p->synchronizer addRenderer:p->renderer]; + if (@available(tvOS 14.5, iOS 14.5, macOS 11.3, *)) { + [p->synchronizer setDelaysRateChangeUntilHasSufficientMediaData:NO]; + } + + if (af_fmt_is_spdif(ao->format)) { + MP_FATAL(ao, "avfoundation does not support SPDIF\n"); +#if HAVE_COREAUDIO + MP_FATAL(ao, "please use coreaudio_exclusive instead\n"); +#endif + goto error; + } + + // AVSampleBufferAudioRenderer only supports interleaved formats + ao->format = af_fmt_from_planar(ao->format); + if (af_fmt_is_planar(ao->format)) { + MP_FATAL(ao, "planar audio formats are unsupported\n"); + goto error; + } + + AudioStreamBasicDescription asbd; + ca_fill_asbd(ao, &asbd); + size_t layout_size = sizeof(AudioChannelLayout) + + (ao->channels.num - 1) * sizeof(AudioChannelDescription); + layout = talloc_size(ao, layout_size); + layout->mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions; + layout->mNumberChannelDescriptions = ao->channels.num; + for (int i = 0; i < ao->channels.num; ++i) { + AudioChannelDescription *desc = layout->mChannelDescriptions + i; + desc->mChannelFlags = kAudioChannelFlags_AllOff; + desc->mChannelLabel = mp_speaker_id_to_ca_label(ao->channels.speaker[i]); + } + + void *talloc_ctx = talloc_new(NULL); + AudioChannelLayout *std_layout = ca_find_standard_layout(talloc_ctx, layout); + memmove(layout, std_layout, sizeof(AudioChannelLayout)); + talloc_free(talloc_ctx); + ca_log_layout(ao, MSGL_V, layout); + + OSStatus err; + if ((err = CMAudioFormatDescriptionCreate( + NULL, + &asbd, + layout_size, + layout, + 0, + NULL, + NULL, + &p->format_description + )) != noErr) { + MP_FATAL(ao, "failed to create audio format description\n"); + MP_VERBOSE(ao, "CMAudioFormatDescriptionCreate returned %d\n", err); + goto error; + } + talloc_free(layout); + layout = NULL; + + // AVSampleBufferAudioRenderer read ahead aggressively + ao->device_buffer = ao->samplerate * 2; + + p->observer = [[AVObserver alloc] initWithAO:ao]; + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + [center addObserver:p->observer selector:@selector(handleRestartNotification:) name:AVSampleBufferAudioRendererOutputConfigurationDidChangeNotification object:p->renderer]; + [center addObserver:p->observer selector:@selector(handleRestartNotification:) name:AVSampleBufferAudioRendererWasFlushedAutomaticallyNotification object:p->renderer]; + + return CONTROL_OK; + +error: + talloc_free(layout); + if (p->renderer) [p->renderer release]; + if (p->synchronizer) [p->synchronizer release]; + if (p->queue) dispatch_release(p->queue); + if (p->format_description) CFRelease(p->format_description); + +#if TARGET_OS_IPHONE + [AVAudioSession.sharedInstance setActive:NO + withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation + error:nil + ]; +#endif + + return CONTROL_ERROR; +} + +static void uninit(struct ao *ao) +{ + struct priv *p = ao->priv; + + stop(ao); + + [p->renderer release]; + [p->synchronizer release]; + dispatch_release(p->queue); + CFRelease(p->format_description); + + [[NSNotificationCenter defaultCenter] removeObserver:p->observer]; + [p->observer release]; + +#if TARGET_OS_IPHONE + [AVAudioSession.sharedInstance setActive:NO + withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation + error:nil + ]; +#endif +} + +#define OPT_BASE_STRUCT struct priv + +const struct ao_driver audio_out_avfoundation = { + .description = "AVFoundation AVSampleBufferAudioRenderer", + .name = "avfoundation", + .uninit = uninit, + .init = init, + .control = control, + .reset = stop, + .start = start, + .set_pause = set_pause, + .list_devs = ca_get_device_list, + .priv_size = sizeof(struct priv), +}; diff --git a/audio/out/ao_coreaudio.c b/audio/out/ao_coreaudio.c index 37f1313..ae743c9 100644 --- a/audio/out/ao_coreaudio.c +++ b/audio/out/ao_coreaudio.c @@ -27,6 +27,11 @@ #include "ao_coreaudio_properties.h" #include "ao_coreaudio_utils.h" +// The timeout for stopping the audio unit after being reset. This allows the +// device to sleep after playback paused. The duration is chosen to match the +// behavior of AVFoundation. +#define IDLE_TIME 7 * NSEC_PER_SEC + struct priv { AudioDeviceID device; AudioUnit audio_unit; @@ -37,6 +42,12 @@ struct priv { AudioStreamID original_asbd_stream; bool change_physical_format; + + // Block that is executed after `IDLE_TIME` to stop audio output unit. + dispatch_block_t idle_work; + dispatch_queue_t queue; + + int hotplug_cb_registration_times; }; static int64_t ca_get_hardware_latency(struct ao *ao) { @@ -78,7 +89,7 @@ static OSStatus render_cb_lpcm(void *ctx, AudioUnitRenderActionFlags *aflags, int64_t end = mp_time_ns(); end += p->hw_latency_ns + ca_get_latency(ts) + ca_frames_to_ns(ao, frames); - int samples = ao_read_data_nonblocking(ao, planes, frames, end); + int samples = ao_read_data(ao, planes, frames, end); if (samples == 0) *aflags |= kAudioUnitRenderAction_OutputIsSilence; @@ -128,6 +139,9 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg) static bool init_audiounit(struct ao *ao, AudioStreamBasicDescription asbd); static void init_physical_format(struct ao *ao); +static void reinit_latency(struct ao *ao); +static bool register_hotplug_cb(struct ao *ao); +static void unregister_hotplug_cb(struct ao *ao); static bool reinit_device(struct ao *ao) { struct priv *p = ao->priv; @@ -154,6 +168,9 @@ static int init(struct ao *ao) if (!reinit_device(ao)) goto coreaudio_error; + if (!register_hotplug_cb(ao)) + goto coreaudio_error; + if (p->change_physical_format) init_physical_format(ao); @@ -166,6 +183,11 @@ static int init(struct ao *ao) if (!init_audiounit(ao, asbd)) goto coreaudio_error; + reinit_latency(ao); + + p->queue = dispatch_queue_create("io.mpv.coreaudio_stop_during_idle", + DISPATCH_QUEUE_SERIAL); + return CONTROL_OK; coreaudio_error: @@ -295,8 +317,6 @@ static bool init_audiounit(struct ao *ao, AudioStreamBasicDescription asbd) CHECK_CA_ERROR_L(coreaudio_error_audiounit, "can't link audio unit to selected device"); - p->hw_latency_ns = ca_get_hardware_latency(ao); - AURenderCallbackStruct render_cb = (AURenderCallbackStruct) { .inputProc = render_cb_lpcm, .inputProcRefCon = ao, @@ -320,24 +340,96 @@ coreaudio_error: return false; } -static void reset(struct ao *ao) +static void reinit_latency(struct ao *ao) +{ + struct priv *p = ao->priv; + + p->hw_latency_ns = ca_get_hardware_latency(ao); +} + +static void stop(struct ao *ao) +{ + struct priv *p = ao->priv; + OSStatus err = AudioOutputUnitStop(p->audio_unit); + CHECK_CA_WARN("can't stop audio unit"); +} + +static void cancel_and_release_idle_work(struct priv *p) +{ + if (!p->idle_work) + return; + + dispatch_block_cancel(p->idle_work); + Block_release(p->idle_work); + p->idle_work = NULL; +} + +static void stop_after_idle_time(struct ao *ao) { struct priv *p = ao->priv; + + cancel_and_release_idle_work(p); + + p->idle_work = dispatch_block_create(0, ^{ + MP_VERBOSE(ao, "Stopping audio unit due to idle timeout\n"); + stop(ao); + }); + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, IDLE_TIME), + p->queue, p->idle_work); +} + +static void _reset(void *_ao) +{ + struct ao *ao = (struct ao *)_ao; + struct priv *p = ao->priv; OSStatus err = AudioUnitReset(p->audio_unit, kAudioUnitScope_Global, 0); CHECK_CA_WARN("can't reset audio unit"); + + // Until the audio unit is stopped the macOS daemon coreaudiod continues to + // consume CPU and prevent macOS from sleeping. Immediately stopping the + // audio unit would be disruptive for short pause/resume cycles as + // restarting the audio unit takes a noticeable amount of time when a + // wireless audio device is being used. Instead the audio unit is stopped + // after a delay if it remains idle. + stop_after_idle_time(ao); } -static void start(struct ao *ao) +static void reset(struct ao *ao) { struct priv *p = ao->priv; + // Must dispatch to serialize reset, start and stop operations. + dispatch_sync_f(p->queue, ao, &_reset); +} + +static void _start(void *_ao) +{ + struct ao *ao = (struct ao *)_ao; + struct priv *p = ao->priv; + + if (p->idle_work) + dispatch_block_cancel(p->idle_work); + OSStatus err = AudioOutputUnitStart(p->audio_unit); CHECK_CA_WARN("can't start audio unit"); } +static void start(struct ao *ao) +{ + struct priv *p = ao->priv; + // Must dispatch to serialize reset, start and stop operations. + dispatch_sync_f(p->queue, ao, &_start); +} static void uninit(struct ao *ao) { struct priv *p = ao->priv; + + dispatch_sync(p->queue, ^{ + cancel_and_release_idle_work(p); + }); + dispatch_release(p->queue); + AudioOutputUnitStop(p->audio_unit); AudioUnitUninitialize(p->audio_unit); AudioComponentInstanceDispose(p->audio_unit); @@ -348,6 +440,8 @@ static void uninit(struct ao *ao) &p->original_asbd); CHECK_CA_WARN("could not restore physical stream format"); } + + unregister_hotplug_cb(ao); } static OSStatus hotplug_cb(AudioObjectID id, UInt32 naddr, @@ -355,8 +449,11 @@ static OSStatus hotplug_cb(AudioObjectID id, UInt32 naddr, void *ctx) { struct ao *ao = ctx; + struct priv *p = ao->priv; MP_VERBOSE(ao, "Handling potential hotplug event...\n"); reinit_device(ao); + if (p->audio_unit) + reinit_latency(ao); ao_hotplug_event(ao); return noErr; } @@ -369,7 +466,25 @@ static uint32_t hotplug_properties[] = { static int hotplug_init(struct ao *ao) { if (!reinit_device(ao)) - goto coreaudio_error; + return -1; + + if (!register_hotplug_cb(ao)) + return -1; + + return 0; +} + +static void hotplug_uninit(struct ao *ao) +{ + unregister_hotplug_cb(ao); +} + +static bool register_hotplug_cb(struct ao *ao) +{ + struct priv *p = ao->priv; + + if (p->hotplug_cb_registration_times++) + return true; OSStatus err = noErr; for (int i = 0; i < MP_ARRAY_SIZE(hotplug_properties); i++) { @@ -388,14 +503,19 @@ static int hotplug_init(struct ao *ao) } } - return 0; + return true; coreaudio_error: - return -1; + return false; } -static void hotplug_uninit(struct ao *ao) +static void unregister_hotplug_cb(struct ao *ao) { + struct priv *p = ao->priv; + + if (--p->hotplug_cb_registration_times) + return; + OSStatus err = noErr; for (int i = 0; i < MP_ARRAY_SIZE(hotplug_properties); i++) { AudioObjectPropertyAddress addr = { diff --git a/audio/out/ao_coreaudio_chmap.c b/audio/out/ao_coreaudio_chmap.c index 3fd9550..5a129c4 100644 --- a/audio/out/ao_coreaudio_chmap.c +++ b/audio/out/ao_coreaudio_chmap.c @@ -22,6 +22,7 @@ #include "ao_coreaudio_utils.h" #include "ao_coreaudio_chmap.h" +#include <CoreAudioTypes/CoreAudioTypes.h> static const int speaker_map[][2] = { { kAudioChannelLabel_Left, MP_SPEAKER_ID_FL }, @@ -65,6 +66,119 @@ static const int speaker_map[][2] = { { 0, -1 }, }; +static const AudioChannelLayoutTag std_layouts[] = { + (100U<<16) | 1, // kAudioChannelLayoutTag_Mono + (101U<<16) | 2, // kAudioChannelLayoutTag_Stereo + (102U<<16) | 2, // kAudioChannelLayoutTag_StereoHeadphones + (103U<<16) | 2, // kAudioChannelLayoutTag_MatrixStereo + (104U<<16) | 2, // kAudioChannelLayoutTag_MidSide + (105U<<16) | 2, // kAudioChannelLayoutTag_XY + (106U<<16) | 2, // kAudioChannelLayoutTag_Binaural + (107U<<16) | 4, // kAudioChannelLayoutTag_Ambisonic_B_Format + (108U<<16) | 4, // kAudioChannelLayoutTag_Quadraphonic + (109U<<16) | 5, // kAudioChannelLayoutTag_Pentagonal + (110U<<16) | 6, // kAudioChannelLayoutTag_Hexagonal + (111U<<16) | 8, // kAudioChannelLayoutTag_Octagonal + (112U<<16) | 8, // kAudioChannelLayoutTag_Cube + (113U<<16) | 3, // kAudioChannelLayoutTag_MPEG_3_0_A + (114U<<16) | 3, // kAudioChannelLayoutTag_MPEG_3_0_B + (115U<<16) | 4, // kAudioChannelLayoutTag_MPEG_4_0_A + (116U<<16) | 4, // kAudioChannelLayoutTag_MPEG_4_0_B + (117U<<16) | 5, // kAudioChannelLayoutTag_MPEG_5_0_A + (118U<<16) | 5, // kAudioChannelLayoutTag_MPEG_5_0_B + (119U<<16) | 5, // kAudioChannelLayoutTag_MPEG_5_0_C + (120U<<16) | 5, // kAudioChannelLayoutTag_MPEG_5_0_D + (121U<<16) | 6, // kAudioChannelLayoutTag_MPEG_5_1_A + (122U<<16) | 6, // kAudioChannelLayoutTag_MPEG_5_1_B + (123U<<16) | 6, // kAudioChannelLayoutTag_MPEG_5_1_C + (124U<<16) | 6, // kAudioChannelLayoutTag_MPEG_5_1_D + (125U<<16) | 7, // kAudioChannelLayoutTag_MPEG_6_1_A + (126U<<16) | 8, // kAudioChannelLayoutTag_MPEG_7_1_A + (127U<<16) | 8, // kAudioChannelLayoutTag_MPEG_7_1_B + (128U<<16) | 8, // kAudioChannelLayoutTag_MPEG_7_1_C + (129U<<16) | 8, // kAudioChannelLayoutTag_Emagic_Default_7_1 + (130U<<16) | 8, // kAudioChannelLayoutTag_SMPTE_DTV + (131U<<16) | 3, // kAudioChannelLayoutTag_ITU_2_1 + (132U<<16) | 4, // kAudioChannelLayoutTag_ITU_2_2 + (133U<<16) | 3, // kAudioChannelLayoutTag_DVD_4 + (134U<<16) | 4, // kAudioChannelLayoutTag_DVD_5 + (135U<<16) | 5, // kAudioChannelLayoutTag_DVD_6 + (136U<<16) | 4, // kAudioChannelLayoutTag_DVD_10 + (137U<<16) | 5, // kAudioChannelLayoutTag_DVD_11 + (138U<<16) | 5, // kAudioChannelLayoutTag_DVD_18 + (139U<<16) | 6, // kAudioChannelLayoutTag_AudioUnit_6_0 + (140U<<16) | 7, // kAudioChannelLayoutTag_AudioUnit_7_0 + (148U<<16) | 7, // kAudioChannelLayoutTag_AudioUnit_7_0_Front + (141U<<16) | 6, // kAudioChannelLayoutTag_AAC_6_0 + (142U<<16) | 7, // kAudioChannelLayoutTag_AAC_6_1 + (143U<<16) | 7, // kAudioChannelLayoutTag_AAC_7_0 + (183U<<16) | 8, // kAudioChannelLayoutTag_AAC_7_1_B + (184U<<16) | 8, // kAudioChannelLayoutTag_AAC_7_1_C + (144U<<16) | 8, // kAudioChannelLayoutTag_AAC_Octagonal + (145U<<16) | 16, // kAudioChannelLayoutTag_TMH_10_2_std + (146U<<16) | 21, // kAudioChannelLayoutTag_TMH_10_2_full + (149U<<16) | 2, // kAudioChannelLayoutTag_AC3_1_0_1 + (150U<<16) | 3, // kAudioChannelLayoutTag_AC3_3_0 + (151U<<16) | 4, // kAudioChannelLayoutTag_AC3_3_1 + (152U<<16) | 4, // kAudioChannelLayoutTag_AC3_3_0_1 + (153U<<16) | 4, // kAudioChannelLayoutTag_AC3_2_1_1 + (154U<<16) | 5, // kAudioChannelLayoutTag_AC3_3_1_1 + (155U<<16) | 6, // kAudioChannelLayoutTag_EAC_6_0_A + (156U<<16) | 7, // kAudioChannelLayoutTag_EAC_7_0_A + (157U<<16) | 7, // kAudioChannelLayoutTag_EAC3_6_1_A + (158U<<16) | 7, // kAudioChannelLayoutTag_EAC3_6_1_B + (159U<<16) | 7, // kAudioChannelLayoutTag_EAC3_6_1_C + (160U<<16) | 8, // kAudioChannelLayoutTag_EAC3_7_1_A + (161U<<16) | 8, // kAudioChannelLayoutTag_EAC3_7_1_B + (162U<<16) | 8, // kAudioChannelLayoutTag_EAC3_7_1_C + (163U<<16) | 8, // kAudioChannelLayoutTag_EAC3_7_1_D + (164U<<16) | 8, // kAudioChannelLayoutTag_EAC3_7_1_E + (165U<<16) | 8, // kAudioChannelLayoutTag_EAC3_7_1_F + (166U<<16) | 8, // kAudioChannelLayoutTag_EAC3_7_1_G + (167U<<16) | 8, // kAudioChannelLayoutTag_EAC3_7_1_H + (168U<<16) | 4, // kAudioChannelLayoutTag_DTS_3_1 + (169U<<16) | 5, // kAudioChannelLayoutTag_DTS_4_1 + (170U<<16) | 6, // kAudioChannelLayoutTag_DTS_6_0_A + (171U<<16) | 6, // kAudioChannelLayoutTag_DTS_6_0_B + (172U<<16) | 6, // kAudioChannelLayoutTag_DTS_6_0_C + (173U<<16) | 7, // kAudioChannelLayoutTag_DTS_6_1_A + (174U<<16) | 7, // kAudioChannelLayoutTag_DTS_6_1_B + (175U<<16) | 7, // kAudioChannelLayoutTag_DTS_6_1_C + (176U<<16) | 7, // kAudioChannelLayoutTag_DTS_7_0 + (177U<<16) | 8, // kAudioChannelLayoutTag_DTS_7_1 + (178U<<16) | 8, // kAudioChannelLayoutTag_DTS_8_0_A + (179U<<16) | 8, // kAudioChannelLayoutTag_DTS_8_0_B + (180U<<16) | 9, // kAudioChannelLayoutTag_DTS_8_1_A + (181U<<16) | 9, // kAudioChannelLayoutTag_DTS_8_1_B + (182U<<16) | 7, // kAudioChannelLayoutTag_DTS_6_1_D + (185U<<16) | 4, // kAudioChannelLayoutTag_WAVE_4_0_B + (186U<<16) | 5, // kAudioChannelLayoutTag_WAVE_5_0_B + (187U<<16) | 6, // kAudioChannelLayoutTag_WAVE_5_1_B + (188U<<16) | 7, // kAudioChannelLayoutTag_WAVE_6_1 + (189U<<16) | 8, // kAudioChannelLayoutTag_WAVE_7_1 + (194U<<16) | 8, // kAudioChannelLayoutTag_Atmos_5_1_2 + (195U<<16) | 10, // kAudioChannelLayoutTag_Atmos_5_1_4 + (196U<<16) | 10, // kAudioChannelLayoutTag_Atmos_7_1_2 + (192U<<16) | 12, // kAudioChannelLayoutTag_Atmos_7_1_4 + (193U<<16) | 16, // kAudioChannelLayoutTag_Atmos_9_1_6 + (197U<<16) | 4, // kAudioChannelLayoutTag_Logic_4_0_C + (198U<<16) | 6, // kAudioChannelLayoutTag_Logic_6_0_B + (199U<<16) | 7, // kAudioChannelLayoutTag_Logic_6_1_B + (200U<<16) | 7, // kAudioChannelLayoutTag_Logic_6_1_D + (201U<<16) | 8, // kAudioChannelLayoutTag_Logic_7_1_B + (202U<<16) | 12, // kAudioChannelLayoutTag_Logic_Atmos_7_1_4_B + (203U<<16) | 14, // kAudioChannelLayoutTag_Logic_Atmos_7_1_6 + (204U<<16) | 24, // kAudioChannelLayoutTag_CICP_13 + (205U<<16) | 8, // kAudioChannelLayoutTag_CICP_14 + (206U<<16) | 12, // kAudioChannelLayoutTag_CICP_15 + (207U<<16) | 10, // kAudioChannelLayoutTag_CICP_16 + (208U<<16) | 12, // kAudioChannelLayoutTag_CICP_17 + (209U<<16) | 14, // kAudioChannelLayoutTag_CICP_18 + (210U<<16) | 12, // kAudioChannelLayoutTag_CICP_19 + (211U<<16) | 14, // kAudioChannelLayoutTag_CICP_20 + kAudioChannelLayoutTag_Unknown +}; + int ca_label_to_mp_speaker_id(AudioChannelLabel label) { for (int i = 0; speaker_map[i][1] >= 0; i++) @@ -73,30 +187,48 @@ int ca_label_to_mp_speaker_id(AudioChannelLabel label) return -1; } +AudioChannelLabel mp_speaker_id_to_ca_label(int speaker_id) +{ + for (int i = 0; speaker_map[i][1] >= 0; i++) + if (speaker_map[i][1] == speaker_id) + return speaker_map[i][0]; + return -1; // kAudioChannelLabel_Unknown +} + #if HAVE_COREAUDIO -static void ca_log_layout(struct ao *ao, int l, AudioChannelLayout *layout) +void ca_log_layout(struct ao *ao, int l, AudioChannelLayout *layout) { if (!mp_msg_test(ao->log, l)) return; - AudioChannelDescription *descs = layout->mChannelDescriptions; - - mp_msg(ao->log, l, "layout: tag: <%u>, bitmap: <%u>, " - "descriptions <%u>\n", - (unsigned) layout->mChannelLayoutTag, - (unsigned) layout->mChannelBitmap, - (unsigned) layout->mNumberChannelDescriptions); - - for (int i = 0; i < layout->mNumberChannelDescriptions; i++) { - AudioChannelDescription d = descs[i]; - mp_msg(ao->log, l, " - description %d: label <%u, %u>, " - " flags: <%u>, coords: <%f, %f, %f>\n", i, - (unsigned) d.mChannelLabel, - (unsigned) ca_label_to_mp_speaker_id(d.mChannelLabel), - (unsigned) d.mChannelFlags, - d.mCoordinates[0], - d.mCoordinates[1], - d.mCoordinates[2]); + AudioChannelLayoutTag tag = layout->mChannelLayoutTag; + mp_msg(ao->log, l, "audio channel layout: tag: <%u>", tag); + + if (tag == kAudioChannelLayoutTag_UseChannelDescriptions) { + AudioChannelDescription *descs = layout->mChannelDescriptions; + mp_msg(ao->log, l, ", descriptions <%u>\n", + (unsigned) layout->mNumberChannelDescriptions); + + for (int i = 0; i < layout->mNumberChannelDescriptions; i++) { + AudioChannelDescription d = descs[i]; + mp_msg(ao->log, l, " - description %d: label <%u, %u>, flags: <%u>", + i, + (unsigned) d.mChannelLabel, + (unsigned) ca_label_to_mp_speaker_id(d.mChannelLabel), + (unsigned) d.mChannelFlags); + if (d.mChannelFlags != kAudioChannelFlags_AllOff) { + mp_msg(ao->log, l, ", coords: <%f, %f, %f>\n", + d.mCoordinates[0], + d.mCoordinates[1], + d.mCoordinates[2]); + } else { + mp_msg(ao->log, l, "\n"); + } + } + } else if (tag == kAudioChannelLayoutTag_UseChannelBitmap) { + mp_msg(ao->log, l, ", bitmap <%u>\n", layout->mChannelBitmap); + } else { + mp_msg(ao->log, l, "\n"); } } @@ -117,7 +249,7 @@ static AudioChannelLayout *ca_layout_to_custom_layout(struct ao *ao, kAudioFormatProperty_ChannelLayoutForBitmap, sizeof(uint32_t), &l->mChannelBitmap, &psize); CHECK_CA_ERROR("failed to convert channel bitmap to descriptions (info)"); - r = talloc_size(NULL, psize); + r = talloc_size(talloc_ctx, psize); err = AudioFormatGetProperty( kAudioFormatProperty_ChannelLayoutForBitmap, sizeof(uint32_t), &l->mChannelBitmap, &psize, r); @@ -127,7 +259,7 @@ static AudioChannelLayout *ca_layout_to_custom_layout(struct ao *ao, err = AudioFormatGetPropertyInfo( kAudioFormatProperty_ChannelLayoutForTag, sizeof(AudioChannelLayoutTag), &l->mChannelLayoutTag, &psize); - r = talloc_size(NULL, psize); + r = talloc_size(talloc_ctx, psize); CHECK_CA_ERROR("failed to convert channel tag to descriptions (info)"); err = AudioFormatGetProperty( kAudioFormatProperty_ChannelLayoutForTag, @@ -135,14 +267,53 @@ static AudioChannelLayout *ca_layout_to_custom_layout(struct ao *ao, CHECK_CA_ERROR("failed to convert channel tag to descriptions (get)"); } - MP_VERBOSE(ao, "converted input channel layout:\n"); - ca_log_layout(ao, MSGL_V, l); + if (ao) { + MP_VERBOSE(ao, "converted input channel layout:\n"); + ca_log_layout(ao, MSGL_V, l); + } return r; coreaudio_error: return NULL; } +AudioChannelLayout *ca_find_standard_layout(void *talloc_ctx, AudioChannelLayout *l) +{ + if (l->mChannelLayoutTag != kAudioChannelLayoutTag_UseChannelDescriptions) + return l; + + AudioChannelLayout *s = talloc_size(talloc_ctx, sizeof(AudioChannelLayout)); + + for (int i = 0; ; ++i) { + if ((s->mChannelLayoutTag = std_layouts[i]) == kAudioChannelLayoutTag_Unknown) { + s = NULL; + break; + } + + AudioChannelLayout *r = ca_layout_to_custom_layout(NULL, talloc_ctx, s); + + if (!r) + goto mismatch; + if (l->mNumberChannelDescriptions != r->mNumberChannelDescriptions) + goto mismatch; + + for (int i = 0; i < l->mNumberChannelDescriptions; ++i) { + AudioChannelDescription *ld = l->mChannelDescriptions + i; + AudioChannelDescription *rd = r->mChannelDescriptions + i; + if (ld->mChannelLabel == rd->mChannelLabel) + continue; + // XXX: we cannot handle channels with coordinates + goto mismatch; + } + + break; + +mismatch:; + } + + return s ? s : l; +} + #define CHMAP(n, ...) &(struct mp_chmap) MP_CONCAT(MP_CHMAP, n) (__VA_ARGS__) @@ -241,8 +412,8 @@ static AudioChannelLayout* ca_query_stereo_layout(struct ao *ao, void *talloc_ctx) { OSStatus err; - const int nch = 2; - uint32_t channels[nch]; + uint32_t channels[2]; + const int nch = MP_ARRAY_SIZE(channels); AudioChannelLayout *r = NULL; AudioObjectPropertyAddress p_addr = (AudioObjectPropertyAddress) { diff --git a/audio/out/ao_coreaudio_chmap.h b/audio/out/ao_coreaudio_chmap.h index b6d160c..0b21e83 100644 --- a/audio/out/ao_coreaudio_chmap.h +++ b/audio/out/ao_coreaudio_chmap.h @@ -18,15 +18,22 @@ #ifndef MPV_COREAUDIO_CHMAP_H #define MPV_COREAUDIO_CHMAP_H +#include "config.h" #include <AudioToolbox/AudioToolbox.h> -#include "config.h" +#if HAVE_AVFOUNDATION || HAVE_AUDIOUNIT +#undef HAVE_COREAUDIO +#define HAVE_COREAUDIO 1 +#endif struct mp_chmap; int ca_label_to_mp_speaker_id(AudioChannelLabel label); +AudioChannelLabel mp_speaker_id_to_ca_label(int speaker_id); #if HAVE_COREAUDIO +AudioChannelLayout *ca_find_standard_layout(void *talloc_ctx, AudioChannelLayout *l); +void ca_log_layout(struct ao *ao, int l, AudioChannelLayout *layout); bool ca_init_chmap(struct ao *ao, AudioDeviceID device); void ca_get_active_chmap(struct ao *ao, AudioDeviceID device, int channel_count, struct mp_chmap *out_map); diff --git a/audio/out/ao_coreaudio_exclusive.c b/audio/out/ao_coreaudio_exclusive.c index e24f791..5e0ec3b 100644 --- a/audio/out/ao_coreaudio_exclusive.c +++ b/audio/out/ao_coreaudio_exclusive.c @@ -1,5 +1,5 @@ /* - * CoreAudio audio output driver for Mac OS X + * CoreAudio audio output driver for macOS * * original copyright (C) Timothy J. Wood - Aug 2000 * ported to MPlayer libao2 by Dan Christiansen @@ -28,7 +28,7 @@ */ /* - * The MacOS X CoreAudio framework doesn't mesh as simply as some + * The macOS CoreAudio framework doesn't mesh as simply as some * simpler frameworks do. This is due to the fact that CoreAudio pulls * audio samples rather than having them pushed at it (which is nice * when you are wanting to do good buffering of audio). @@ -114,7 +114,7 @@ static OSStatus enable_property_listener(struct ao *ao, bool enabled) kAudioHardwarePropertyDevices}; AudioDeviceID devs[] = {p->device, kAudioObjectSystemObject}; - assert(MP_ARRAY_SIZE(selectors) == MP_ARRAY_SIZE(devs)); + static_assert(MP_ARRAY_SIZE(selectors) == MP_ARRAY_SIZE(devs), ""); OSStatus status = noErr; for (int n = 0; n < MP_ARRAY_SIZE(devs); n++) { diff --git a/audio/out/ao_coreaudio_properties.h b/audio/out/ao_coreaudio_properties.h index f293968..2c9c565 100644 --- a/audio/out/ao_coreaudio_properties.h +++ b/audio/out/ao_coreaudio_properties.h @@ -23,6 +23,11 @@ #include "internal.h" +#if HAVE_AVFOUNDATION || HAVE_AUDIOUNIT +#undef HAVE_COREAUDIO +#define HAVE_COREAUDIO 1 +#endif + // CoreAudio names are way too verbose #define ca_sel AudioObjectPropertySelector #define ca_scope AudioObjectPropertyScope diff --git a/audio/out/ao_coreaudio_utils.c b/audio/out/ao_coreaudio_utils.c index 14db8e3..e74092a 100644 --- a/audio/out/ao_coreaudio_utils.c +++ b/audio/out/ao_coreaudio_utils.c @@ -138,7 +138,8 @@ bool check_ca_st(struct ao *ao, int level, OSStatus code, const char *message) { if (code == noErr) return true; - mp_msg(ao->log, level, "%s (%s/%d)\n", message, mp_tag_str(code), (int)code); + if (ao) + mp_msg(ao->log, level, "%s (%s/%d)\n", message, mp_tag_str(code), (int)code); return false; } @@ -470,11 +471,9 @@ bool ca_change_physical_format_sync(struct ao *ao, AudioStreamID stream, ca_print_asbd(ao, "setting stream physical format:", &change_format); - sem_t wakeup; - if (mp_sem_init(&wakeup, 0, 0)) { - MP_WARN(ao, "OOM\n"); - return false; - } + mp_sem_t wakeup; + if (mp_sem_init(&wakeup, 0, 0)) + MP_HANDLE_OOM(0); AudioStreamBasicDescription prev_format; err = CA_GET(stream, kAudioStreamPropertyPhysicalFormat, &prev_format); diff --git a/audio/out/ao_coreaudio_utils.h b/audio/out/ao_coreaudio_utils.h index 0e2b8b1..699ffde 100644 --- a/audio/out/ao_coreaudio_utils.h +++ b/audio/out/ao_coreaudio_utils.h @@ -27,7 +27,12 @@ #include "common/msg.h" #include "audio/out/ao.h" #include "internal.h" -#include "osdep/apple_utils.h" +#include "osdep/utils-mac.h" + +#if HAVE_AVFOUNDATION || HAVE_AUDIOUNIT +#undef HAVE_COREAUDIO +#define HAVE_COREAUDIO 1 +#endif bool check_ca_st(struct ao *ao, int level, OSStatus code, const char *message); diff --git a/audio/out/ao_lavc.c b/audio/out/ao_lavc.c index 163fdca..4bae438 100644 --- a/audio/out/ao_lavc.c +++ b/audio/out/ao_lavc.c @@ -26,6 +26,7 @@ #include <limits.h> #include <libavutil/common.h> +#include <libavutil/samplefmt.h> #include "config.h" #include "options/options.h" diff --git a/audio/out/ao_null.c b/audio/out/ao_null.c index fcb61d2..0cda8d9 100644 --- a/audio/out/ao_null.c +++ b/audio/out/ao_null.c @@ -116,10 +116,11 @@ static void uninit(struct ao *ao) { } -// stop playing and empty buffers (for seeking/pause) +// stop playing and empty buffers (for seeking) static void reset(struct ao *ao) { struct priv *priv = ao->priv; + priv->paused = false; priv->buffered = 0; priv->playing = false; } diff --git a/audio/out/ao_oss.c b/audio/out/ao_oss.c index 5c0b8c9..afe5839 100644 --- a/audio/out/ao_oss.c +++ b/audio/out/ao_oss.c @@ -36,6 +36,7 @@ #include <sys/types.h> #include "audio/format.h" +#include "common/common.h" #include "common/msg.h" #include "options/options.h" #include "osdep/endian.h" @@ -89,7 +90,7 @@ static const int format_table[][2] = { #define MP_WARN_IOCTL_ERR(__ao) \ MP_WARN((__ao), "%s: ioctl() fail, err = %i: %s\n", \ - __FUNCTION__, errno, strerror(errno)) + __FUNCTION__, errno, mp_strerror(errno)) static void uninit(struct ao *ao); @@ -329,7 +330,7 @@ static bool audio_write(struct ao *ao, void **data, int samples) if (errno == EINTR) continue; MP_WARN(ao, "audio_write: write() fail, err = %i: %s.\n", - errno, strerror(errno)); + errno, mp_strerror(errno)); return false; } if ((size_t)rc != size) { diff --git a/audio/out/ao_pipewire.c b/audio/out/ao_pipewire.c index 3fbcbf6..94d393a 100644 --- a/audio/out/ao_pipewire.c +++ b/audio/out/ao_pipewire.c @@ -27,6 +27,7 @@ #include <spa/utils/result.h> #include <math.h> +#include "common/common.h" #include "common/msg.h" #include "options/m_config.h" #include "options/m_option.h" @@ -46,6 +47,15 @@ static inline int pw_stream_get_time_n(struct pw_stream *stream, struct pw_time #define spa_hook_remove(hook) if ((hook)->link.prev) spa_hook_remove(hook) #endif +#if !PW_CHECK_VERSION(1, 0, 4) +static uint64_t pw_stream_get_nsec(struct pw_stream *stream) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return SPA_TIMESPEC_TO_NSEC(&ts); +} +#endif + enum init_state { INIT_STATE_NONE, INIT_STATE_SUCCESS, @@ -86,7 +96,7 @@ struct id_list { struct spa_list node; }; -static enum spa_audio_format af_fmt_to_pw(struct ao *ao, enum af_format format) +static enum spa_audio_format af_fmt_to_pw(enum af_format format) { switch (format) { case AF_FORMAT_U8: return SPA_AUDIO_FORMAT_U8; @@ -99,9 +109,21 @@ static enum spa_audio_format af_fmt_to_pw(struct ao *ao, enum af_format format) case AF_FORMAT_S32P: return SPA_AUDIO_FORMAT_S32P; case AF_FORMAT_FLOATP: return SPA_AUDIO_FORMAT_F32P; case AF_FORMAT_DOUBLEP: return SPA_AUDIO_FORMAT_F64P; - default: - MP_WARN(ao, "Unhandled format %d\n", format); - return SPA_AUDIO_FORMAT_UNKNOWN; + default: return SPA_AUDIO_FORMAT_UNKNOWN; + } +} + +static enum spa_audio_iec958_codec af_fmt_to_codec(enum af_format format) +{ + switch (format) { + case AF_FORMAT_S_AAC: return SPA_AUDIO_IEC958_CODEC_MPEG2_AAC; + case AF_FORMAT_S_AC3: return SPA_AUDIO_IEC958_CODEC_AC3; + case AF_FORMAT_S_DTS: return SPA_AUDIO_IEC958_CODEC_DTS; + case AF_FORMAT_S_DTSHD: return SPA_AUDIO_IEC958_CODEC_DTSHD; + case AF_FORMAT_S_EAC3: return SPA_AUDIO_IEC958_CODEC_EAC3; + case AF_FORMAT_S_MP3: return SPA_AUDIO_IEC958_CODEC_MPEG; + case AF_FORMAT_S_TRUEHD: return SPA_AUDIO_IEC958_CODEC_TRUEHD; + default: return SPA_AUDIO_IEC958_CODEC_UNKNOWN; } } @@ -154,14 +176,13 @@ static void on_process(void *userdata) void *data[MP_NUM_CHANNELS]; if ((b = pw_stream_dequeue_buffer(p->stream)) == NULL) { - MP_WARN(ao, "out of buffers: %s\n", strerror(errno)); + MP_WARN(ao, "out of buffers: %s\n", mp_strerror(errno)); return; } struct spa_buffer *buf = b->buffer; - int bytes_per_channel = buf->datas[0].maxsize / ao->channels.num; - int nframes = bytes_per_channel / ao->sstride; + int nframes = buf->datas[0].maxsize / ao->sstride; #if PW_CHECK_VERSION(0, 3, 49) if (b->requested != 0) nframes = MPMIN(b->requested, nframes); @@ -177,9 +198,13 @@ static void on_process(void *userdata) time.rate.num = 1; int64_t end_time = mp_time_ns(); - /* time.queued is always going to be 0, so we don't need to care */ - end_time += (nframes * 1e9 / ao->samplerate) + - ((double) time.delay * SPA_NSEC_PER_SEC * time.rate.num / time.rate.denom); + end_time += MP_TIME_S_TO_NS(nframes) / ao->samplerate; + end_time += MP_TIME_S_TO_NS(time.delay) * time.rate.num / time.rate.denom; + end_time += MP_TIME_S_TO_NS(time.queued) / ao->samplerate; +#if PW_CHECK_VERSION(0, 3, 50) + end_time += MP_TIME_S_TO_NS(time.buffered) / ao->samplerate; +#endif + end_time -= pw_stream_get_nsec(p->stream) - time.now; int samples = ao_read_data_nonblocking(ao, data, nframes, end_time); b->size = samples; @@ -214,7 +239,7 @@ static void on_param_changed(void *userdata, uint32_t id, const struct spa_pod * if (param == NULL || id != SPA_PARAM_Format) return; - int buffer_size = ao->device_buffer * af_fmt_to_bytes(ao->format) * ao->channels.num; + int buffer_size = ao->device_buffer * ao->sstride; params[0] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, @@ -506,7 +531,7 @@ static int pipewire_init_boilerplate(struct ao *ao) if (!p->core) { MP_MSG(ao, ao->probing ? MSGL_V : MSGL_ERR, "Could not connect to context '%s': %s\n", - p->options.remote, strerror(errno)); + p->options.remote, mp_strerror(errno)); pw_context_destroy(context); goto error; } @@ -580,24 +605,41 @@ static int init(struct ao *ao) pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%d", ao->samplerate); - enum spa_audio_format spa_format = af_fmt_to_pw(ao, ao->format); - if (spa_format == SPA_AUDIO_FORMAT_UNKNOWN) { - ao->format = AF_FORMAT_FLOATP; - spa_format = SPA_AUDIO_FORMAT_F32P; - } + if (af_fmt_is_spdif(ao->format)) { + enum spa_audio_iec958_codec spa_codec = af_fmt_to_codec(ao->format); + if (spa_codec == SPA_AUDIO_IEC958_CODEC_UNKNOWN) { + MP_ERR(ao, "Unhandled codec %d\n", ao->format); + goto error_props; + } - struct spa_audio_info_raw audio_info = { - .format = spa_format, - .rate = ao->samplerate, - .channels = ao->channels.num, - }; + struct spa_audio_info_iec958 audio_info = { + .codec = spa_codec, + .rate = ao->samplerate, + }; - for (int i = 0; i < ao->channels.num; i++) - audio_info.position[i] = mp_speaker_id_to_spa(ao, ao->channels.speaker[i]); + params[0] = spa_format_audio_iec958_build(&b, SPA_PARAM_EnumFormat, &audio_info); + if (!params[0]) + goto error_props; + } else { + enum spa_audio_format spa_format = af_fmt_to_pw(ao->format); + if (spa_format == SPA_AUDIO_FORMAT_UNKNOWN) { + MP_ERR(ao, "Unhandled format %d\n", ao->format); + goto error_props; + } - params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &audio_info); - if (!params[0]) - goto error_props; + struct spa_audio_info_raw audio_info = { + .format = spa_format, + .rate = ao->samplerate, + .channels = ao->channels.num, + }; + + for (int i = 0; i < ao->channels.num; i++) + audio_info.position[i] = mp_speaker_id_to_spa(ao, ao->channels.speaker[i]); + + params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &audio_info); + if (!params[0]) + goto error_props; + } if (af_fmt_is_planar(ao->format)) { ao->num_planes = ao->channels.num; @@ -664,6 +706,15 @@ static void start(struct ao *ao) pw_thread_loop_unlock(p->loop); } +static bool set_pause(struct ao *ao, bool paused) +{ + struct priv *p = ao->priv; + pw_thread_loop_lock(p->loop); + pw_stream_set_active(p->stream, !paused); + pw_thread_loop_unlock(p->loop); + return true; +} + #define CONTROL_RET(r) (!r ? CONTROL_OK : CONTROL_ERROR) static int control(struct ao *ao, enum aocontrol cmd, void *arg) @@ -855,7 +906,7 @@ const struct ao_driver audio_out_pipewire = { .uninit = uninit, .reset = reset, .start = start, - + .set_pause = set_pause, .control = control, .hotplug_init = hotplug_init, diff --git a/audio/out/ao_pulse.c b/audio/out/ao_pulse.c index 3b29b1a..5c86855 100644 --- a/audio/out/ao_pulse.c +++ b/audio/out/ao_pulse.c @@ -118,7 +118,7 @@ static void stream_request_cb(pa_stream *s, size_t length, void *userdata) { struct ao *ao = userdata; struct priv *priv = ao->priv; - ao_wakeup_playthread(ao); + ao_wakeup(ao); pa_threaded_mainloop_signal(priv->mainloop, 0); } @@ -135,7 +135,7 @@ static void underflow_cb(pa_stream *s, void *userdata) struct priv *priv = ao->priv; priv->playing = false; priv->underrun_signalled = true; - ao_wakeup_playthread(ao); + ao_wakeup(ao); pa_threaded_mainloop_signal(priv->mainloop, 0); } @@ -804,6 +804,7 @@ const struct ao_driver audio_out_pulse = { .priv_size = sizeof(struct priv), .priv_defaults = &(const struct priv) { .cfg_buffer = 100, + .cfg_latency_hacks = true, }, .options = (const struct m_option[]) { {"host", OPT_STRING(cfg_host)}, diff --git a/audio/out/ao_sndio.c b/audio/out/ao_sndio.c index fce7139..309ea4a 100644 --- a/audio/out/ao_sndio.c +++ b/audio/out/ao_sndio.c @@ -22,6 +22,8 @@ #include <errno.h> #include <sndio.h> +#include "config.h" + #include "options/m_option.h" #include "common/msg.h" @@ -292,7 +294,7 @@ static void get_state(struct ao *ao, struct mp_pcm_state *state) state->delay = p->delay / (double)p->par.rate; /* report unexpected EOF / underrun */ - if ((state->queued_samples && state->queued_samples && + if ((state->queued_samples && (state->queued_samples < state->free_samples) && p->playing) || sio_eof(p->hdl)) { @@ -301,7 +303,7 @@ static void get_state(struct ao *ao, struct mp_pcm_state *state) state->free_samples, state->queued_samples, state->delay); p->playing = false; state->playing = p->playing; - ao_wakeup_playthread(ao); + ao_wakeup(ao); } else { state->playing = p->playing; } diff --git a/audio/out/ao_wasapi.c b/audio/out/ao_wasapi.c index b201f26..d986d80 100644 --- a/audio/out/ao_wasapi.c +++ b/audio/out/ao_wasapi.c @@ -150,14 +150,32 @@ exit_label: return false; } +static void thread_pause(struct ao *ao) +{ + struct wasapi_state *state = ao->priv; + MP_DBG(state, "Thread Pause\n"); + HRESULT hr = IAudioClient_Stop(state->pAudioClient); + if (FAILED(hr)) + MP_ERR(state, "IAudioClient_Stop returned: %s\n", mp_HRESULT_to_str(hr)); +} + +static void thread_unpause(struct ao *ao) +{ + struct wasapi_state *state = ao->priv; + MP_DBG(state, "Thread Unpause\n"); + HRESULT hr = IAudioClient_Start(state->pAudioClient); + if (FAILED(hr)) { + MP_ERR(state, "IAudioClient_Start returned %s\n", + mp_HRESULT_to_str(hr)); + } +} + static void thread_reset(struct ao *ao) { struct wasapi_state *state = ao->priv; HRESULT hr; MP_DBG(state, "Thread Reset\n"); - hr = IAudioClient_Stop(state->pAudioClient); - if (FAILED(hr)) - MP_ERR(state, "IAudioClient_Stop returned: %s\n", mp_HRESULT_to_str(hr)); + thread_pause(ao); hr = IAudioClient_Reset(state->pAudioClient); if (FAILED(hr)) @@ -172,27 +190,20 @@ static void thread_resume(struct ao *ao) MP_DBG(state, "Thread Resume\n"); thread_reset(ao); thread_feed(ao); - - HRESULT hr = IAudioClient_Start(state->pAudioClient); - if (FAILED(hr)) { - MP_ERR(state, "IAudioClient_Start returned %s\n", - mp_HRESULT_to_str(hr)); - } + thread_unpause(ao); } -static void thread_wakeup(void *ptr) +static void set_state_and_wakeup_thread(struct ao *ao, + enum wasapi_thread_state thread_state) { - struct ao *ao = ptr; struct wasapi_state *state = ao->priv; + atomic_store(&state->thread_state, thread_state); SetEvent(state->hWake); } -static void set_thread_state(struct ao *ao, - enum wasapi_thread_state thread_state) +static void thread_process_dispatch(void *ptr) { - struct wasapi_state *state = ao->priv; - atomic_store(&state->thread_state, thread_state); - thread_wakeup(ao); + set_state_and_wakeup_thread(ptr, WASAPI_THREAD_DISPATCH); } static DWORD __stdcall AudioThread(void *lpParameter) @@ -212,8 +223,6 @@ static DWORD __stdcall AudioThread(void *lpParameter) if (WaitForSingleObject(state->hWake, INFINITE) != WAIT_OBJECT_0) MP_ERR(ao, "Unexpected return value from WaitForSingleObject\n"); - mp_dispatch_queue_process(state->dispatch, 0); - int thread_state = atomic_load(&state->thread_state); switch (thread_state) { case WASAPI_THREAD_FEED: @@ -221,6 +230,9 @@ static DWORD __stdcall AudioThread(void *lpParameter) if (thread_feed(ao) && thread_feed(ao)) MP_ERR(ao, "Unable to fill buffer fast enough\n"); break; + case WASAPI_THREAD_DISPATCH: + mp_dispatch_queue_process(state->dispatch, 0); + break; case WASAPI_THREAD_RESET: thread_reset(ao); break; @@ -230,6 +242,12 @@ static DWORD __stdcall AudioThread(void *lpParameter) case WASAPI_THREAD_SHUTDOWN: thread_reset(ao); goto exit_label; + case WASAPI_THREAD_PAUSE: + thread_pause(ao); + break; + case WASAPI_THREAD_UNPAUSE: + thread_unpause(ao); + break; default: MP_ERR(ao, "Unhandled thread state: %d\n", thread_state); } @@ -250,7 +268,7 @@ static void uninit(struct ao *ao) MP_DBG(ao, "Uninit wasapi\n"); struct wasapi_state *state = ao->priv; if (state->hWake) - set_thread_state(ao, WASAPI_THREAD_SHUTDOWN); + set_state_and_wakeup_thread(ao, WASAPI_THREAD_SHUTDOWN); if (state->hAudioThread && WaitForSingleObject(state->hAudioThread, INFINITE) != WAIT_OBJECT_0) @@ -301,7 +319,7 @@ static int init(struct ao *ao) } state->dispatch = mp_dispatch_create(state); - mp_dispatch_set_wakeup_fn(state->dispatch, thread_wakeup, ao); + mp_dispatch_set_wakeup_fn(state->dispatch, thread_process_dispatch, ao); state->init_ok = false; state->hAudioThread = CreateThread(NULL, 0, &AudioThread, ao, 0, NULL); @@ -349,7 +367,7 @@ static int thread_control_exclusive(struct ao *ao, enum aocontrol cmd, void *arg case AOCONTROL_GET_VOLUME: IAudioEndpointVolume_GetMasterVolumeLevelScalar( state->pEndpointVolume, &volume); - *(float *)arg = volume; + *(float *)arg = volume * 100.f; return CONTROL_OK; case AOCONTROL_SET_VOLUME: volume = (*(float *)arg) / 100.f; @@ -379,7 +397,7 @@ static int thread_control_shared(struct ao *ao, enum aocontrol cmd, void *arg) switch(cmd) { case AOCONTROL_GET_VOLUME: ISimpleAudioVolume_GetMasterVolume(state->pAudioVolume, &volume); - *(float *)arg = volume; + *(float *)arg = volume * 100.f; return CONTROL_OK; case AOCONTROL_SET_VOLUME: volume = (*(float *)arg) / 100.f; @@ -456,12 +474,18 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg) static void audio_reset(struct ao *ao) { - set_thread_state(ao, WASAPI_THREAD_RESET); + set_state_and_wakeup_thread(ao, WASAPI_THREAD_RESET); } static void audio_resume(struct ao *ao) { - set_thread_state(ao, WASAPI_THREAD_RESUME); + set_state_and_wakeup_thread(ao, WASAPI_THREAD_RESUME); +} + +static bool audio_set_pause(struct ao *ao, bool paused) +{ + set_state_and_wakeup_thread(ao, paused ? WASAPI_THREAD_PAUSE : WASAPI_THREAD_UNPAUSE); + return true; } static void hotplug_uninit(struct ao *ao) @@ -497,6 +521,7 @@ const struct ao_driver audio_out_wasapi = { .control = control, .reset = audio_reset, .start = audio_resume, + .set_pause = audio_set_pause, .list_devs = wasapi_list_devs, .hotplug_init = hotplug_init, .hotplug_uninit = hotplug_uninit, diff --git a/audio/out/ao_wasapi.h b/audio/out/ao_wasapi.h index 17b8f7a..4e5e9c8 100644 --- a/audio/out/ao_wasapi.h +++ b/audio/out/ao_wasapi.h @@ -48,9 +48,12 @@ void wasapi_change_uninit(struct ao* ao); enum wasapi_thread_state { WASAPI_THREAD_FEED = 0, + WASAPI_THREAD_DISPATCH, WASAPI_THREAD_RESUME, WASAPI_THREAD_RESET, - WASAPI_THREAD_SHUTDOWN + WASAPI_THREAD_SHUTDOWN, + WASAPI_THREAD_PAUSE, + WASAPI_THREAD_UNPAUSE, }; typedef struct wasapi_state { diff --git a/audio/out/ao_wasapi_utils.c b/audio/out/ao_wasapi_utils.c index 731fe8a..7e85f75 100644 --- a/audio/out/ao_wasapi_utils.c +++ b/audio/out/ao_wasapi_utils.c @@ -18,12 +18,15 @@ */ #include <math.h> -#include <wchar.h> + #include <windows.h> -#include <errors.h> +#include <mmsystem.h> +#include <mmreg.h> #include <ksguid.h> #include <ksmedia.h> #include <avrt.h> +#include <propsys.h> +#include <functiondiscoverykeys_devpkey.h> #include "audio/format.h" #include "osdep/timer.h" @@ -31,36 +34,47 @@ #include "osdep/strnlen.h" #include "ao_wasapi.h" -DEFINE_PROPERTYKEY(mp_PKEY_Device_FriendlyName, - 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, - 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); -DEFINE_PROPERTYKEY(mp_PKEY_Device_DeviceDesc, - 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, - 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 2); -// CEA 861 subformats -// should work on vista -DEFINE_GUID(mp_KSDATAFORMAT_SUBTYPE_IEC61937_DTS, - 0x00000008, 0x0000, 0x0010, 0x80, 0x00, +#ifndef KSDATAFORMAT_SUBTYPE_IEC61937_DTS +DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEC61937_DTS, + WAVE_FORMAT_DTS, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); -DEFINE_GUID(mp_KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL, - 0x00000092, 0x0000, 0x0010, 0x80, 0x00, +#endif + +#ifndef KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL +DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL, + WAVE_FORMAT_DOLBY_AC3_SPDIF, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); -// might require 7+ -DEFINE_GUID(mp_KSDATAFORMAT_SUBTYPE_IEC61937_AAC, +#endif + +#ifndef KSDATAFORMAT_SUBTYPE_IEC61937_AAC +DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEC61937_AAC, 0x00000006, 0x0cea, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); -DEFINE_GUID(mp_KSDATAFORMAT_SUBTYPE_IEC61937_MPEG3, - 0x00000004, 0x0cea, 0x0010, 0x80, 0x00, +#endif + +#ifndef KSDATAFORMAT_SUBTYPE_IEC61937_MPEG3 +DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEC61937_MPEG3, + 0x00000005, 0x0cea, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); -DEFINE_GUID(mp_KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL_PLUS, +#endif + +#ifndef KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL_PLUS +DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL_PLUS, 0x0000000a, 0x0cea, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); -DEFINE_GUID(mp_KSDATAFORMAT_SUBTYPE_IEC61937_DTS_HD, +#endif + +#ifndef KSDATAFORMAT_SUBTYPE_IEC61937_DTS_HD +DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEC61937_DTS_HD, 0x0000000b, 0x0cea, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); -DEFINE_GUID(mp_KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_MLP, +#endif + +#ifndef KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_MLP +DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_MLP, 0x0000000c, 0x0cea, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); +#endif struct wasapi_sample_fmt { int mp_format; // AF_FORMAT_* @@ -82,13 +96,13 @@ static const struct wasapi_sample_fmt wasapi_formats[] = { // aka S24 (with conversion on output) {AF_FORMAT_S32, 24, 24, &KSDATAFORMAT_SUBTYPE_PCM}, {AF_FORMAT_FLOAT, 32, 32, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT}, - {AF_FORMAT_S_AC3, 16, 16, &mp_KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL}, - {AF_FORMAT_S_DTS, 16, 16, &mp_KSDATAFORMAT_SUBTYPE_IEC61937_DTS}, - {AF_FORMAT_S_AAC, 16, 16, &mp_KSDATAFORMAT_SUBTYPE_IEC61937_AAC}, - {AF_FORMAT_S_MP3, 16, 16, &mp_KSDATAFORMAT_SUBTYPE_IEC61937_MPEG3}, - {AF_FORMAT_S_TRUEHD, 16, 16, &mp_KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_MLP}, - {AF_FORMAT_S_EAC3, 16, 16, &mp_KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL_PLUS}, - {AF_FORMAT_S_DTSHD, 16, 16, &mp_KSDATAFORMAT_SUBTYPE_IEC61937_DTS_HD}, + {AF_FORMAT_S_AC3, 16, 16, &KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL}, + {AF_FORMAT_S_DTS, 16, 16, &KSDATAFORMAT_SUBTYPE_IEC61937_DTS}, + {AF_FORMAT_S_AAC, 16, 16, &KSDATAFORMAT_SUBTYPE_IEC61937_AAC}, + {AF_FORMAT_S_MP3, 16, 16, &KSDATAFORMAT_SUBTYPE_IEC61937_MPEG3}, + {AF_FORMAT_S_TRUEHD, 16, 16, &KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_MLP}, + {AF_FORMAT_S_EAC3, 16, 16, &KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL_PLUS}, + {AF_FORMAT_S_DTSHD, 16, 16, &KSDATAFORMAT_SUBTYPE_IEC61937_DTS_HD}, {0}, }; @@ -562,9 +576,10 @@ static void init_session_display(struct wasapi_state *state, const char *name) { (void **)&state->pSessionControl); EXIT_ON_ERROR(hr); - wchar_t path[MAX_PATH] = {0}; - GetModuleFileNameW(NULL, path, MAX_PATH); + wchar_t *path = talloc_array(NULL, wchar_t, MP_PATH_MAX); + GetModuleFileNameW(NULL, path, MP_PATH_MAX); hr = IAudioSessionControl_SetIconPath(state->pSessionControl, path, NULL); + talloc_free(path); if (FAILED(hr)) { // don't goto exit_label here since SetDisplayName might still work MP_WARN(state, "Error setting audio session icon: %s\n", @@ -718,7 +733,7 @@ static char* get_device_name(struct mp_log *l, void *talloc_ctx, IMMDevice *pDev HRESULT hr = IMMDevice_OpenPropertyStore(pDevice, STGM_READ, &pProps); EXIT_ON_ERROR(hr); - hr = IPropertyStore_GetValue(pProps, &mp_PKEY_Device_FriendlyName, + hr = IPropertyStore_GetValue(pProps, &PKEY_Device_FriendlyName, &devname); EXIT_ON_ERROR(hr); diff --git a/audio/out/buffer.c b/audio/out/buffer.c index 5b8b523..97f7ea1 100644 --- a/audio/out/buffer.c +++ b/audio/out/buffer.c @@ -41,7 +41,7 @@ struct buffer_state { mp_mutex lock; mp_cond wakeup; - // Playthread sleep + // AO thread sleep mp_mutex pt_lock; mp_cond pt_wakeup; @@ -62,6 +62,11 @@ struct buffer_state { bool paused; // logically paused int64_t end_time_ns; // absolute output time of last played sample + int64_t queued_time_ns; // duration of samples that have been queued to + // the device but have not been played. + // This field is only set in ao_set_paused(), + // and is considered as a temporary solution; + // DO NOT USE IT IN OTHER PLACES. bool initial_unblocked; @@ -78,9 +83,9 @@ struct buffer_state { bool terminate; // exit thread }; -static MP_THREAD_VOID playthread(void *arg); +static MP_THREAD_VOID ao_thread(void *arg); -void ao_wakeup_playthread(struct ao *ao) +void ao_wakeup(struct ao *ao) { struct buffer_state *p = ao->buffer_state; mp_mutex_lock(&p->pt_lock); @@ -173,8 +178,8 @@ static int read_buffer(struct ao *ao, void **data, int samples, bool *eof, return pos; } -static int ao_read_data_unlocked(struct ao *ao, void **data, int samples, - int64_t out_time_ns, bool pad_silence) +static int ao_read_data_locked(struct ao *ao, void **data, int samples, + int64_t out_time_ns, bool pad_silence) { struct buffer_state *p = ao->buffer_state; assert(!ao->driver->write); @@ -208,7 +213,7 @@ int ao_read_data(struct ao *ao, void **data, int samples, int64_t out_time_ns) mp_mutex_lock(&p->lock); - int pos = ao_read_data_unlocked(ao, data, samples, out_time_ns, true); + int pos = ao_read_data_locked(ao, data, samples, out_time_ns, true); mp_mutex_unlock(&p->lock); @@ -224,7 +229,7 @@ int ao_read_data_nonblocking(struct ao *ao, void **data, int samples, int64_t ou if (mp_mutex_trylock(&p->lock)) return 0; - int pos = ao_read_data_unlocked(ao, data, samples, out_time_ns, false); + int pos = ao_read_data_locked(ao, data, samples, out_time_ns, false); mp_mutex_unlock(&p->lock); @@ -347,7 +352,7 @@ void ao_reset(struct ao *ao) ao->driver->reset(ao); if (wakeup) - ao_wakeup_playthread(ao); + ao_wakeup(ao); } // Initiate playback. This moves from the stop/underrun state to actually @@ -374,14 +379,14 @@ void ao_start(struct ao *ao) if (do_start) ao->driver->start(ao); - ao_wakeup_playthread(ao); + ao_wakeup(ao); } void ao_set_paused(struct ao *ao, bool paused, bool eof) { struct buffer_state *p = ao->buffer_state; bool wakeup = false; - bool do_reset = false, do_start = false; + bool do_change_state = false; // If we are going to pause on eof and ao is still playing, // be sure to drain the ao first for gapless. @@ -402,9 +407,9 @@ void ao_set_paused(struct ao *ao, bool paused, bool eof) p->streaming = false; p->recover_pause = !ao->untimed; } - } else if (ao->driver->reset) { + } else if (ao->driver->reset || ao->driver->set_pause) { // See ao_reset() why this is done outside of the lock. - do_reset = true; + do_change_state = true; p->streaming = false; } } @@ -416,7 +421,7 @@ void ao_set_paused(struct ao *ao, bool paused, bool eof) p->hw_paused = false; } else { if (!p->streaming) - do_start = true; + do_change_state = true; p->streaming = true; } wakeup = true; @@ -425,13 +430,25 @@ void ao_set_paused(struct ao *ao, bool paused, bool eof) mp_mutex_unlock(&p->lock); - if (do_reset) - ao->driver->reset(ao); - if (do_start) - ao->driver->start(ao); + if (do_change_state) { + if (ao->driver->set_pause) { + if (paused) { + ao->driver->set_pause(ao, true); + p->queued_time_ns = p->end_time_ns - mp_time_ns(); + } else { + p->end_time_ns = p->queued_time_ns + mp_time_ns(); + ao->driver->set_pause(ao, false); + } + } else { + if (paused) + ao->driver->reset(ao); + else + ao->driver->start(ao); + } + } if (wakeup) - ao_wakeup_playthread(ao); + ao_wakeup(ao); } // Whether audio is playing. This means that there is still data in the buffers, @@ -486,7 +503,7 @@ void ao_drain(struct ao *ao) static void wakeup_filters(void *ctx) { struct ao *ao = ctx; - ao_wakeup_playthread(ao); + ao_wakeup(ao); } void ao_uninit(struct ao *ao) @@ -561,7 +578,7 @@ bool init_buffer_post(struct ao *ao) mp_filter_graph_set_wakeup_cb(p->filter_root, wakeup_filters, ao); p->thread_valid = true; - if (mp_thread_create(&p->thread, playthread, ao)) { + if (mp_thread_create(&p->thread, ao_thread, ao)) { p->thread_valid = false; return false; } @@ -684,7 +701,7 @@ eof: return true; } -static MP_THREAD_VOID playthread(void *arg) +static MP_THREAD_VOID ao_thread(void *arg) { struct ao *ao = arg; struct buffer_state *p = ao->buffer_state; @@ -731,6 +748,6 @@ void ao_unblock(struct ao *ao) mp_mutex_lock(&p->lock); p->initial_unblocked = true; mp_mutex_unlock(&p->lock); - ao_wakeup_playthread(ao); + ao_wakeup(ao); } } diff --git a/audio/out/internal.h b/audio/out/internal.h index 7951b38..51429b9 100644 --- a/audio/out/internal.h +++ b/audio/out/internal.h @@ -108,6 +108,7 @@ struct mp_pcm_state { * start * Optional for both types: * control + * set_pause * a) ->write is called to queue audio. push.c creates a thread to regularly * refill audio device buffers with ->write, but all driver functions are * always called under an exclusive lock. @@ -115,8 +116,6 @@ struct mp_pcm_state { * reset * write * get_state - * Optional: - * set_pause * b) ->write must be NULL. ->start must be provided, and should make the * audio API start calling the audio callback. Your audio callback should * in turn call ao_read_data() to get audio data. Most functions are @@ -149,6 +148,9 @@ struct ao_driver { // Stop all audio playback, clear buffers, back to state after init(). // Optional for pull AOs. void (*reset)(struct ao *ao); + // pull based: set pause state. Only called after start() and before reset(). + // The return value is ignored. + // The pausing state is also cleared by reset(). // push based: set pause state. Only called after start() and before reset(). // returns success (this is intended for paused=true; if it // returns false, playback continues, and the core emulates via @@ -157,7 +159,7 @@ struct ao_driver { bool (*set_pause)(struct ao *ao, bool paused); // pull based: start the audio callback // push based: start playing queued data - // AO should call ao_wakeup_playthread() if a period boundary + // AO should call ao_wakeup() if a period boundary // is crossed, or playback stops due to external reasons // (including underruns or device removal) // must set mp_pcm_state.playing; unset on error/underrun/end @@ -229,7 +231,7 @@ bool ao_can_convert_inplace(struct ao_convert_fmt *fmt); bool ao_need_conversion(struct ao_convert_fmt *fmt); void ao_convert_inplace(struct ao_convert_fmt *fmt, void **data, int num_samples); -void ao_wakeup_playthread(struct ao *ao); +void ao_wakeup(struct ao *ao); int ao_read_data_converted(struct ao *ao, struct ao_convert_fmt *fmt, void **data, int samples, int64_t out_time_ns); diff --git a/ci/build-freebsd.sh b/ci/build-freebsd.sh index bc68a25..e272ef6 100755 --- a/ci/build-freebsd.sh +++ b/ci/build-freebsd.sh @@ -5,10 +5,11 @@ export CFLAGS="$CFLAGS -isystem/usr/local/include" export CXXFLAGS="$CXXFLAGS -isystem/usr/local/include" export LDFLAGS="$LDFLAGS -L/usr/local/lib" +# TODO: readd -Ddvbin=enabled + meson setup build \ --werror \ - -Dlibplacebo:werror=false \ - -Dc_args="-Wno-error=deprecated -Wno-error=deprecated-declarations" \ + -Dc_args="-Wno-error=deprecated -Wno-error=deprecated-declarations -march=native" \ -Diconv=disabled \ -Dlibmpv=true \ -Dlua=enabled \ @@ -19,7 +20,6 @@ meson setup build \ -Dvdpau=enabled \ -Dvulkan=enabled \ -Doss-audio=enabled \ - $(pkg info -q v4l_compat && echo -Ddvbin=enabled) \ $(pkg info -q libdvdnav && echo -Ddvdnav=enabled) \ $(pkg info -q libcdio-paranoia && echo -Dcdda=enabled) \ $(pkg info -q pipewire && echo -Dpipewire=enabled) \ diff --git a/ci/build-linux-old.sh b/ci/build-linux-old.sh new file mode 100755 index 0000000..d9cd9ee --- /dev/null +++ b/ci/build-linux-old.sh @@ -0,0 +1,18 @@ +#!/bin/sh +set -e + +# clone exactly the oldest libplacebo we want to support +rm -rf subprojects +mkdir -p subprojects +git clone https://code.videolan.org/videolan/libplacebo.git \ + --recurse-submodules --shallow-submodules \ + --depth=1 --branch v6.338 subprojects/libplacebo \ + +meson setup build \ + -Dlibplacebo:vulkan=disabled \ + -Dlibmpv=true \ + -Dlua=enabled \ + -Dtests=true + +meson compile -C build +./build/mpv -v --no-config diff --git a/ci/build-macos.sh b/ci/build-macos.sh index 14b3a1b..d11befa 100755 --- a/ci/build-macos.sh +++ b/ci/build-macos.sh @@ -12,10 +12,13 @@ fi PKG_CONFIG_PATH="${FFMPEG_SYSROOT}/lib/pkgconfig/" CC="${CC}" CXX="${CXX}" \ meson setup build \ + --werror \ -Dprefix="${MPV_INSTALL_PREFIX}" \ + -D{c_args,objc_args}="-Wno-error=deprecated -Wno-error=deprecated-declarations" \ -D{libmpv,tests}=true \ -D{gl,iconv,lcms2,lua,jpeg,plain-gl,zlib}=enabled \ - -D{cocoa,coreaudio,gl-cocoa,macos-cocoa-cb,macos-touchbar,videotoolbox-gl}=enabled + -D{cocoa,coreaudio,gl-cocoa,videotoolbox-gl,videotoolbox-pl}=enabled \ + -D{swift-build,macos-cocoa-cb,macos-media-player,macos-touchbar,vulkan}=enabled meson compile -C build -j4 meson install -C build diff --git a/ci/build-mingw64.sh b/ci/build-mingw64.sh index adca649..ae64473 100755 --- a/ci/build-mingw64.sh +++ b/ci/build-mingw64.sh @@ -6,10 +6,11 @@ ln -snf . "$prefix_dir/usr" ln -snf . "$prefix_dir/local" wget="wget -nc --progress=bar:force" -gitclone="git clone --depth=1 --recursive" +gitclone="git clone --depth=1 --recursive --shallow-submodules" # -posix is Ubuntu's variant with pthreads support export CC=$TARGET-gcc-posix +export AS=$TARGET-gcc-posix export CXX=$TARGET-g++-posix export AR=$TARGET-ar export NM=$TARGET-nm @@ -50,9 +51,11 @@ EOF # CMake cmake_args=( -Wno-dev + -DCMAKE_SYSTEM_PROCESSOR="${fam}" -DCMAKE_SYSTEM_NAME=Windows -DCMAKE_FIND_ROOT_PATH="$PKG_CONFIG_SYSROOT_DIR" -DCMAKE_RC_COMPILER="${TARGET}-windres" + -DCMAKE_ASM_COMPILER="$AS" -DCMAKE_BUILD_TYPE=Release ) @@ -110,7 +113,7 @@ _iconv () { _iconv_mark=lib/libiconv.dll.a _zlib () { - local ver=1.3 + local ver=1.3.1 gettar "https://zlib.net/fossils/zlib-${ver}.tar.gz" pushd zlib-${ver} make -fwin32/Makefile.gcc clean @@ -121,6 +124,16 @@ _zlib () { } _zlib_mark=lib/libz.dll.a +_dav1d () { + [ -d dav1d ] || $gitclone https://code.videolan.org/videolan/dav1d.git + builddir dav1d + meson setup .. --cross-file "$prefix_dir/crossfile" \ + -Denable_{tools,tests}=false + makeplusinstall + popd +} +_dav1d_mark=lib/libdav1d.dll.a + _ffmpeg () { [ -d ffmpeg ] || $gitclone https://github.com/FFmpeg/FFmpeg.git ffmpeg builddir ffmpeg @@ -129,7 +142,7 @@ _ffmpeg () { --enable-cross-compile --cross-prefix=$TARGET- --arch=${TARGET%%-*} --cc="$CC" --cxx="$CXX" $commonflags --disable-{doc,programs,muxers,encoders} - --enable-encoder=mjpeg,png + --enable-muxer=spdif --enable-encoder=mjpeg,png --enable-libdav1d ) pkg-config vulkan && args+=(--enable-vulkan --enable-libshaderc) ../configure "${args[@]}" @@ -161,8 +174,16 @@ _spirv_cross () { } _spirv_cross_mark=lib/libspirv-cross-c-shared.dll.a +_nv_headers () { + [ -d nv-codec-headers ] || $gitclone https://github.com/FFmpeg/nv-codec-headers + pushd nv-codec-headers + makeplusinstall + popd +} +_nv_headers_mark=include/ffnvcodec/dynlink_loader.h + _vulkan_headers () { - [ -d Vulkan-Headers ] || $gitclone https://github.com/KhronosGroup/Vulkan-Headers -b v1.3.266 + [ -d Vulkan-Headers ] || $gitclone https://github.com/KhronosGroup/Vulkan-Headers builddir Vulkan-Headers cmake .. "${cmake_args[@]}" makeplusinstall @@ -171,10 +192,10 @@ _vulkan_headers () { _vulkan_headers_mark=include/vulkan/vulkan.h _vulkan_loader () { - [ -d Vulkan-Loader ] || $gitclone https://github.com/KhronosGroup/Vulkan-Loader -b v1.3.266 + [ -d Vulkan-Loader ] || $gitclone https://github.com/KhronosGroup/Vulkan-Loader builddir Vulkan-Loader cmake .. "${cmake_args[@]}" \ - -DENABLE_WERROR=OFF + -DENABLE_WERROR=OFF -DUSE_GAS=ON makeplusinstall popd } @@ -191,7 +212,7 @@ _libplacebo () { _libplacebo_mark=lib/libplacebo.dll.a _freetype () { - local ver=2.13.1 + local ver=2.13.2 gettar "https://mirror.netcologne.de/savannah/freetype/freetype-${ver}.tar.xz" builddir freetype-${ver} meson setup .. --cross-file "$prefix_dir/crossfile" @@ -212,7 +233,7 @@ _fribidi () { _fribidi_mark=lib/libfribidi.dll.a _harfbuzz () { - local ver=8.1.1 + local ver=8.3.0 gettar "https://github.com/harfbuzz/harfbuzz/releases/download/${ver}/harfbuzz-${ver}.tar.xz" builddir harfbuzz-${ver} meson setup .. --cross-file "$prefix_dir/crossfile" \ @@ -246,7 +267,7 @@ _luajit () { } _luajit_mark=lib/libluajit-5.1.a -for x in iconv zlib shaderc spirv-cross; do +for x in iconv zlib shaderc spirv-cross nv-headers dav1d; do build_if_missing $x done if [[ "$TARGET" != "i686-"* ]]; then @@ -269,7 +290,6 @@ rm -rf $build meson setup $build --cross-file "$prefix_dir/crossfile" \ --werror \ - -Dlibplacebo:werror=false \ -Dc_args="-Wno-error=deprecated -Wno-error=deprecated-declarations" \ --buildtype debugoptimized \ -Dlibmpv=true -Dlua=luajit \ @@ -280,7 +300,7 @@ meson compile -C $build if [ "$2" = pack ]; then mkdir -p artifact/tmp echo "Copying:" - cp -pv $build/player/mpv.com $build/mpv.exe artifact/ + cp -pv $build/mpv.com $build/mpv.exe artifact/ # copy everything we can get our hands on cp -p "$prefix_dir/bin/"*.dll artifact/tmp/ shopt -s nullglob @@ -292,9 +312,11 @@ if [ "$2" = pack ]; then dlls=( libgcc_*.dll lib{ssp,stdc++,winpthread}-[0-9]*.dll # compiler runtime av*.dll sw*.dll lib{ass,freetype,fribidi,harfbuzz,iconv,placebo}-[0-9]*.dll - lib{shaderc_shared,spirv-cross-c-shared}.dll zlib1.dll - # note: vulkan-1.dll is not here since drivers provide it + lib{shaderc_shared,spirv-cross-c-shared,dav1d}.dll zlib1.dll ) + if [[ -f vulkan-1.dll ]]; then + dlls+=(vulkan-1.dll) + fi mv -v "${dlls[@]}" .. popd diff --git a/ci/build-msys2.sh b/ci/build-msys2.sh index d3e1ce9..1a0e022 100755 --- a/ci/build-msys2.sh +++ b/ci/build-msys2.sh @@ -1,19 +1,7 @@ #!/bin/sh -e -mkdir subprojects -cat > subprojects/libplacebo.wrap <<EOF -[wrap-git] -url = https://code.videolan.org/videolan/libplacebo.git -revision = v6.338.1 -depth = 1 -clone-recursive = true -EOF - meson setup build \ --werror \ - -Dlibplacebo:werror=false \ - -Dlibplacebo:demos=false \ - -Dlibplacebo:default_library=static \ -Dc_args="-Wno-error=deprecated -Wno-error=deprecated-declarations" \ -D cdda=enabled \ -D d3d-hwaccel=enabled \ @@ -35,5 +23,4 @@ meson setup build \ -D uchardet=enabled \ -D vapoursynth=enabled meson compile -C build -cp ./build/player/mpv.com ./build ./build/mpv.com -v --no-config diff --git a/ci/build-openbsd.sh b/ci/build-openbsd.sh new file mode 100755 index 0000000..8e9125c --- /dev/null +++ b/ci/build-openbsd.sh @@ -0,0 +1,22 @@ +#!/bin/sh +set -e + +# libplacebo on openBSD is too old; use a subproject +rm -rf subprojects +mkdir -p subprojects +git clone https://code.videolan.org/videolan/libplacebo.git \ + --recurse-submodules --shallow-submodules \ + --depth=1 --recurse-submodules subprojects/libplacebo + +meson setup build \ + -Dlibmpv=true \ + -Dlua=enabled \ + -Dopenal=enabled \ + -Dpulse=enabled \ + -Dtests=true \ + -Dvulkan=enabled \ + -Ddvdnav=enabled \ + -Dcdda=enabled + +meson compile -C build +./build/mpv -v --no-config diff --git a/ci/build-tumbleweed.sh b/ci/build-tumbleweed.sh index 03e8d1a..069372a 100755 --- a/ci/build-tumbleweed.sh +++ b/ci/build-tumbleweed.sh @@ -3,7 +3,6 @@ set -e meson setup build \ --werror \ - -Dlibplacebo:werror=false \ -Dc_args="-Wno-error=deprecated -Wno-error=deprecated-declarations" \ -Db_sanitize=address,undefined \ -Dcdda=enabled \ @@ -13,7 +12,6 @@ meson setup build \ -Dlibmpv=true \ -Dmanpage-build=enabled \ -Dpipewire=enabled \ - -Dshaderc=enabled \ -Dtests=true \ -Dvulkan=enabled meson compile -C build diff --git a/ci/lint-commit-msg.py b/ci/lint-commit-msg.py index 4198ed4..10c7d18 100755 --- a/ci/lint-commit-msg.py +++ b/ci/lint-commit-msg.py @@ -1,12 +1,13 @@ #!/usr/bin/env python3 import os, sys, json, subprocess, re +from typing import Dict, Tuple, Callable, Optional def call(cmd) -> str: sys.stdout.flush() ret = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, text=True) return ret.stdout -lint_rules = {} +lint_rules: Dict[str, Tuple[Callable, str]] = {} def lint_rule(description: str): def f(func): @@ -14,7 +15,7 @@ def lint_rule(description: str): lint_rules[func.__name__] = (func, description) return f -def get_commit_range() -> str: +def get_commit_range() -> Optional[str]: if len(sys.argv) > 1: return sys.argv[1] # https://github.com/actions/runner/issues/342#issuecomment-590670059 @@ -28,6 +29,7 @@ def get_commit_range() -> str: return event["before"] + "..." + event["after"] elif event_name == "pull_request": return event["pull_request"]["base"]["sha"] + ".." + event["pull_request"]["head"]["sha"] + return None def do_lint(commit_range: str) -> bool: commits = call(["git", "log", "--pretty=format:%h %s", commit_range]).splitlines() @@ -56,7 +58,7 @@ def do_lint(commit_range: str) -> bool: ################################################################################ -NO_PREFIX_WHITELIST = r"^Revert \"(.*)\"|^Release [0-9]|^Update VERSION$" +NO_PREFIX_WHITELIST = r"^Revert \"(.*)\"|^Reapply \"(.*)\"|^Release [0-9]|^Update VERSION$" @lint_rule("Subject line must contain a prefix identifying the sub system") def subsystem_prefix(body): @@ -98,7 +100,7 @@ def no_merge(body): @lint_rule("Subject line should be shorter than 72 characters") def line_too_long(body): - revert = re.search(r"^Revert \"(.*)\"", body[0]) + revert = re.search(r"^Revert \"(.*)\"|^Reapply \"(.*)\"", body[0]) return True if revert else len(body[0]) <= 72 @lint_rule("Prefix should not include C file extensions (use `vo_gpu: ...` not `vo_gpu.c: ...`)") diff --git a/common/av_common.c b/common/av_common.c index 5d07349..e5733c3 100644 --- a/common/av_common.c +++ b/common/av_common.c @@ -402,3 +402,12 @@ void mp_free_av_packet(AVPacket **pkt) } av_packet_free(pkt); } + +void mp_codec_info_from_av(const AVCodecContext *avctx, struct mp_codec_params *c) +{ + c->codec_profile = av_get_profile_name(avctx->codec, avctx->profile); + if (!c->codec_profile) + c->codec_profile = avcodec_profile_name(avctx->codec_id, avctx->profile); + c->codec = avctx->codec_descriptor->name; + c->codec_desc = avctx->codec_descriptor->long_name; +} diff --git a/common/av_common.h b/common/av_common.h index 1f05e14..c584085 100644 --- a/common/av_common.h +++ b/common/av_common.h @@ -50,5 +50,6 @@ void mp_avdict_print_unset(struct mp_log *log, int msgl, struct AVDictionary *d) int mp_set_avopts(struct mp_log *log, void *avobj, char **kv); int mp_set_avopts_pos(struct mp_log *log, void *avobj, void *posargs, char **kv); void mp_free_av_packet(AVPacket **pkt); +void mp_codec_info_from_av(const AVCodecContext *avctx, struct mp_codec_params *c); #endif diff --git a/common/av_log.c b/common/av_log.c index b6bad04..54b78a6 100644 --- a/common/av_log.c +++ b/common/av_log.c @@ -28,6 +28,7 @@ #include "common/global.h" #include "common/msg.h" #include "config.h" +#include "misc/bstr.h" #include "osdep/threads.h" #include <libavutil/avutil.h> @@ -55,6 +56,7 @@ static mp_static_mutex log_lock = MP_STATIC_MUTEX_INITIALIZER; static struct mpv_global *log_mpv_instance; static struct mp_log *log_root, *log_decaudio, *log_decvideo, *log_demuxer; static bool log_print_prefix = true; +static bstr log_buffer; static int av_log_level_to_mp_level(int av_level) { @@ -80,7 +82,7 @@ static struct mp_log *get_av_log(void *ptr) if (!avc) { mp_warn(log_root, "av_log callback called with bad parameters (NULL AVClass).\n" - "This is a bug in one of Libav/FFmpeg libraries used.\n"); + "This is a bug in one of FFmpeg libraries used.\n"); return log_root; } @@ -106,6 +108,11 @@ static struct mp_log *get_av_log(void *ptr) return log_root; } +static const char *avclass_item_name(void *obj, const AVClass *avc) +{ + return (avc->item_name ? avc->item_name : av_default_item_name)(obj); +} + static void mp_msg_av_log_callback(void *ptr, int level, const char *fmt, va_list vl) { @@ -125,19 +132,20 @@ static void mp_msg_av_log_callback(void *ptr, int level, const char *fmt, struct mp_log *log = get_av_log(ptr); if (mp_msg_test(log, mp_level)) { - char buffer[4096] = ""; - int pos = 0; - const char *prefix = avc ? avc->item_name(ptr) : NULL; - if (log_print_prefix && prefix) - pos = snprintf(buffer, sizeof(buffer), "%s: ", prefix); - log_print_prefix = fmt[strlen(fmt) - 1] == '\n'; - - pos = MPMIN(MPMAX(pos, 0), sizeof(buffer)); - vsnprintf(buffer + pos, sizeof(buffer) - pos, fmt, vl); - - mp_msg(log, mp_level, "%s", buffer); + log_buffer.len = 0; + bstr_xappend_vasprintf(log_root, &log_buffer, fmt, vl); + if (!log_buffer.len) + goto done; + const char *prefix = avc ? avclass_item_name(ptr, avc) : NULL; + if (log_print_prefix && prefix) { + mp_msg(log, mp_level, "%s: %.*s", prefix, BSTR_P(log_buffer)); + } else { + mp_msg(log, mp_level, "%.*s", BSTR_P(log_buffer)); + } + log_print_prefix = log_buffer.start[log_buffer.len - 1] == '\n'; } +done: mp_mutex_unlock(&log_lock); } @@ -150,6 +158,7 @@ void init_libav(struct mpv_global *global) log_decaudio = mp_log_new(log_root, log_root, "audio"); log_decvideo = mp_log_new(log_root, log_root, "video"); log_demuxer = mp_log_new(log_root, log_root, "demuxer"); + log_buffer = (bstr){0}; av_log_set_callback(mp_msg_av_log_callback); } mp_mutex_unlock(&log_lock); diff --git a/common/common.c b/common/common.c index 9f8230f..7089a66 100644 --- a/common/common.c +++ b/common/common.c @@ -94,6 +94,25 @@ char *mp_format_time(double time, bool fractions) return mp_format_time_fmt(fractions ? "%H:%M:%S.%T" : "%H:%M:%S", time); } +char *mp_format_double(void *talloc_ctx, double val, int precision, + bool plus_sign, bool percent_sign, bool trim) +{ + bstr str = {0}; + const char *fmt = plus_sign ? "%+.*f" : "%.*f"; + bstr_xappend_asprintf(talloc_ctx, &str, fmt, precision, val); + size_t pos = str.len; + if (trim) { + while (--pos && str.start[pos] == '0') + str.len--; + if (str.start[pos] == '.') + str.len--; + } + if (percent_sign) + bstr_xappend(talloc_ctx, &str, bstr0("%")); + str.start[str.len] = '\0'; + return str.start; +} + // Set rc to the union of rc and rc2 void mp_rect_union(struct mp_rect *rc, const struct mp_rect *rc2) { diff --git a/common/common.h b/common/common.h index ccdd94b..cd9bea9 100644 --- a/common/common.h +++ b/common/common.h @@ -99,6 +99,12 @@ extern const char mpv_copyright[]; char *mp_format_time(double time, bool fractions); char *mp_format_time_fmt(const char *fmt, double time); +// Formats a double value to a string with the specified precision. +// Trailing zeros (and the dot) can be trimmed. +// Optionally, a plus sign and a percent sign can be added. +char *mp_format_double(void *talloc_ctx, double val, int precision, + bool plus_sign, bool percent_sign, bool trim); + struct mp_rect { int x0, y0; int x1, y1; diff --git a/common/encode_lavc.c b/common/encode_lavc.c index 898545d..a250f9c 100644 --- a/common/encode_lavc.c +++ b/common/encode_lavc.c @@ -870,8 +870,8 @@ bool encoder_init_codec_and_muxer(struct encoder_context *p, " ********************************************\n\n" "This means the output file may be broken or bad.\n" "Possible reasons, problems, workarounds:\n" - "- Codec implementation in ffmpeg/libav is not finished yet.\n" - " Try updating ffmpeg or libav.\n" + "- Codec implementation in ffmpeg is not finished yet.\n" + " Try updating ffmpeg.\n" "- Bad picture quality, blocks, blurriness.\n" " Experiment with codec settings to maybe still get the\n" " desired quality output at the expense of bitrate.\n" @@ -906,7 +906,7 @@ bool encoder_init_codec_and_muxer(struct encoder_context *p, return true; fail: - avcodec_close(p->encoder); + avcodec_free_context(&p->encoder); return false; } diff --git a/common/meson.build b/common/meson.build index 4bca5ea..202880f 100644 --- a/common/meson.build +++ b/common/meson.build @@ -1,5 +1,8 @@ version_h = vcs_tag( - command: ['git', 'describe', '--always', '--tags', '--dirty'], + command: ['git', + '--git-dir=' + join_paths(source_root, '.git'), + '--work-tree=' + source_root, + 'describe', '--always', '--tags', '--dirty'], input: 'version.h.in', output: 'version.h', replace_string: '@VERSION@', diff --git a/common/msg.c b/common/msg.c index b14bd5a..840f2ab 100644 --- a/common/msg.c +++ b/common/msg.c @@ -42,10 +42,12 @@ #include "msg.h" #include "msg_control.h" -// log buffer size (lines) for terminal level and logfile level -#define TERM_BUF 100 +// log buffer size (lines) logfile level #define FILE_BUF 100 +// lines to accumulate before any client requests the terminal loglevel +#define EARLY_TERM_BUF 100 + // logfile lines to accumulate during init before we know the log file name. // thousands of logfile lines during init can happen (especially with many // scripts, big config, etc), so we set 5000. If it cycles and messages are @@ -214,9 +216,9 @@ static void prepare_prefix(struct mp_log_root *root, bstr *out, int lev, int ter // Set cursor state if (new_lines && !root->status_lines) { - bstr_xappend(root, out, bstr0("\033[?25l")); + bstr_xappend(root, out, bstr0(TERM_ESC_HIDE_CURSOR)); } else if (!new_lines && root->status_lines) { - bstr_xappend(root, out, bstr0("\033[?25h")); + bstr_xappend(root, out, bstr0(TERM_ESC_RESTORE_CURSOR)); } int line_skip = 0; @@ -226,41 +228,49 @@ static void prepare_prefix(struct mp_log_root *root, bstr *out, int lev, int ter bstr up_clear = bstr0("\033[A\033[K"); for (int i = 1; i < root->status_lines; ++i) bstr_xappend(root, out, up_clear); - // Reposition cursor after last message - line_skip = (new_lines ? new_lines : root->blank_lines) - root->status_lines; - line_skip = MPMIN(root->blank_lines - root->status_lines, line_skip); - if (line_skip) - bstr_xappend_asprintf(root, out, "\033[%dA", line_skip); - } else if (new_lines) { - line_skip = new_lines - root->blank_lines; + assert(root->status_lines > 0 && root->blank_lines >= root->status_lines); + line_skip = root->blank_lines - root->status_lines; } - if (line_skip < 0) { - // Reposition cursor to keep status line at the same line - line_skip = MPMIN(root->blank_lines, -line_skip); - if (line_skip) - bstr_xappend_asprintf(root, out, "\033[%dB", line_skip); - } + if (new_lines) + line_skip -= MPMAX(0, root->blank_lines - new_lines); + + if (line_skip) + bstr_xappend_asprintf(root, out, line_skip > 0 ? "\033[%dA" : "\033[%dB", abs(line_skip)); root->blank_lines = MPMAX(0, root->blank_lines - term_lines); root->status_lines = new_lines; root->blank_lines += root->status_lines; } -void mp_msg_flush_status_line(struct mp_log *log) +void mp_msg_flush_status_line(struct mp_log *log, bool clear) { - if (log->root) { - mp_mutex_lock(&log->root->lock); - if (log->root->status_lines) { - bstr term_msg = (bstr){0}; - prepare_prefix(log->root, &term_msg, MSGL_STATUS, 0); - if (term_msg.len) { - fprintf(stderr, "%.*s", BSTR_P(term_msg)); - talloc_free(term_msg.start); - } - } - mp_mutex_unlock(&log->root->lock); + if (!log->root) + return; + + mp_mutex_lock(&log->root->lock); + if (!log->root->status_lines) + goto done; + + if (!clear) { + if (log->root->isatty[STDERR_FILENO]) + fprintf(stderr, TERM_ESC_RESTORE_CURSOR); + fprintf(stderr, "\n"); + log->root->blank_lines = 0; + log->root->status_lines = 0; + goto done; } + + bstr term_msg = {0}; + prepare_prefix(log->root, &term_msg, MSGL_STATUS, 0); + if (term_msg.len) { + fprintf(stderr, "%.*s", BSTR_P(term_msg)); + talloc_free(term_msg.start); + } + +done: + log->root->status_line.len = 0; + mp_mutex_unlock(&log->root->lock); } void mp_msg_set_term_title(struct mp_log *log, const char *title) @@ -268,7 +278,7 @@ void mp_msg_set_term_title(struct mp_log *log, const char *title) if (log->root && title) { // Lock because printf to terminal is not necessarily atomic. mp_mutex_lock(&log->root->lock); - fprintf(stderr, "\e]0;%s\007", title); + fprintf(stderr, "\033]0;%s\007", title); mp_mutex_unlock(&log->root->lock); } } @@ -284,15 +294,18 @@ bool mp_msg_has_status_line(struct mpv_global *global) static void set_term_color(void *talloc_ctx, bstr *text, int c) { - return c == -1 ? bstr_xappend(talloc_ctx, text, bstr0("\033[0m")) - : bstr_xappend_asprintf(talloc_ctx, text, - "\033[%d;3%dm", c >> 3, c & 7); + if (c == -1) { + bstr_xappend(talloc_ctx, text, bstr0("\033[0m")); + return; + } + + bstr_xappend_asprintf(talloc_ctx, text, "\033[%d;3%dm", c >> 3, c & 7); } static void set_msg_color(void *talloc_ctx, bstr *text, int lev) { static const int v_colors[] = {9, 1, 3, -1, -1, 2, 8, 8, 8, -1}; - return set_term_color(talloc_ctx, text, v_colors[lev]); + set_term_color(talloc_ctx, text, v_colors[lev]); } static void pretty_print_module(struct mp_log_root *root, bstr *text, @@ -325,31 +338,30 @@ static bool test_terminal_level(struct mp_log *log, int lev) } // This is very basic way to infer needed width for a string. -static int term_disp_width(bstr str, size_t start, size_t end) +static int term_disp_width(bstr str) { int width = 0; - bool escape = false; - const char *line = str.start; - for (size_t i = start; i < end && i < str.len; ++i) { - if (escape) { - escape = !(line[i] >= '@' && line[i] <= '~'); + while (str.len) { + if (bstr_eatstart0(&str, "\033[")) { + while (str.len && !((*str.start >= '@' && *str.start <= '~') || *str.start == 'm')) + str = bstr_cut(str, 1); + str = bstr_cut(str, 1); continue; } - if (line[i] == '\033' && line[i + 1] == '[') { - escape = true; - ++i; - continue; - } + bstr code = bstr_split_utf8(str, &str); + if (code.len == 0) + return 0; - if (line[i] == '\n') + if (code.len == 1 && *code.start == '\n') continue; + // Only single-width characters are supported width++; // Assume that everything before \r should be discarded for simplicity - if (line[i] == '\r') + if (code.len == 1 && *code.start == '\r') width = 0; } @@ -378,7 +390,7 @@ static void append_terminal_line(struct mp_log *log, int lev, bstr_xappend(root, term_msg, text); *line_w = root->isatty[term_msg_fileno(root, lev)] - ? term_disp_width(*term_msg, start, term_msg->len) : 0; + ? term_disp_width(bstr_splice(*term_msg, start, term_msg->len)) : 0; } static struct mp_log_buffer_entry *log_buffer_read(struct mp_log_buffer *buffer) @@ -486,9 +498,9 @@ static void write_term_msg(struct mp_log *log, int lev, bstr text, bstr *out) write_msg_to_buffers(log, lev, line); } - if (lev == MSGL_STATUS && print_term) { + if (lev == MSGL_STATUS) { int line_w = 0; - if (str.len) + if (str.len && print_term) append_terminal_line(log, lev, str, &root->term_msg_tmp, &line_w); term_msg_lines += !term_w ? (str.len ? 1 : 0) : (line_w + term_w - 1) / term_w; @@ -551,12 +563,9 @@ void mp_msg_va(struct mp_log *log, int lev, const char *format, va_list va) int fileno = term_msg_fileno(root, lev); FILE *stream = fileno == STDERR_FILENO ? stderr : stdout; if (root->term_msg.len) { - if (root->term_status_msg.len) { - fprintf(stream, "%.*s%.*s", BSTR_P(root->term_msg), - BSTR_P(root->term_status_msg)); - } else { - fprintf(stream, "%.*s", BSTR_P(root->term_msg)); - } + fwrite(root->term_msg.start, root->term_msg.len, 1, stream); + if (root->term_status_msg.len) + fwrite(root->term_status_msg.start, root->term_status_msg.len, 1, stream); fflush(stream); } } @@ -738,6 +747,10 @@ void mp_msg_update_msglevels(struct mpv_global *global, struct MPOpts *opts) root->module = opts->msg_module; root->use_terminal = opts->use_terminal; root->show_time = opts->msg_time; + + if (root->really_quiet) + root->status_lines = 0; + for (int i = STDOUT_FILENO; i <= STDERR_FILENO && root->use_terminal; ++i) { root->isatty[i] = isatty(i); root->color[i] = opts->msg_color && root->isatty[i]; @@ -836,7 +849,9 @@ bool mp_msg_has_log_file(struct mpv_global *global) void mp_msg_uninit(struct mpv_global *global) { struct mp_log_root *root = global->log->root; - mp_msg_flush_status_line(global->log); + mp_msg_flush_status_line(global->log, true); + if (root->really_quiet && root->isatty[STDERR_FILENO]) + fprintf(stderr, TERM_ESC_RESTORE_CURSOR); terminate_log_file_thread(root); mp_msg_log_buffer_destroy(root->early_buffer); mp_msg_log_buffer_destroy(root->early_filebuffer); @@ -903,7 +918,7 @@ void mp_msg_set_early_logging(struct mpv_global *global, bool enable) struct mp_log_root *root = global->log->root; mp_msg_set_early_logging_raw(global, enable, &root->early_buffer, - TERM_BUF, MP_LOG_BUFFER_MSGL_TERM); + EARLY_TERM_BUF, MP_LOG_BUFFER_MSGL_TERM); // normally MSGL_LOGFILE buffer gets a write thread, but not the early buf mp_msg_set_early_logging_raw(global, enable, &root->early_filebuffer, @@ -920,8 +935,6 @@ struct mp_log_buffer *mp_msg_log_buffer_new(struct mpv_global *global, mp_mutex_lock(&root->lock); if (level == MP_LOG_BUFFER_MSGL_TERM) { - size = TERM_BUF; - // The first thing which creates a terminal-level log buffer gets the // early log buffer, if it exists. This is supposed to enable a script // to grab log messages from before it was initialized. It's OK that @@ -929,6 +942,7 @@ struct mp_log_buffer *mp_msg_log_buffer_new(struct mpv_global *global, if (root->early_buffer) { struct mp_log_buffer *buffer = root->early_buffer; root->early_buffer = NULL; + mp_msg_log_buffer_resize(buffer, size); buffer->wakeup_cb = wakeup_cb; buffer->wakeup_cb_ctx = wakeup_cb_ctx; mp_mutex_unlock(&root->lock); @@ -958,6 +972,40 @@ struct mp_log_buffer *mp_msg_log_buffer_new(struct mpv_global *global, return buffer; } +void mp_msg_log_buffer_resize(struct mp_log_buffer *buffer, int size) +{ + mp_mutex_lock(&buffer->lock); + + assert(size > 0); + if (buffer->capacity < size && + buffer->entry0 + buffer->num_entries <= buffer->capacity) { + // shortcut if buffer doesn't wrap + buffer->entries = talloc_realloc(buffer, buffer->entries, + struct mp_log_buffer_entry *, size); + } else if (buffer->capacity != size) { + struct mp_log_buffer_entry **entries = + talloc_array(buffer, struct mp_log_buffer_entry *, size); + int num_entries = 0; + for (int i = buffer->num_entries - 1; i >= 0; i--) { + int entry = (buffer->entry0 + i) % buffer->num_entries; + struct mp_log_buffer_entry *res = buffer->entries[entry]; + if (num_entries < size) { + entries[num_entries++] = res; + } else { + talloc_free(res); + buffer->dropped += 1; + } + } + talloc_free(buffer->entries); + buffer->entries = entries; + buffer->entry0 = 0; + buffer->num_entries = num_entries; + } + buffer->capacity = size; + + mp_mutex_unlock(&buffer->lock); +} + void mp_msg_log_buffer_set_silent(struct mp_log_buffer *buffer, bool silent) { mp_mutex_lock(&buffer->lock); diff --git a/common/msg_control.h b/common/msg_control.h index e4da59e..ee02eb2 100644 --- a/common/msg_control.h +++ b/common/msg_control.h @@ -14,7 +14,7 @@ bool mp_msg_has_status_line(struct mpv_global *global); bool mp_msg_has_log_file(struct mpv_global *global); void mp_msg_set_early_logging(struct mpv_global *global, bool enable); -void mp_msg_flush_status_line(struct mp_log *log); +void mp_msg_flush_status_line(struct mp_log *log, bool clear); void mp_msg_set_term_title(struct mp_log *log, const char *title); struct mp_log_buffer_entry { @@ -35,6 +35,7 @@ struct mp_log_buffer *mp_msg_log_buffer_new(struct mpv_global *global, void *wakeup_cb_ctx); void mp_msg_log_buffer_destroy(struct mp_log_buffer *buffer); struct mp_log_buffer_entry *mp_msg_log_buffer_read(struct mp_log_buffer *buffer); +void mp_msg_log_buffer_resize(struct mp_log_buffer *buffer, int size); void mp_msg_log_buffer_set_silent(struct mp_log_buffer *buffer, bool silent); int mp_msg_find_level(const char *s); diff --git a/common/playlist.c b/common/playlist.c index c1636bc..39c49a5 100644 --- a/common/playlist.c +++ b/common/playlist.c @@ -60,13 +60,23 @@ static void playlist_update_indexes(struct playlist *pl, int start, int end) pl->entries[n]->pl_index = n; } -void playlist_add(struct playlist *pl, struct playlist_entry *add) +// Inserts the entry so that it takes "at"'s place, shifting "at" and all +// further entires to the right (or append to end, if at==NULL). +void playlist_insert_at(struct playlist *pl, struct playlist_entry *add, + struct playlist_entry *at) { assert(add->filename); - MP_TARRAY_APPEND(pl, pl->entries, pl->num_entries, add); + assert(!at || at->pl == pl); + + int index = at ? at->pl_index : pl->num_entries; + MP_TARRAY_INSERT_AT(pl, pl->entries, pl->num_entries, index, add); + add->pl = pl; - add->pl_index = pl->num_entries - 1; + add->pl_index = index; add->id = ++pl->id_alloc; + + playlist_update_indexes(pl, index, pl->num_entries); + talloc_steal(pl, add); } @@ -137,9 +147,9 @@ void playlist_move(struct playlist *pl, struct playlist_entry *entry, MPMAX(index + 1, old_index + 1)); } -void playlist_add_file(struct playlist *pl, const char *filename) +void playlist_append_file(struct playlist *pl, const char *filename) { - playlist_add(pl, playlist_entry_new(filename)); + playlist_insert_at(pl, playlist_entry_new(filename), NULL); } void playlist_populate_playlist_path(struct playlist *pl, const char *path) @@ -302,8 +312,8 @@ void playlist_set_stream_flags(struct playlist *pl, int flags) pl->entries[n]->stream_flags = flags; } -static int64_t playlist_transfer_entries_to(struct playlist *pl, int dst_index, - struct playlist *source_pl) +int64_t playlist_transfer_entries_to(struct playlist *pl, int dst_index, + struct playlist *source_pl) { assert(pl != source_pl); struct playlist_entry *first = playlist_get_first(source_pl); @@ -391,6 +401,7 @@ struct playlist *playlist_parse_file(const char *file, struct mp_cancel *cancel, struct playlist *ret = NULL; if (d && d->playlist) { ret = talloc_zero(NULL, struct playlist); + playlist_populate_playlist_path(d->playlist, file); playlist_transfer_entries(ret, d->playlist); if (d->filetype && strcmp(d->filetype, "hls") == 0) { mp_warn(log, "This might be a HLS stream. For correct operation, " diff --git a/common/playlist.h b/common/playlist.h index aecd539..853cd31 100644 --- a/common/playlist.h +++ b/common/playlist.h @@ -81,7 +81,9 @@ void playlist_entry_add_params(struct playlist_entry *e, struct playlist_entry *playlist_entry_new(const char *filename); -void playlist_add(struct playlist *pl, struct playlist_entry *add); +void playlist_insert_at(struct playlist *pl, struct playlist_entry *entry, + struct playlist_entry *at); + void playlist_remove(struct playlist *pl, struct playlist_entry *entry); void playlist_clear(struct playlist *pl); void playlist_clear_except_current(struct playlist *pl); @@ -89,7 +91,7 @@ void playlist_clear_except_current(struct playlist *pl); void playlist_move(struct playlist *pl, struct playlist_entry *entry, struct playlist_entry *at); -void playlist_add_file(struct playlist *pl, const char *filename); +void playlist_append_file(struct playlist *pl, const char *filename); void playlist_populate_playlist_path(struct playlist *pl, const char *path); void playlist_shuffle(struct playlist *pl); void playlist_unshuffle(struct playlist *pl); @@ -104,6 +106,8 @@ struct playlist_entry *playlist_get_first_in_same_playlist(struct playlist_entry char *current_playlist_path); void playlist_add_base_path(struct playlist *pl, bstr base_path); void playlist_set_stream_flags(struct playlist *pl, int flags); +int64_t playlist_transfer_entries_to(struct playlist *pl, int dst_index, + struct playlist *source_pl); int64_t playlist_transfer_entries(struct playlist *pl, struct playlist *source_pl); int64_t playlist_append_entries(struct playlist *pl, struct playlist *source_pl); diff --git a/common/version.h.in b/common/version.h.in index b09718f..25d6441 100644 --- a/common/version.h.in +++ b/common/version.h.in @@ -1,5 +1,5 @@ #define VERSION "@VERSION@" -#define MPVCOPYRIGHT "Copyright © 2000-2023 mpv/MPlayer/mplayer2 projects" +#define MPVCOPYRIGHT "Copyright © 2000-2024 mpv/MPlayer/mplayer2 projects" #ifndef NO_BUILD_TIMESTAMPS #define BUILDDATE __DATE__ " " __TIME__ #else diff --git a/demux/cache.c b/demux/cache.c index 562eab0..6398f61 100644 --- a/demux/cache.c +++ b/demux/cache.c @@ -26,6 +26,7 @@ #include "common/msg.h" #include "common/av_common.h" #include "demux.h" +#include "misc/io_utils.h" #include "options/path.h" #include "options/m_config.h" #include "options/m_option.h" @@ -214,7 +215,7 @@ int64_t demux_cache_write(struct demux_cache *cache, struct demux_packet *dp) } assert(!dp->is_cached); - assert(dp->len >= 0 && dp->len <= INT32_MAX); + assert(dp->len <= INT32_MAX); assert(dp->avpacket->flags >= 0 && dp->avpacket->flags <= INT32_MAX); assert(dp->avpacket->side_data_elems >= 0 && dp->avpacket->side_data_elems <= INT32_MAX); @@ -260,7 +261,7 @@ int64_t demux_cache_write(struct demux_cache *cache, struct demux_packet *dp) for (int n = 0; n < dp->avpacket->side_data_elems; n++) { AVPacketSideData *sd = &dp->avpacket->side_data[n]; - assert(sd->size >= 0 && sd->size <= INT32_MAX); + assert(sd->size <= INT32_MAX); assert(sd->type >= 0 && sd->type <= INT32_MAX); struct sd_header sd_hd = { @@ -293,9 +294,6 @@ struct demux_packet *demux_cache_read(struct demux_cache *cache, uint64_t pos) if (!read_raw(cache, &hd, sizeof(hd))) return NULL; - if (hd.data_len >= (size_t)-1) - return NULL; - struct demux_packet *dp = new_demux_packet(hd.data_len); if (!dp) goto fail; diff --git a/demux/codec_tags.c b/demux/codec_tags.c index 55700d9..d118fbe 100644 --- a/demux/codec_tags.c +++ b/demux/codec_tags.c @@ -48,8 +48,8 @@ static const char *lookup_tag(int type, uint32_t tag) /* * As seen in the following page: * - * https://web.archive.org/web/20220406060153/ - * http://dream.cs.bath.ac.uk/researchdev/wave-ex/bformat.html + * <https://web.archive.org/web/20220406060153/ + * http://dream.cs.bath.ac.uk/researchdev/wave-ex/bformat.html> * * Note that the GUID struct in the above citation has its * integers encoded in little-endian format, which means that @@ -180,81 +180,6 @@ void mp_set_pcm_codec(struct mp_codec_params *c, bool sign, bool is_float, c->codec = talloc_strdup(c, codec); } -// map file extension/type to an image codec name -static const char *const type_to_codec[][2] = { - { "bmp", "bmp" }, - { "dpx", "dpx" }, - { "j2c", "jpeg2000" }, - { "j2k", "jpeg2000" }, - { "jp2", "jpeg2000" }, - { "jpc", "jpeg2000" }, - { "jpeg", "mjpeg" }, - { "jpg", "mjpeg" }, - { "jps", "mjpeg" }, - { "jls", "ljpeg" }, - { "thm", "mjpeg" }, - { "db", "mjpeg" }, - { "pcd", "photocd" }, - { "pfm", "pfm" }, - { "phm", "phm" }, - { "hdr", "hdr" }, - { "pcx", "pcx" }, - { "png", "png" }, - { "pns", "png" }, - { "ptx", "ptx" }, - { "tga", "targa" }, - { "tif", "tiff" }, - { "tiff", "tiff" }, - { "sgi", "sgi" }, - { "sun", "sunrast" }, - { "ras", "sunrast" }, - { "rs", "sunrast" }, - { "ra", "sunrast" }, - { "im1", "sunrast" }, - { "im8", "sunrast" }, - { "im24", "sunrast" }, - { "im32", "sunrast" }, - { "sunras", "sunrast" }, - { "xbm", "xbm" }, - { "pam", "pam" }, - { "pbm", "pbm" }, - { "pgm", "pgm" }, - { "pgmyuv", "pgmyuv" }, - { "ppm", "ppm" }, - { "pnm", "ppm" }, - { "gif", "gif" }, - { "pix", "brender_pix" }, - { "exr", "exr" }, - { "pic", "pictor" }, - { "qoi", "qoi" }, - { "xface", "xface" }, - { "xwd", "xwd" }, - { "svg", "svg" }, - {0} -}; - -bool mp_codec_is_image(const char *codec) -{ - if (codec) { - for (int n = 0; type_to_codec[n][0]; n++) { - if (strcasecmp(type_to_codec[n][1], codec) == 0) - return true; - } - } - return false; -} - -const char *mp_map_type_to_image_codec(const char *type) -{ - if (type) { - for (int n = 0; type_to_codec[n][0]; n++) { - if (strcasecmp(type_to_codec[n][0], type) == 0) - return type_to_codec[n][1]; - } - } - return NULL; -}; - static const char *const mimetype_to_codec[][2] = { {"image/apng", "apng"}, {"image/avif", "av1"}, diff --git a/demux/codec_tags.h b/demux/codec_tags.h index 8fc49df..147760b 100644 --- a/demux/codec_tags.h +++ b/demux/codec_tags.h @@ -28,8 +28,6 @@ void mp_set_codec_from_tag(struct mp_codec_params *c); void mp_set_pcm_codec(struct mp_codec_params *c, bool sign, bool is_float, int bits, bool is_be); -bool mp_codec_is_image(const char *codec); -const char *mp_map_type_to_image_codec(const char *type); const char *mp_map_mimetype_to_video_codec(const char *mimetype); #endif diff --git a/demux/demux.c b/demux/demux.c index 256e1b6..9aa63bc 100644 --- a/demux/demux.c +++ b/demux/demux.c @@ -367,7 +367,7 @@ struct demux_stream { bool eager; // try to keep at least 1 packet queued // if false, this stream is disabled, or passively // read (like subtitles) - bool still_image; // stream has still video images + bool still_image; // stream consists of multiple sparse still images bool refreshing; // finding old position after track switches bool eof; // end of demuxed stream? (true if no more packets) @@ -663,7 +663,7 @@ static void update_seek_ranges(struct demux_cached_range *range) } } - if (range->seek_start >= range->seek_end) + if (range->seek_start >= range->seek_end && !(range->is_bof && range->is_eof)) goto broken; prune_metadata(range); diff --git a/demux/demux_disc.c b/demux/demux_disc.c index 3dfff45..8cd4b7b 100644 --- a/demux/demux_disc.c +++ b/demux/demux_disc.c @@ -94,7 +94,7 @@ static void add_dvd_streams(demuxer_t *demuxer) // emulate the extradata struct mp_csp_params csp = MP_CSP_PARAMS_DEFAULTS; - struct mp_cmat cmatrix; + struct pl_transform3x3 cmatrix; mp_get_csp_matrix(&csp, &cmatrix); char *s = talloc_strdup(sh, ""); diff --git a/demux/demux_lavf.c b/demux/demux_lavf.c index 663fab4..fa9fd5c 100644 --- a/demux/demux_lavf.c +++ b/demux/demux_lavf.c @@ -29,14 +29,15 @@ #include <libavformat/avformat.h> #include <libavformat/avio.h> + #include <libavutil/avutil.h> #include <libavutil/avstring.h> -#include <libavutil/mathematics.h> -#include <libavutil/replaygain.h> #include <libavutil/display.h> -#include <libavutil/opt.h> - #include <libavutil/dovi_meta.h> +#include <libavutil/mathematics.h> +#include <libavutil/opt.h> +#include <libavutil/pixdesc.h> +#include <libavutil/replaygain.h> #include "audio/chmap_avchannel.h" @@ -146,7 +147,6 @@ struct format_hack { // segment, with e.g. HLS, player knows about the playlist main file only). bool clear_filepos : 1; bool linearize_audio_ts : 1;// compensate timestamp resets (audio only) - bool fix_editlists : 1; bool is_network : 1; bool no_seek : 1; bool no_pcm_seek : 1; @@ -175,8 +175,7 @@ static const struct format_hack format_hacks[] = { {"mxf", .use_stream_ids = true}, {"avi", .use_stream_ids = true}, {"asf", .use_stream_ids = true}, - {"mp4", .skipinfo = true, .fix_editlists = true, .no_pcm_seek = true, - .use_stream_ids = true}, + {"mp4", .skipinfo = true, .no_pcm_seek = true, .use_stream_ids = true}, {"matroska", .skipinfo = true, .no_pcm_seek = true, .use_stream_ids = true}, {"v4l2", .no_seek = true}, @@ -512,6 +511,10 @@ static int lavf_check_file(demuxer_t *demuxer, enum demux_check check) break; } + // AVIF always needs to find stream info + if (bstrcasecmp0(ext, "avif") == 0) + priv->format_hack.skipinfo = false; + if (score >= lavfdopts->probescore) break; @@ -673,6 +676,7 @@ static bool is_image(AVStream *st, bool attached_picture, const AVInputFormat *a bstr_endswith0(bstr0(avif->name), "_pipe") || strcmp(avif->name, "alias_pix") == 0 || strcmp(avif->name, "gif") == 0 || + strcmp(avif->name, "ico") == 0 || strcmp(avif->name, "image2pipe") == 0 || (st->codecpar->codec_id == AV_CODEC_ID_AV1 && st->nb_frames == 1) ); @@ -723,6 +727,7 @@ static void handle_new_stream(demuxer_t *demuxer, int i) sh->codec->samplerate = codec->sample_rate; sh->codec->bitrate = codec->bit_rate; + sh->codec->format_name = talloc_strdup(sh, av_get_sample_fmt_name(codec->format)); double delay = 0; if (codec->sample_rate > 0) @@ -761,6 +766,7 @@ static void handle_new_stream(demuxer_t *demuxer, int i) sh->codec->disp_w = codec->width; sh->codec->disp_h = codec->height; + sh->codec->format_name = talloc_strdup(sh, av_get_pix_fmt_name(codec->format)); if (st->avg_frame_rate.num) sh->codec->fps = av_q2d(st->avg_frame_rate); if (is_image(st, sh->attached_picture, priv->avif)) { @@ -1065,9 +1071,6 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check) guess_and_set_vobsub_name(demuxer, &dopts); - if (priv->format_hack.fix_editlists) - av_dict_set(&dopts, "advanced_editlist", "0", 0); - avfc->interrupt_callback = (AVIOInterruptCB){ .callback = interrupt_cb, .opaque = demuxer, @@ -1267,8 +1270,6 @@ static bool demux_lavf_read_packet(struct demuxer *demux, dp->duration = pkt->duration * av_q2d(st->time_base); dp->pos = pkt->pos; dp->keyframe = pkt->flags & AV_PKT_FLAG_KEY; - if (pkt->flags & AV_PKT_FLAG_DISCARD) - MP_ERR(demux, "Edit lists are not correctly supported (FFmpeg issue).\n"); av_packet_unref(pkt); if (priv->format_hack.clear_filepos) diff --git a/demux/demux_libarchive.c b/demux/demux_libarchive.c index ec50498..a85e40f 100644 --- a/demux/demux_libarchive.c +++ b/demux/demux_libarchive.c @@ -91,7 +91,7 @@ static int open_file(struct demuxer *demuxer, enum demux_check check) qsort(files, num_files, sizeof(files[0]), cmp_filename); for (int n = 0; n < num_files; n++) - playlist_add_file(pl, files[n]); + playlist_append_file(pl, files[n]); playlist_set_stream_flags(pl, demuxer->stream_origin); diff --git a/demux/demux_mf.c b/demux/demux_mf.c index 8f7cb70..d971f04 100644 --- a/demux/demux_mf.c +++ b/demux/demux_mf.c @@ -95,7 +95,6 @@ static mf_t *open_mf_pattern(void *talloc_ctx, struct demuxer *d, char *filename } free_stream(s); - mp_info(log, "number of files: %d\n", mf->nr_of_files); goto exit_mf; } mp_info(log, "%s is not indirect filelist\n", filename + 1); @@ -117,7 +116,6 @@ static mf_t *open_mf_pattern(void *talloc_ctx, struct demuxer *d, char *filename } talloc_free(fname2); } - mp_info(log, "number of files: %d\n", mf->nr_of_files); goto exit_mf; } @@ -143,7 +141,6 @@ static mf_t *open_mf_pattern(void *talloc_ctx, struct demuxer *d, char *filename continue; mf_add(mf, gg.gl_pathv[i]); } - mp_info(log, "number of files: %d\n", mf->nr_of_files); globfree(&gg); goto exit_mf; } @@ -189,7 +186,9 @@ static mf_t *open_mf_pattern(void *talloc_ctx, struct demuxer *d, char *filename // 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); + mp_err(log, + "unsupported expr format: '%s' - exactly one format specifier of the form %%[.][NUM]d is expected\n", + filename); goto exit_mf; } @@ -208,9 +207,8 @@ static mf_t *open_mf_pattern(void *talloc_ctx, struct demuxer *d, char *filename } } - mp_info(log, "number of files: %d\n", mf->nr_of_files); - exit_mf: + mp_info(log, "number of files: %d\n", mf->nr_of_files); return mf; } @@ -284,6 +282,65 @@ static bool demux_mf_read_packet(struct demuxer *demuxer, return true; } +// map file extension/type to a codec name + +static const struct { + const char *type; + const char *codec; +} type2format[] = { + { "bmp", "bmp" }, + { "dpx", "dpx" }, + { "j2c", "jpeg2000" }, + { "j2k", "jpeg2000" }, + { "jp2", "jpeg2000" }, + { "jpc", "jpeg2000" }, + { "jpeg", "mjpeg" }, + { "jpg", "mjpeg" }, + { "jps", "mjpeg" }, + { "jls", "ljpeg" }, + { "thm", "mjpeg" }, + { "db", "mjpeg" }, + { "pcd", "photocd" }, + { "pfm", "pfm" }, + { "phm", "phm" }, + { "hdr", "hdr" }, + { "pcx", "pcx" }, + { "png", "png" }, + { "pns", "png" }, + { "ptx", "ptx" }, + { "tga", "targa" }, + { "tif", "tiff" }, + { "tiff", "tiff" }, + { "sgi", "sgi" }, + { "sun", "sunrast" }, + { "ras", "sunrast" }, + { "rs", "sunrast" }, + { "ra", "sunrast" }, + { "im1", "sunrast" }, + { "im8", "sunrast" }, + { "im24", "sunrast" }, + { "im32", "sunrast" }, + { "sunras", "sunrast" }, + { "xbm", "xbm" }, + { "pam", "pam" }, + { "pbm", "pbm" }, + { "pgm", "pgm" }, + { "pgmyuv", "pgmyuv" }, + { "ppm", "ppm" }, + { "pnm", "ppm" }, + { "gif", "gif" }, // usually handled by demux_lavf + { "pix", "brender_pix" }, + { "exr", "exr" }, + { "pic", "pictor" }, + { "qoi", "qoi" }, + { "xface", "xface" }, + { "xwd", "xwd" }, + { "svg", "svg" }, + { "webp", "webp" }, + { "jxl", "jpegxl" }, + {0} +}; + static const char *probe_format(mf_t *mf, char *type, enum demux_check check) { if (check > DEMUX_CHECK_REQUEST) @@ -294,9 +351,10 @@ static const char *probe_format(mf_t *mf, char *type, enum demux_check check) if (p) type = p + 1; } - const char *codec = mp_map_type_to_image_codec(type); - if (codec) - return codec; + for (int i = 0; type2format[i].type; i++) { + if (type && strcasecmp(type, type2format[i].type) == 0) + return type2format[i].codec; + } if (check == DEMUX_CHECK_REQUEST) { if (!org_type) { MP_ERR(mf, "file type was not set! (try --mf-type=ext)\n"); diff --git a/demux/demux_mkv.c b/demux/demux_mkv.c index 41226c5..443cec0 100644 --- a/demux/demux_mkv.c +++ b/demux/demux_mkv.c @@ -36,6 +36,8 @@ #include <libavcodec/avcodec.h> #include <libavcodec/version.h> +#include <libplacebo/utils/libav.h> + #include "config.h" #if HAVE_ZLIB @@ -108,10 +110,12 @@ typedef struct mkv_track { double v_frate; uint32_t colorspace; int stereo_mode; - struct mp_colorspace color; + struct pl_color_repr repr; + struct pl_color_space color; uint32_t v_crop_top, v_crop_left, v_crop_right, v_crop_bottom; + float v_projection_pose_yaw; + float v_projection_pose_pitch; float v_projection_pose_roll; - bool v_projection_pose_roll_set; uint32_t a_channels, a_bps; float a_sfreq; @@ -387,6 +391,10 @@ static bstr demux_mkv_decode(struct mp_log *log, mkv_track_t *track, } size = dstlen - out_avail; } else if (enc->comp_algo == 3) { + if (enc->comp_settings_len == 0 || !enc->comp_settings) { + mp_warn(log, "missing comp_settings, unable to reconstruct the data.\n"); + goto error; + } dest = talloc_size(track->parser_tmp, size + enc->comp_settings_len); memcpy(dest, enc->comp_settings, enc->comp_settings_len); memcpy(dest + enc->comp_settings_len, src, size); @@ -399,6 +407,8 @@ static bstr demux_mkv_decode(struct mp_log *log, mkv_track_t *track, talloc_free(src); if (!size) dest = NULL; + if (!dest) + size = 0; return (bstr){dest, size}; } @@ -573,24 +583,24 @@ static void parse_trackcolour(struct demuxer *demuxer, struct mkv_track *track, // 23001-8:2013/DCOR1, which is the same order used by libavutil/pixfmt.h, // so we can just re-use our avcol_ conversion functions. if (colour->n_matrix_coefficients) { - track->color.space = avcol_spc_to_mp_csp(colour->matrix_coefficients); + track->repr.sys = pl_system_from_av(colour->matrix_coefficients); MP_DBG(demuxer, "| + Matrix: %s\n", - m_opt_choice_str(mp_csp_names, track->color.space)); + m_opt_choice_str(pl_csp_names, track->repr.sys)); } if (colour->n_primaries) { - track->color.primaries = avcol_pri_to_mp_csp_prim(colour->primaries); + track->color.primaries = pl_primaries_from_av(colour->primaries); MP_DBG(demuxer, "| + Primaries: %s\n", - m_opt_choice_str(mp_csp_prim_names, track->color.primaries)); + m_opt_choice_str(pl_csp_prim_names, track->color.primaries)); } if (colour->n_transfer_characteristics) { - track->color.gamma = avcol_trc_to_mp_csp_trc(colour->transfer_characteristics); + track->color.transfer = pl_transfer_from_av(colour->transfer_characteristics); MP_DBG(demuxer, "| + Gamma: %s\n", - m_opt_choice_str(mp_csp_trc_names, track->color.gamma)); + m_opt_choice_str(pl_csp_trc_names, track->color.transfer)); } if (colour->n_range) { - track->color.levels = avcol_range_to_mp_csp_levels(colour->range); + track->repr.levels = pl_levels_from_av(colour->range); MP_DBG(demuxer, "| + Levels: %s\n", - m_opt_choice_str(mp_csp_levels_names, track->color.levels)); + m_opt_choice_str(pl_csp_levels_names, track->repr.levels)); } if (colour->n_max_cll) { track->color.hdr.max_cll = colour->max_cll; @@ -649,15 +659,30 @@ static void parse_trackcolour(struct demuxer *demuxer, struct mkv_track *track, static void parse_trackprojection(struct demuxer *demuxer, struct mkv_track *track, struct ebml_projection *projection) { - if (projection->n_projection_pose_yaw || projection->n_projection_pose_pitch) - MP_WARN(demuxer, "Projection pose yaw/pitch not supported!\n"); + if (projection->n_projection_pose_yaw) { + track->v_projection_pose_yaw = projection->projection_pose_yaw; + MP_DBG(demuxer, "| + Projection pose yaw: %f\n", + track->v_projection_pose_yaw); + } + + if (projection->n_projection_pose_pitch) { + track->v_projection_pose_pitch = projection->projection_pose_pitch; + MP_DBG(demuxer, "| + Projection pose pitch: %f\n", + track->v_projection_pose_pitch); + } if (projection->n_projection_pose_roll) { track->v_projection_pose_roll = projection->projection_pose_roll; - track->v_projection_pose_roll_set = true; MP_DBG(demuxer, "| + Projection pose roll: %f\n", track->v_projection_pose_roll); } + + if (track->v_projection_pose_yaw || track->v_projection_pose_pitch) { + MP_WARN(demuxer, "Not supported projection: yaw %f, pitch %f, roll %f\n", + track->v_projection_pose_yaw, + track->v_projection_pose_pitch, + track->v_projection_pose_roll); + } } static void parse_trackvideo(struct demuxer *demuxer, struct mkv_track *track, @@ -795,7 +820,10 @@ static void parse_trackentry(struct demuxer *demuxer, MP_DBG(demuxer, "| + CodecPrivate, length %u\n", track->private_size); } - if (entry->language) { + if (entry->language_bcp47) { + track->language = talloc_strdup(track, entry->language_bcp47); + MP_DBG(demuxer, "| + LanguageBCP47: %s\n", track->language); + } else if (entry->language) { track->language = talloc_strdup(track, entry->language); MP_DBG(demuxer, "| + Language: %s\n", track->language); } else { @@ -1082,8 +1110,8 @@ static int demux_mkv_read_chapters(struct demuxer *demuxer) } } - MP_DBG(demuxer, "Chapter %u from %02d:%02d:%02d.%03d " - "to %02d:%02d:%02d.%03d, %s\n", i, + MP_DBG(demuxer, "Chapter %u from %02d:%02d:%02d.%09d " + "to %02d:%02d:%02d.%09d, %s\n", i, (int) (chapter.start / 60 / 60 / 1000000000), (int) ((chapter.start / 60 / 1000000000) % 60), (int) ((chapter.start / 1000000000) % 60), @@ -1438,6 +1466,7 @@ static const char *const mkv_video_tags[][2] = { {"V_PNG", "png"}, {"V_AVS2", "avs2"}, {"V_AVS3", "avs3"}, + {"V_FFV1", "ffv1"}, {0} }; @@ -1523,10 +1552,6 @@ static int demux_mkv_open_video(demuxer_t *demuxer, mkv_track_t *track) } const char *codec = sh_v->codec ? sh_v->codec : ""; - if (mp_codec_is_image(codec)) { - sh->still_image = true; - sh->image = true; - } if (!strcmp(codec, "mjpeg")) { sh_v->codec_tag = MKTAG('m', 'j', 'p', 'g'); track->require_keyframes = true; @@ -1569,8 +1594,8 @@ static int demux_mkv_open_video(demuxer_t *demuxer, mkv_track_t *track) sh_v->stereo_mode = track->stereo_mode; sh_v->color = track->color; - if (track->v_projection_pose_roll_set) { - int rotate = lrintf(fmodf(fmodf(track->v_projection_pose_roll, 360) + 360, 360)); + if (track->v_projection_pose_roll) { + int rotate = lrintf(fmodf(fmodf(-1 * track->v_projection_pose_roll, 360) + 360, 360)); sh_v->rotate = rotate; } @@ -1647,6 +1672,7 @@ static void parse_flac_chmap(struct mp_chmap *channels, unsigned char *data, } static const char *const mkv_audio_tags[][2] = { + { "A_MPEG/L1", "mp1" }, { "A_MPEG/L2", "mp2" }, { "A_MPEG/L3", "mp3" }, { "A_AC3", "ac3" }, @@ -1835,7 +1861,7 @@ static int demux_mkv_open_audio(demuxer_t *demuxer, mkv_track_t *track) goto error; const char *codec = sh_a->codec; - if (!strcmp(codec, "mp2") || !strcmp(codec, "mp3") || + if (!strcmp(codec, "mp1") || !strcmp(codec, "mp2") || !strcmp(codec, "mp3") || !strcmp(codec, "truehd") || !strcmp(codec, "eac3")) { mkv_demuxer_t *mkv_d = demuxer->priv; @@ -1921,6 +1947,7 @@ static const char *const mkv_sub_tag[][2] = { { "S_TEXT/ASCII", "subrip"}, { "S_TEXT/UTF8", "subrip"}, { "S_HDMV/PGS", "hdmv_pgs_subtitle"}, + { "S_HDMV/TEXTST", "hdmv_text_subtitle"}, { "D_WEBVTT/SUBTITLES", "webvtt-webm"}, { "D_WEBVTT/CAPTIONS", "webvtt-webm"}, { "S_TEXT/WEBVTT", "webvtt"}, @@ -1962,7 +1989,7 @@ static int demux_mkv_open_sub(demuxer_t *demuxer, mkv_track_t *track) sh->codec->extradata = track->private_data; sh->codec->extradata_size = track->private_size; - if (!strcmp(sh->codec->codec, "arib_caption") && track->private_size >= 3) { + if (subtitle_type && !strcmp(sh->codec->codec, "arib_caption") && track->private_size >= 3) { struct AVCodecParameters **lavp = talloc_ptrtype(track, lavp); talloc_set_destructor(lavp, avcodec_par_destructor); @@ -2002,6 +2029,39 @@ static int demux_mkv_open_sub(demuxer_t *demuxer, mkv_track_t *track) return 0; } +// Workaround for broken files that don't set attached_picture +static void probe_if_image(demuxer_t *demuxer) +{ + mkv_demuxer_t *mkv_d = demuxer->priv; + + for (int n = 0; n < mkv_d->num_tracks; n++) { + int video_blocks = 0; + mkv_track_t *track = mkv_d->tracks[n]; + struct sh_stream *sh = track->stream; + + if (!sh || sh->type != STREAM_VIDEO || sh->image) + continue; + + int64_t timecode = -1; + // Arbitrary restriction on packet reading. + for (int i = 0; i < 1000; i++) { + int ret = read_next_block_into_queue(demuxer); + if (ret == 1 && mkv_d->blocks[i].track == track) { + if (timecode != mkv_d->blocks[i].timecode) + ++video_blocks; + timecode = mkv_d->blocks[i].timecode; + } + // No need to read more + if (video_blocks > 1) + break; + } + + // Assume still image + if (video_blocks == 1) + sh->image = true; + } +} + static void probe_x264_garbage(demuxer_t *demuxer) { mkv_demuxer_t *mkv_d = demuxer->priv; @@ -2034,6 +2094,8 @@ static void probe_x264_garbage(demuxer_t *demuxer) bstr sblock = {block->laces[0]->data, block->laces[0]->size}; bstr nblock = demux_mkv_decode(demuxer->log, track, sblock, 1); + if (!nblock.len) + continue; sh->codec->first_packet = new_demux_packet_from(nblock.start, nblock.len); talloc_steal(mkv_d, sh->codec->first_packet); @@ -2254,6 +2316,7 @@ static int demux_mkv_open(demuxer_t *demuxer, enum demux_check check) if (mkv_d->opts->probe_duration) probe_last_timestamp(demuxer, start_pos); probe_x264_garbage(demuxer); + probe_if_image(demuxer); return 0; } @@ -2795,6 +2858,8 @@ static int handle_block(demuxer_t *demuxer, struct block_info *block_info) bstr block = {data->data, data->size}; bstr nblock = demux_mkv_decode(demuxer->log, track, block, 1); + if (!nblock.len) + break; if (block.start != nblock.start || block.len != nblock.len) { // (avoidable copy of the entire data) @@ -2944,20 +3009,22 @@ static int read_next_block_into_queue(demuxer_t *demuxer) if (end > mkv_d->cluster_end) goto find_next_cluster; int res = read_block_group(demuxer, end, &block); - if (res < 0) - goto find_next_cluster; if (res > 0) goto add_block; + free_block(&block); + if (res < 0) + goto find_next_cluster; break; } case MATROSKA_ID_SIMPLEBLOCK: { block = (struct block_info){ .simple = true }; int res = read_block(demuxer, mkv_d->cluster_end, &block); - if (res < 0) - goto find_next_cluster; if (res > 0) goto add_block; + free_block(&block); + if (res < 0) + goto find_next_cluster; break; } diff --git a/demux/demux_playlist.c b/demux/demux_playlist.c index 63355be..66be4b1 100644 --- a/demux/demux_playlist.c +++ b/demux/demux_playlist.c @@ -202,7 +202,7 @@ static void pl_free_line(struct pl_parser *p, bstr line) static void pl_add(struct pl_parser *p, bstr entry) { char *s = bstrto0(NULL, entry); - playlist_add_file(p->pl, s); + playlist_append_file(p->pl, s); talloc_free(s); } @@ -266,7 +266,7 @@ ok: talloc_free(fn); e->title = talloc_steal(e, title); title = NULL; - playlist_add(p->pl, e); + playlist_insert_at(p->pl, e, NULL); } pl_free_line(p, line); line = pl_get_line(p); @@ -296,7 +296,7 @@ static int parse_ref_init(struct pl_parser *p) bstr burl = bstr0(p->s->url); if (bstr_eatstart0(&burl, "http://") && check_mimetype(p->s, mmsh_types)) { MP_INFO(p, "Redirecting to mmsh://\n"); - playlist_add_file(p->pl, talloc_asprintf(p, "mmsh://%.*s", BSTR_P(burl))); + playlist_append_file(p->pl, talloc_asprintf(p, "mmsh://%.*s", BSTR_P(burl))); return 0; } @@ -456,7 +456,7 @@ static bool scan_dir(struct pl_parser *p, char *path, scan_dir(p, file, dir_stack, num_dir_stack + 1); } else { - playlist_add_file(p->pl, dir_entries[n].path); + playlist_append_file(p->pl, dir_entries[n].path); } } diff --git a/demux/packet.h b/demux/packet.h index cd1183d..8087216 100644 --- a/demux/packet.h +++ b/demux/packet.h @@ -58,6 +58,12 @@ typedef struct demux_packet { struct mp_codec_params *codec; // set to non-NULL iff segmented is set double start, end; // set to non-NOPTS iff segmented is set + // subtitles only + bool animated; + bool seen; + int seen_pos; + double sub_duration; + // private struct demux_packet *next; struct AVPacket *avpacket; // keep the buffer allocation and sidedata diff --git a/demux/stheader.h b/demux/stheader.h index 1bc036d..4f33bbc 100644 --- a/demux/stheader.h +++ b/demux/stheader.h @@ -49,7 +49,7 @@ struct sh_stream { bool visual_impaired_track; // container flag bool hearing_impaired_track;// container flag bool image; // video stream is an image - bool still_image; // video stream contains still images + bool still_image; // video consists of multiple sparse still images int hls_bitrate; int program_id; @@ -72,6 +72,12 @@ struct mp_codec_params { // E.g. "h264" (usually corresponds to AVCodecDescriptor.name) const char *codec; + // Usually corresponds to AVCodecDescriptor.long_name + const char *codec_desc; + + // Corresponding codec profile + const char *codec_profile; + // Usually a FourCC, exact meaning depends on codec. unsigned int codec_tag; @@ -105,11 +111,13 @@ struct mp_codec_params { int disp_w, disp_h; // display size int rotate; // intended display rotation, in degrees, [0, 359] int stereo_mode; // mp_stereo3d_mode (0 if none/unknown) - struct mp_colorspace color; // colorspace info where available - struct mp_rect crop; // crop to be applied + struct pl_color_space color; // colorspace info where available + struct pl_color_repr repr; // color representaion info where available + struct mp_rect crop; // crop to be applied // STREAM_VIDEO + STREAM_AUDIO int bits_per_coded_sample; + char *format_name; // pixel format (video) or sample format (audio) // STREAM_SUB double frame_based; // timestamps are frame-based (and this is the diff --git a/etc/builtin.conf b/etc/builtin.conf index 7bfbace..ec0a485 100644 --- a/etc/builtin.conf +++ b/etc/builtin.conf @@ -26,7 +26,7 @@ input-terminal=no osc=no input-default-bindings=no input-vo-keyboard=no -# OSX/Cocoa global input hooks +# macOS global input hooks input-media-keys=no [encoding] @@ -54,7 +54,6 @@ allow-delayed-peak-detect=yes scale=ewa_lanczossharp hdr-peak-percentile=99.995 hdr-contrast-recovery=0.30 -deband=yes # Deprecated alias [gpu-hq] diff --git a/etc/input.conf b/etc/input.conf index 0b0e6da..000f7ae 100644 --- a/etc/input.conf +++ b/etc/input.conf @@ -65,6 +65,8 @@ #ZOOMIN add video-zoom 0.1 # zoom in #Alt+- add video-zoom -0.1 # zoom out #ZOOMOUT add video-zoom -0.1 # zoom out +#Ctrl+WHEEL_UP add video-zoom 0.1 # zoom in +#Ctrl+WHEEL_DOWN add video-zoom -0.1 # zoom out #Alt+BS set video-zoom 0 ; set video-pan-x 0 ; set video-pan-y 0 # reset zoom and pan settings #PGUP add chapter 1 # seek to the next chapter #PGDWN add chapter -1 # seek to the previous chapter @@ -118,7 +120,8 @@ #Alt+0 set current-window-scale 0.5 # halve the window size #Alt+1 set current-window-scale 1.0 # reset the window size #Alt+2 set current-window-scale 2.0 # double the window size -#d cycle deinterlace # toggle the deinterlacing filter +#b cycle deband # toggle the debanding filter +#d cycle deinterlace # cycle the deinterlacing filter #r add sub-pos -1 # move subtitles up #R add sub-pos +1 # move subtitles down #t add sub-pos +1 # move subtitles down diff --git a/etc/mpv.bash-completion b/etc/mpv.bash-completion index d5d504a..4892d0e 100644 --- a/etc/mpv.bash-completion +++ b/etc/mpv.bash-completion @@ -17,12 +17,17 @@ # License along with mpv. If not, see <http://www.gnu.org/licenses/>. # -# Cache all the mpv options -_mpv_options=$(mpv --no-config --list-options) +_mpv_options() +{ + if [ -z ${_mpv_options_cache+x} ]; then + _mpv_options_cache=$(mpv --no-config --list-options) + fi + echo "$_mpv_options_cache" +} _mpv_get_args() { - local doc=$(echo "$_mpv_options" | grep -E "^\\s*$1\\s") + local doc=$(_mpv_options | grep -E "^\\s*$1\\s") local partial="$2" local type=$(echo "$doc" | awk '{print $2;}') @@ -81,10 +86,26 @@ _mpv_get_args() # This regex detects special options where we don't want an '=' appended _mpv_special_regex='\s(Flag.*\[not in config files\]|Print)' _mpv_skip_regex='\sremoved \[deprecated\]' -_mpv_regular_options=($(echo "$_mpv_options" | grep -vE "$_mpv_skip_regex" | \ - grep -vE "$_mpv_special_regex" | awk '{print "\\"$1;}' | grep '\--')) -_mpv_special_options=($(echo "$_mpv_options" | grep -vE "$_mpv_skip_regex" | \ - grep -E "$_mpv_special_regex" | awk '{print "\\"$1;}' | grep '\--')) + +_mpv_regular_options() +{ + if [ -z ${_mpv_regular_options_cache+x} ]; then + _mpv_regular_options_cache=($(_mpv_options | grep -vE "$_mpv_skip_regex" | \ + grep -vE "$_mpv_special_regex" | awk '{print "\\"$1;}' | grep '\--')) + _mpv_regular_options_cache="${_mpv_regular_options_cache[*]}" + fi + echo "$_mpv_regular_options_cache" +} + +_mpv_special_options() +{ + if [ -z ${_mpv_special_options_cache+x} ]; then + _mpv_special_options_cache=($(_mpv_options | grep -vE "$_mpv_skip_regex" | \ + grep -E "$_mpv_special_regex" | awk '{print "\\"$1;}' | grep '\--')) + _mpv_special_options_cache="${_mpv_special_options_cache[*]}" + fi + echo "$_mpv_special_options_cache" +} _mpv() { @@ -106,9 +127,9 @@ _mpv() else case $cur in -*) - COMPREPLY=($(compgen -W "${_mpv_regular_options[*]}" -S '=' -- "${cur}")) + COMPREPLY=($(compgen -W "$(_mpv_regular_options)" -S '=' -- "${cur}")) local normal_count=${#COMPREPLY[@]} - COMPREPLY+=($(compgen -W "${_mpv_special_options[*]}" -- "${cur}")) + COMPREPLY+=($(compgen -W "$(_mpv_special_options)" -- "${cur}")) if [ $normal_count -gt 0 -o ${#COMPREPLY[@]} -gt 1 ]; then compopt -o nospace mpv fi diff --git a/etc/mpv.desktop b/etc/mpv.desktop index db71520..5a2b19b 100644 --- a/etc/mpv.desktop +++ b/etc/mpv.desktop @@ -5,6 +5,7 @@ Name[ca]=Reproductor multimèdia mpv Name[cs]=mpv přehrávač Name[da]=mpv-medieafspiller Name[fr]=Lecteur multimédia mpv +Name[it]=Lettore multimediale mpv Name[ja]=mpv メディアプレイヤー Name[pl]=Odtwarzacz mpv Name[ru]=Проигрыватель mpv @@ -15,6 +16,7 @@ GenericName=Multimedia player GenericName[cs]=Multimediální přehrávač GenericName[da]=Multimedieafspiller GenericName[fr]=Lecteur multimédia +GenericName[it]=Lettore multimediale GenericName[ja]=マルチメディアプレイヤー GenericName[ru]=Мультимедийный проигрыватель GenericName[tr]=Çoklu ortam oynatıcı @@ -28,7 +30,7 @@ Comment[de]=Filme und Musik abspielen Comment[es]=Reproduzca vídeos y canciones Comment[fr]=Lire des vidéos et des musiques Comment[ja]=映画や音楽を再生する -Comment[it]=Lettore multimediale +Comment[it]=Riproduci video e canzoni Comment[pl]=Odtwarzaj filmy i muzykę Comment[ru]=Воспроизведение фильмов и музыки Comment[tr]=Filmleri ve şarkıları oynatın @@ -39,6 +41,7 @@ TryExec=mpv Exec=mpv --player-operation-mode=pseudo-gui -- %U Terminal=false Categories=AudioVideo;Audio;Video;Player;TV; -MimeType=application/ogg;application/x-ogg;application/mxf;application/sdp;application/smil;application/x-smil;application/streamingmedia;application/x-streamingmedia;application/vnd.rn-realmedia;application/vnd.rn-realmedia-vbr;audio/aac;audio/x-aac;audio/vnd.dolby.heaac.1;audio/vnd.dolby.heaac.2;audio/aiff;audio/x-aiff;audio/m4a;audio/x-m4a;application/x-extension-m4a;audio/mp1;audio/x-mp1;audio/mp2;audio/x-mp2;audio/mp3;audio/x-mp3;audio/mpeg;audio/mpeg2;audio/mpeg3;audio/mpegurl;audio/x-mpegurl;audio/mpg;audio/x-mpg;audio/rn-mpeg;audio/musepack;audio/x-musepack;audio/ogg;audio/scpls;audio/x-scpls;audio/vnd.rn-realaudio;audio/wav;audio/x-pn-wav;audio/x-pn-windows-pcm;audio/x-realaudio;audio/x-pn-realaudio;audio/x-ms-wma;audio/x-pls;audio/x-wav;video/mpeg;video/x-mpeg2;video/x-mpeg3;video/mp4v-es;video/x-m4v;video/mp4;application/x-extension-mp4;video/divx;video/vnd.divx;video/msvideo;video/x-msvideo;video/ogg;video/quicktime;video/vnd.rn-realvideo;video/x-ms-afs;video/x-ms-asf;audio/x-ms-asf;application/vnd.ms-asf;video/x-ms-wmv;video/x-ms-wmx;video/x-ms-wvxvideo;video/x-avi;video/avi;video/x-flic;video/fli;video/x-flc;video/flv;video/x-flv;video/x-theora;video/x-theora+ogg;video/x-matroska;video/mkv;audio/x-matroska;application/x-matroska;video/webm;audio/webm;audio/vorbis;audio/x-vorbis;audio/x-vorbis+ogg;video/x-ogm;video/x-ogm+ogg;application/x-ogm;application/x-ogm-audio;application/x-ogm-video;application/x-shorten;audio/x-shorten;audio/x-ape;audio/x-wavpack;audio/x-tta;audio/AMR;audio/ac3;audio/eac3;audio/amr-wb;video/mp2t;audio/flac;audio/mp4;application/x-mpegurl;video/vnd.mpegurl;application/vnd.apple.mpegurl;audio/x-pn-au;video/3gp;video/3gpp;video/3gpp2;audio/3gpp;audio/3gpp2;video/dv;audio/dv;audio/opus;audio/vnd.dts;audio/vnd.dts.hd;audio/x-adpcm;application/x-cue;audio/m3u; +MimeType=application/ogg;application/x-ogg;application/mxf;application/sdp;application/smil;application/x-smil;application/streamingmedia;application/x-streamingmedia;application/vnd.rn-realmedia;application/vnd.rn-realmedia-vbr;audio/aac;audio/x-aac;audio/vnd.dolby.heaac.1;audio/vnd.dolby.heaac.2;audio/aiff;audio/x-aiff;audio/m4a;audio/x-m4a;application/x-extension-m4a;audio/mp1;audio/x-mp1;audio/mp2;audio/x-mp2;audio/mp3;audio/x-mp3;audio/mpeg;audio/mpeg2;audio/mpeg3;audio/mpegurl;audio/x-mpegurl;audio/mpg;audio/x-mpg;audio/rn-mpeg;audio/musepack;audio/x-musepack;audio/ogg;audio/scpls;audio/x-scpls;audio/vnd.rn-realaudio;audio/wav;audio/x-pn-wav;audio/x-pn-windows-pcm;audio/x-realaudio;audio/x-pn-realaudio;audio/x-ms-wma;audio/x-pls;audio/x-wav;video/mpeg;video/x-mpeg2;video/x-mpeg3;video/mp4v-es;video/x-m4v;video/mp4;application/x-extension-mp4;video/divx;video/vnd.divx;video/msvideo;video/x-msvideo;video/ogg;video/quicktime;video/vnd.rn-realvideo;video/x-ms-afs;video/x-ms-asf;audio/x-ms-asf;application/vnd.ms-asf;video/x-ms-wmv;video/x-ms-wmx;video/x-ms-wvxvideo;video/x-avi;video/avi;video/x-flic;video/fli;video/x-flc;video/flv;video/x-flv;video/x-theora;video/x-theora+ogg;video/x-matroska;video/mkv;audio/x-matroska;application/x-matroska;video/webm;audio/webm;audio/vorbis;audio/x-vorbis;audio/x-vorbis+ogg;video/x-ogm;video/x-ogm+ogg;application/x-ogm;application/x-ogm-audio;application/x-ogm-video;application/x-shorten;audio/x-shorten;audio/x-ape;audio/x-wavpack;audio/x-tta;audio/AMR;audio/ac3;audio/eac3;audio/amr-wb;video/mp2t;audio/flac;audio/mp4;application/x-mpegurl;video/vnd.mpegurl;application/vnd.apple.mpegurl;audio/x-pn-au;video/3gp;video/3gpp;video/3gpp2;audio/3gpp;audio/3gpp2;video/dv;audio/dv;audio/opus;audio/vnd.dts;audio/vnd.dts.hd;audio/x-adpcm;application/x-cue;audio/m3u;audio/vnd.wave;video/vnd.avi; X-KDE-Protocols=ftp,http,https,mms,rtmp,rtsp,sftp,smb,srt,rist,webdav,webdavs StartupWMClass=mpv +Keywords=mpv;media;player;video;audio;tv; diff --git a/filters/f_auto_filters.c b/filters/f_auto_filters.c index c8b31f6..6fa38b9 100644 --- a/filters/f_auto_filters.c +++ b/filters/f_auto_filters.c @@ -6,6 +6,7 @@ #include "common/msg.h" #include "options/m_config.h" #include "options/options.h" +#include "video/filter/refqueue.h" #include "video/mp_image.h" #include "video/mp_image_pool.h" @@ -21,7 +22,7 @@ struct deint_priv { struct mp_subfilter sub; int prev_imgfmt; - int prev_setting; + bool deinterlace_active; struct m_config_cache *opts; }; @@ -45,15 +46,18 @@ static void deint_process(struct mp_filter *f) return; } + struct mp_image *img = frame.data; + bool interlaced = img->fields & MP_IMGFIELD_INTERLACED; + m_config_cache_update(p->opts); struct filter_opts *opts = p->opts->opts; + bool should_deinterlace = (opts->deinterlace == -1 && interlaced) || + opts->deinterlace == 1; - if (!opts->deinterlace) + if (!should_deinterlace) mp_subfilter_destroy(&p->sub); - struct mp_image *img = frame.data; - - if (img->imgfmt == p->prev_imgfmt && p->prev_setting == opts->deinterlace) { + if (img->imgfmt == p->prev_imgfmt && p->deinterlace_active == should_deinterlace) { mp_subfilter_continue(&p->sub); return; } @@ -64,33 +68,48 @@ static void deint_process(struct mp_filter *f) assert(!p->sub.filter); p->prev_imgfmt = img->imgfmt; - p->prev_setting = opts->deinterlace; - if (!p->prev_setting) { + p->deinterlace_active = should_deinterlace; + if (!p->deinterlace_active) { mp_subfilter_continue(&p->sub); return; } + char *field_parity; + switch (opts->field_parity) { + case MP_FIELD_PARITY_TFF: + field_parity = "tff"; + break; + case MP_FIELD_PARITY_BFF: + field_parity = "bff"; + break; + default: + field_parity = "auto"; + } + bool has_filter = true; if (img->imgfmt == IMGFMT_VDPAU) { - char *args[] = {"deint", "yes", NULL}; + char *args[] = {"deint", "yes", + "parity", field_parity, NULL}; p->sub.filter = mp_create_user_filter(f, MP_OUTPUT_CHAIN_VIDEO, "vdpaupp", args); } else if (img->imgfmt == IMGFMT_D3D11) { + char *args[] = {"parity", field_parity, NULL}; p->sub.filter = - mp_create_user_filter(f, MP_OUTPUT_CHAIN_VIDEO, "d3d11vpp", NULL); + mp_create_user_filter(f, MP_OUTPUT_CHAIN_VIDEO, "d3d11vpp", args); } else if (img->imgfmt == IMGFMT_CUDA) { - char *args[] = {"mode", "send_field", NULL}; + char *args[] = {"mode", "send_field", + "parity", field_parity, NULL}; p->sub.filter = - mp_create_user_filter(f, MP_OUTPUT_CHAIN_VIDEO, "yadif_cuda", args); -#if HAVE_VULKAN_INTEROP + mp_create_user_filter(f, MP_OUTPUT_CHAIN_VIDEO, "bwdif_cuda", args); } else if (img->imgfmt == IMGFMT_VULKAN) { - char *args[] = {"mode", "send_field", NULL}; + char *args[] = {"mode", "send_field", + "parity", field_parity, NULL}; p->sub.filter = mp_create_user_filter(f, MP_OUTPUT_CHAIN_VIDEO, "bwdif_vulkan", args); -#endif } else if (img->imgfmt == IMGFMT_VAAPI) { char *args[] = {"deint", "motion-adaptive", - "interlaced-only", "yes", NULL}; + "interlaced-only", "yes", + "parity", field_parity, NULL}; p->sub.filter = mp_create_user_filter(f, MP_OUTPUT_CHAIN_VIDEO, "vavpp", args); } else { @@ -107,7 +126,7 @@ static void deint_process(struct mp_filter *f) struct mp_autoconvert *ac = mp_autoconvert_create(subf); if (ac) { filters[0] = ac->f; - // We know vf_yadif does not support hw inputs. + // We know vf_bwdif does not support hw inputs. mp_autoconvert_add_all_sw_imgfmts(ac); if (!mp_autoconvert_probe_input_video(ac, img)) { @@ -119,9 +138,10 @@ static void deint_process(struct mp_filter *f) } } - char *args[] = {"mode", "send_field", NULL}; + char *args[] = {"mode", "send_field", + "parity", field_parity, NULL}; filters[1] = - mp_create_user_filter(subf, MP_OUTPUT_CHAIN_VIDEO, "yadif", args); + mp_create_user_filter(subf, MP_OUTPUT_CHAIN_VIDEO, "bwdif", args); mp_chain_filters(subf->ppins[0], subf->ppins[1], filters, 2); p->sub.filter = subf; @@ -165,6 +185,12 @@ static const struct mp_filter_info deint_filter = { .destroy = deint_destroy, }; +bool mp_deint_active(struct mp_filter *f) +{ + struct deint_priv *p = f->priv; + return p->deinterlace_active; +} + struct mp_filter *mp_deint_create(struct mp_filter *parent) { struct mp_filter *f = mp_filter_create(parent, &deint_filter); diff --git a/filters/f_auto_filters.h b/filters/f_auto_filters.h index f315084..f926f6e 100644 --- a/filters/f_auto_filters.h +++ b/filters/f_auto_filters.h @@ -11,3 +11,5 @@ struct mp_filter *mp_autorotate_create(struct mp_filter *parent); // Insert a filter that inserts scaletempo2 depending on speed settings. struct mp_filter *mp_autoaspeed_create(struct mp_filter *parent); + +bool mp_deint_active(struct mp_filter *parent); diff --git a/filters/f_autoconvert.c b/filters/f_autoconvert.c index dcd5ea2..e045d74 100644 --- a/filters/f_autoconvert.c +++ b/filters/f_autoconvert.c @@ -157,7 +157,7 @@ static bool build_image_converter(struct mp_autoconvert *c, struct mp_log *log, */ if (samefmt && samesubffmt) { if (p->imgparams_set) { - if (!mp_image_params_equal(&p->imgparams, &img->params)) + if (!mp_image_params_static_equal(&p->imgparams, &img->params)) break; } return true; @@ -477,7 +477,7 @@ cont: mp_subfilter_continue(&p->sub); } -static void process(struct mp_filter *f) +static void autoconvert_process(struct mp_filter *f) { struct priv *p = f->priv; @@ -508,7 +508,7 @@ void mp_autoconvert_format_change_continue(struct mp_autoconvert *c) } } -static bool command(struct mp_filter *f, struct mp_filter_command *cmd) +static bool autoconvert_command(struct mp_filter *f, struct mp_filter_command *cmd) { struct priv *p = f->priv; @@ -529,7 +529,7 @@ static bool command(struct mp_filter *f, struct mp_filter_command *cmd) return false; } -static void reset(struct mp_filter *f) +static void autoconvert_reset(struct mp_filter *f) { struct priv *p = f->priv; @@ -539,7 +539,7 @@ static void reset(struct mp_filter *f) p->format_change_blocked = false; } -static void destroy(struct mp_filter *f) +static void autoconvert_destroy(struct mp_filter *f) { struct priv *p = f->priv; @@ -550,10 +550,10 @@ static void destroy(struct mp_filter *f) static const struct mp_filter_info autoconvert_filter = { .name = "autoconvert", .priv_size = sizeof(struct priv), - .process = process, - .command = command, - .reset = reset, - .destroy = destroy, + .process = autoconvert_process, + .command = autoconvert_command, + .reset = autoconvert_reset, + .destroy = autoconvert_destroy, }; struct mp_autoconvert *mp_autoconvert_create(struct mp_filter *parent) diff --git a/filters/f_decoder_wrapper.c b/filters/f_decoder_wrapper.c index 76b9707..ba86762 100644 --- a/filters/f_decoder_wrapper.c +++ b/filters/f_decoder_wrapper.c @@ -551,31 +551,36 @@ void mp_decoder_wrapper_set_play_dir(struct mp_decoder_wrapper *d, int dir) } static void fix_image_params(struct priv *p, - struct mp_image_params *params) + struct mp_image_params *params, + bool quiet) { struct mp_image_params m = *params; struct mp_codec_params *c = p->codec; struct dec_wrapper_opts *opts = p->opts; - MP_VERBOSE(p, "Decoder format: %s\n", mp_image_params_to_str(params)); + if (!quiet) + MP_VERBOSE(p, "Decoder format: %s\n", mp_image_params_to_str(params)); p->dec_format = *params; // While mp_image_params normally always have to have d_w/d_h set, the // decoder signals unknown bitstream aspect ratio with both set to 0. bool use_container = true; if (opts->aspect_method == 1 && m.p_w > 0 && m.p_h > 0) { - MP_VERBOSE(p, "Using bitstream aspect ratio.\n"); + if (!quiet) + MP_VERBOSE(p, "Using bitstream aspect ratio.\n"); use_container = false; } if (use_container && c->par_w > 0 && c->par_h) { - MP_VERBOSE(p, "Using container aspect ratio.\n"); + if (!quiet) + MP_VERBOSE(p, "Using container aspect ratio.\n"); m.p_w = c->par_w; m.p_h = c->par_h; } if (opts->movie_aspect >= 0) { - MP_VERBOSE(p, "Forcing user-set aspect ratio.\n"); + if (!quiet) + MP_VERBOSE(p, "Forcing user-set aspect ratio.\n"); if (opts->movie_aspect == 0) { m.p_w = m.p_h = 1; } else { @@ -618,7 +623,8 @@ static void fix_image_params(struct priv *p, m.rotate = (m.rotate + opts->video_rotate) % 360; } - mp_colorspace_merge(&m.color, &c->color); + pl_color_space_merge(&m.color, &c->color); + pl_color_repr_merge(&m.repr, &c->repr); // Guess missing colorspace fields from metadata. This guarantees all // fields are at least set to legal values afterwards. @@ -818,8 +824,10 @@ static void process_output_frame(struct priv *p, struct mp_frame frame) correct_video_pts(p, mpi); - if (!mp_image_params_equal(&p->last_format, &mpi->params)) - fix_image_params(p, &mpi->params); + if (!mp_image_params_equal(&p->last_format, &mpi->params)) { + fix_image_params(p, &mpi->params, + mp_image_params_static_equal(&p->last_format, &mpi->params)); + } mpi->params = p->fixed_format; mpi->nominal_fps = p->fps; diff --git a/filters/f_demux_in.c b/filters/f_demux_in.c index 995bb00..61f4683 100644 --- a/filters/f_demux_in.c +++ b/filters/f_demux_in.c @@ -17,7 +17,7 @@ static void wakeup(void *ctx) mp_filter_wakeup(f); } -static void process(struct mp_filter *f) +static void demux_process(struct mp_filter *f) { struct priv *p = f->priv; @@ -45,14 +45,14 @@ static void process(struct mp_filter *f) mp_pin_in_write(f->ppins[0], frame); } -static void reset(struct mp_filter *f) +static void demux_reset(struct mp_filter *f) { struct priv *p = f->priv; p->eof_returned = false; } -static void destroy(struct mp_filter *f) +static void demux_destroy(struct mp_filter *f) { struct priv *p = f->priv; @@ -62,9 +62,9 @@ static void destroy(struct mp_filter *f) static const struct mp_filter_info demux_filter = { .name = "demux_in", .priv_size = sizeof(struct priv), - .process = process, - .reset = reset, - .destroy = destroy, + .process = demux_process, + .reset = demux_reset, + .destroy = demux_destroy, }; struct mp_filter *mp_demux_in_create(struct mp_filter *parent, diff --git a/filters/f_hwtransfer.c b/filters/f_hwtransfer.c index 94359bf..77dfaff 100644 --- a/filters/f_hwtransfer.c +++ b/filters/f_hwtransfer.c @@ -56,12 +56,10 @@ struct hwmap_pairs { // We cannot discover which pairs of hardware formats need to use hwmap to // convert between the formats, so we need a lookup table. static const struct hwmap_pairs hwmap_pairs[] = { -#if HAVE_VULKAN_INTEROP { .first_fmt = IMGFMT_VAAPI, .second_fmt = IMGFMT_VULKAN, }, -#endif { .first_fmt = IMGFMT_DRMPRIME, .second_fmt = IMGFMT_VAAPI, @@ -94,10 +92,12 @@ static bool select_format(struct priv *p, int input_fmt, if (!input_fmt) return false; - // If the input format is a hw format, then we shouldn't be doing this - // format selection here at all. + // If the input format is a hw format, then we won't be doing any sort of + // conversion. Just assume that it will pass-through successfully. if (IMGFMT_IS_HWACCEL(input_fmt)) { - return false; + *out_hw_input_fmt = input_fmt; + *out_hw_output_fmt = input_fmt; + return true; } // If there is no capability to do uploads or conversions during uploads, @@ -143,7 +143,7 @@ static bool select_format(struct priv *p, int input_fmt, return true; } -static void process(struct mp_filter *f) +static void hwupload_process(struct mp_filter *f) { struct priv *p = f->priv; @@ -236,7 +236,7 @@ error: mp_filter_internal_mark_failed(f); } -static void destroy(struct mp_filter *f) +static void hwupload_destroy(struct mp_filter *f) { struct priv *p = f->priv; @@ -247,8 +247,8 @@ static void destroy(struct mp_filter *f) static const struct mp_filter_info hwupload_filter = { .name = "hwupload", .priv_size = sizeof(struct priv), - .process = process, - .destroy = destroy, + .process = hwupload_process, + .destroy = hwupload_destroy, }; // The VO layer might have restricted format support. It might actually diff --git a/filters/f_lavfi.c b/filters/f_lavfi.c index fe7d3e4..afc9f2d 100644 --- a/filters/f_lavfi.c +++ b/filters/f_lavfi.c @@ -32,6 +32,7 @@ #include <libavfilter/avfilter.h> #include <libavfilter/buffersink.h> #include <libavfilter/buffersrc.h> +#include <libplacebo/utils/libav.h> #include "config.h" @@ -492,6 +493,10 @@ static bool init_pads(struct lavfi *c) params->sample_aspect_ratio.den = fmt->params.p_h; params->hw_frames_ctx = fmt->hwctx; params->frame_rate = av_d2q(fmt->nominal_fps, 1000000); +#if LIBAVFILTER_VERSION_INT >= AV_VERSION_INT(9, 16, 100) + params->color_space = pl_system_to_av(fmt->params.repr.sys); + params->color_range = pl_levels_to_av(fmt->params.repr.levels); +#endif filter_name = "buffer"; } else { MP_ASSERT_UNREACHABLE(); @@ -1029,7 +1034,11 @@ static const char *get_avopt_type_name(enum AVOptionType type) case AV_OPT_TYPE_VIDEO_RATE: return "fps"; case AV_OPT_TYPE_DURATION: return "duration"; case AV_OPT_TYPE_COLOR: return "color"; - case AV_OPT_TYPE_CHANNEL_LAYOUT: return "channellayout"; +#if LIBAVUTIL_VERSION_MAJOR < 59 + case AV_OPT_TYPE_CHANNEL_LAYOUT: return "ch_layout"; +#else + case AV_OPT_TYPE_CHLAYOUT: return "ch_layout"; +#endif case AV_OPT_TYPE_BOOL: return "bool"; case AV_OPT_TYPE_CONST: // fallthrough default: diff --git a/filters/f_output_chain.c b/filters/f_output_chain.c index 2d4dcba..cbccb3a 100644 --- a/filters/f_output_chain.c +++ b/filters/f_output_chain.c @@ -100,27 +100,29 @@ static void check_in_format_change(struct mp_user_filter *u, struct mp_image *img = frame.data; if (!mp_image_params_equal(&img->params, &u->last_in_vformat)) { - MP_VERBOSE(p, "[%s] %s\n", u->name, - mp_image_params_to_str(&img->params)); - u->last_in_vformat = img->params; - if (u == p->input) { p->public.input_params = img->params; } else if (u == p->output) { p->public.output_params = img->params; } - // Unfortunately there's no good place to update these. - // But a common case is enabling HW decoding, which - // might init some support of them in the VO, and update - // the VO's format list. - // - // But as this is only relevant to the "convert" filter, don't - // do this for the other filters as it is wasted work. - if (strcmp(u->name, "convert") == 0) - update_output_caps(p); - - p->public.reconfig_happened = true; + if (!mp_image_params_static_equal(&img->params, &u->last_in_vformat)) { + MP_VERBOSE(p, "[%s] %s\n", u->name, + mp_image_params_to_str(&img->params)); + + // Unfortunately there's no good place to update these. + // But a common case is enabling HW decoding, which + // might init some support of them in the VO, and update + // the VO's format list. + // + // But as this is only relevant to the "convert" filter, don't + // do this for the other filters as it is wasted work. + if (strcmp(u->name, "convert") == 0) + update_output_caps(p); + + p->public.reconfig_happened = true; + } + u->last_in_vformat = img->params; } } @@ -143,7 +145,7 @@ static void check_in_format_change(struct mp_user_filter *u, } } -static void process_user(struct mp_filter *f) +static void user_wrapper_process(struct mp_filter *f) { struct mp_user_filter *u = f->priv; struct chain *p = u->p; @@ -210,7 +212,7 @@ static void process_user(struct mp_filter *f) } } -static void reset_user(struct mp_filter *f) +static void user_wrapper_reset(struct mp_filter *f) { struct mp_user_filter *u = f->priv; @@ -218,7 +220,7 @@ static void reset_user(struct mp_filter *f) u->last_in_pts = u->last_out_pts = MP_NOPTS_VALUE; } -static void destroy_user(struct mp_filter *f) +static void user_wrapper_destroy(struct mp_filter *f) { struct mp_user_filter *u = f->priv; @@ -231,9 +233,9 @@ static void destroy_user(struct mp_filter *f) static const struct mp_filter_info user_wrapper_filter = { .name = "user_filter_wrapper", .priv_size = sizeof(struct mp_user_filter), - .process = process_user, - .reset = reset_user, - .destroy = destroy_user, + .process = user_wrapper_process, + .reset = user_wrapper_reset, + .destroy = user_wrapper_destroy, }; static struct mp_user_filter *create_wrapper_filter(struct chain *p) @@ -280,7 +282,7 @@ static void relink_filter_list(struct chain *p) } } -static void process(struct mp_filter *f) +static void output_chain_process(struct mp_filter *f) { struct chain *p = f->priv; @@ -309,7 +311,7 @@ static void process(struct mp_filter *f) } } -static void reset(struct mp_filter *f) +static void output_chain_reset(struct mp_filter *f) { struct chain *p = f->priv; @@ -339,17 +341,17 @@ void mp_output_chain_reset_harder(struct mp_output_chain *c) } } -static void destroy(struct mp_filter *f) +static void output_chain_destroy(struct mp_filter *f) { - reset(f); + output_chain_reset(f); } static const struct mp_filter_info output_chain_filter = { .name = "output_chain", .priv_size = sizeof(struct chain), - .process = process, - .reset = reset, - .destroy = destroy, + .process = output_chain_process, + .reset = output_chain_reset, + .destroy = output_chain_destroy, }; static double get_display_fps(struct mp_stream_info *i) @@ -518,6 +520,17 @@ double mp_output_get_measured_total_delay(struct mp_output_chain *c) return delay; } +bool mp_output_chain_deinterlace_active(struct mp_output_chain *c) +{ + struct chain *p = c->f->priv; + for (int n = 0; n < p->num_all_filters; n++) { + struct mp_user_filter *u = p->all_filters[n]; + if (strcmp(u->name, "userdeint") == 0) + return mp_deint_active(u->f); + } + return false; +} + bool mp_output_chain_update_filters(struct mp_output_chain *c, struct m_obj_settings *list) { @@ -616,7 +629,7 @@ bool mp_output_chain_update_filters(struct mp_output_chain *c, error: for (int n = 0; n < num_add; n++) - talloc_free(add[n]); + talloc_free(add[n]->wrapper); talloc_free(add); talloc_free(used); return false; @@ -723,7 +736,7 @@ struct mp_output_chain *mp_output_chain_create(struct mp_filter *parent, relink_filter_list(p); - reset(f); + output_chain_reset(f); return c; } diff --git a/filters/f_output_chain.h b/filters/f_output_chain.h index f06769c..f313f9a 100644 --- a/filters/f_output_chain.h +++ b/filters/f_output_chain.h @@ -85,3 +85,6 @@ void mp_output_chain_set_audio_speed(struct mp_output_chain *p, // due to the change. // Makes sense for audio only. double mp_output_get_measured_total_delay(struct mp_output_chain *p); + +// Check if deinterlace user filter is inserted +bool mp_output_chain_deinterlace_active(struct mp_output_chain *p); diff --git a/filters/f_swresample.c b/filters/f_swresample.c index 8cb687d..0d88a07 100644 --- a/filters/f_swresample.c +++ b/filters/f_swresample.c @@ -23,6 +23,7 @@ #include <libswresample/swresample.h> #include "audio/aframe.h" +#include "audio/chmap_avchannel.h" #include "audio/fmt-conversion.h" #include "audio/format.h" #include "common/common.h" @@ -269,14 +270,28 @@ static bool configure_lavrr(struct priv *p, bool verbose) out_ch_layout = fudge_layout_conversion(p, in_ch_layout, out_ch_layout); +#if HAVE_AV_CHANNEL_LAYOUT // Real conversion; output is input to avrctx_out. + AVChannelLayout in_layout, out_layout; + mp_chmap_to_av_layout(&in_layout, &in_lavc); + mp_chmap_to_av_layout(&out_layout, &out_lavc); + av_opt_set_chlayout(p->avrctx, "in_chlayout", &in_layout, 0); + av_opt_set_chlayout(p->avrctx, "out_chlayout", &out_layout, 0); +#else av_opt_set_int(p->avrctx, "in_channel_layout", in_ch_layout, 0); av_opt_set_int(p->avrctx, "out_channel_layout", out_ch_layout, 0); +#endif av_opt_set_int(p->avrctx, "in_sample_rate", p->in_rate, 0); av_opt_set_int(p->avrctx, "out_sample_rate", p->out_rate, 0); av_opt_set_int(p->avrctx, "in_sample_fmt", in_samplefmt, 0); av_opt_set_int(p->avrctx, "out_sample_fmt", out_samplefmtp, 0); +#if HAVE_AV_CHANNEL_LAYOUT + AVChannelLayout fake_layout; + av_channel_layout_default(&fake_layout, map_out.num); + av_opt_set_chlayout(p->avrctx_out, "in_chlayout", &fake_layout, 0); + av_opt_set_chlayout(p->avrctx_out, "out_chlayout", &fake_layout, 0); +#else // Just needs the correct number of channels for deplanarization. struct mp_chmap fake_chmap; mp_chmap_set_unknown(&fake_chmap, map_out.num); @@ -285,6 +300,7 @@ static bool configure_lavrr(struct priv *p, bool verbose) goto error; av_opt_set_int(p->avrctx_out, "in_channel_layout", fake_out_ch_layout, 0); av_opt_set_int(p->avrctx_out, "out_channel_layout", fake_out_ch_layout, 0); +#endif av_opt_set_int(p->avrctx_out, "in_sample_fmt", out_samplefmtp, 0); av_opt_set_int(p->avrctx_out, "out_sample_fmt", out_samplefmt, 0); @@ -311,7 +327,7 @@ error: return false; } -static void reset(struct mp_filter *f) +static void swresample_reset(struct mp_filter *f) { struct priv *p = f->priv; @@ -457,7 +473,7 @@ error: return MP_NO_FRAME; } -static void process(struct mp_filter *f) +static void swresample_process(struct mp_filter *f) { struct priv *p = f->priv; @@ -618,7 +634,7 @@ double mp_swresample_get_delay(struct mp_swresample *s) return get_delay(p); } -static bool command(struct mp_filter *f, struct mp_filter_command *cmd) +static bool swresample_command(struct mp_filter *f, struct mp_filter_command *cmd) { struct priv *p = f->priv; @@ -630,7 +646,7 @@ static bool command(struct mp_filter *f, struct mp_filter_command *cmd) return false; } -static void destroy(struct mp_filter *f) +static void swresample_destroy(struct mp_filter *f) { struct priv *p = f->priv; @@ -641,10 +657,10 @@ static void destroy(struct mp_filter *f) static const struct mp_filter_info swresample_filter = { .name = "swresample", .priv_size = sizeof(struct priv), - .process = process, - .command = command, - .reset = reset, - .destroy = destroy, + .process = swresample_process, + .command = swresample_command, + .reset = swresample_reset, + .destroy = swresample_destroy, }; struct mp_swresample *mp_swresample_create(struct mp_filter *parent, diff --git a/filters/f_swscale.c b/filters/f_swscale.c index 4aca609..89fc2bb 100644 --- a/filters/f_swscale.c +++ b/filters/f_swscale.c @@ -68,7 +68,7 @@ bool mp_sws_supports_input(int imgfmt) return sws_isSupportedInput(imgfmt2pixfmt(imgfmt)); } -static void process(struct mp_filter *f) +static void sws_process(struct mp_filter *f) { struct mp_sws_filter *s = f->priv; @@ -130,7 +130,7 @@ error: static const struct mp_filter_info sws_filter = { .name = "swscale", .priv_size = sizeof(struct mp_sws_filter), - .process = process, + .process = sws_process, }; struct mp_sws_filter *mp_sws_filter_create(struct mp_filter *parent) diff --git a/filters/user_filters.c b/filters/user_filters.c index c879535..b5f29ad 100644 --- a/filters/user_filters.c +++ b/filters/user_filters.c @@ -94,7 +94,7 @@ const struct mp_user_filter_entry *vf_list[] = { #if HAVE_D3D_HWACCEL &vf_d3d11vpp, #endif -#if HAVE_EGL_HELPERS && HAVE_GL && HAVE_EGL +#if HAVE_GL && HAVE_EGL &vf_gpu, #endif }; diff --git a/input/cmd.h b/input/cmd.h index 1c9fb3e..3d4b15f 100644 --- a/input/cmd.h +++ b/input/cmd.h @@ -23,7 +23,7 @@ #include "misc/bstr.h" #include "options/m_option.h" -#define MP_CMD_DEF_MAX_ARGS 9 +#define MP_CMD_DEF_MAX_ARGS 11 #define MP_CMD_OPT_ARG M_OPT_OPTIONAL_PARAM struct mp_log; diff --git a/input/event.c b/input/event.c index 266e029..6c1e004 100644 --- a/input/event.c +++ b/input/event.c @@ -37,6 +37,21 @@ void mp_event_drop_files(struct input_ctx *ictx, int num_files, char **files, }; mp_input_run_cmd(ictx, cmd); } + } else if (action == DND_INSERT_NEXT) { + /* To insert the entries in the correct order, we iterate over them + backwards */ + for (int i = num_files - 1; i >= 0; i--) { + const char *cmd[] = { + "osd-auto", + "loadfile", + files[i], + /* Since we're inserting in reverse, wait til the final item + is added to start playing */ + (i > 0) ? "insert-next" : "insert-next-play", + NULL + }; + mp_input_run_cmd(ictx, cmd); + } } else { for (int i = 0; i < num_files; i++) { const char *cmd[] = { diff --git a/input/event.h b/input/event.h index 1e2149b..5f48bee 100644 --- a/input/event.h +++ b/input/event.h @@ -24,16 +24,17 @@ struct input_ctx; enum mp_dnd_action { DND_REPLACE, DND_APPEND, + DND_INSERT_NEXT, }; // Enqueue files for playback after drag and drop void mp_event_drop_files(struct input_ctx *ictx, int num_files, char **files, - enum mp_dnd_action append); + enum mp_dnd_action action); // Drop data in a specific format (identified by the mimetype). // Returns <0 on error, ==0 if data was ok but empty, >0 on success. int mp_event_drop_mime_data(struct input_ctx *ictx, const char *mime_type, - bstr data, enum mp_dnd_action append); + bstr data, enum mp_dnd_action action); // Many drag & drop APIs support multiple mime types, and this function returns // whether a type is preferred (higher integer score), or supported (scores diff --git a/input/input.c b/input/input.c index b8d12aa..9fd2a16 100644 --- a/input/input.c +++ b/input/input.c @@ -50,13 +50,13 @@ #include "common/common.h" #if HAVE_COCOA -#include "osdep/macosx_events.h" +#include "osdep/mac/app_bridge.h" #endif #define input_lock(ictx) mp_mutex_lock(&ictx->mutex) #define input_unlock(ictx) mp_mutex_unlock(&ictx->mutex) -#define MP_MAX_KEY_DOWN 4 +#define MP_MAX_KEY_DOWN 16 struct cmd_bind { int keys[MP_MAX_KEY_DOWN]; @@ -79,8 +79,6 @@ struct cmd_bind_section { #define MP_MAX_SOURCES 10 -#define MAX_ACTIVE_SECTIONS 50 - struct active_section { char *name; int flags; @@ -142,7 +140,7 @@ struct input_ctx { int num_sections; // List currently active command sections - struct active_section active_sections[MAX_ACTIVE_SECTIONS]; + struct active_section *active_sections; int num_active_sections; unsigned int mouse_event_counter; @@ -179,6 +177,7 @@ struct input_opts { bool vo_key_input; bool test; bool allow_win_drag; + bool preprocess_wheel; }; const struct m_sub_options input_config = { @@ -198,6 +197,7 @@ const struct m_sub_options input_config = { {"input-cursor", OPT_BOOL(enable_mouse_movements)}, {"input-vo-keyboard", OPT_BOOL(vo_key_input)}, {"input-media-keys", OPT_BOOL(use_media_keys)}, + {"input-preprocess-wheel", OPT_BOOL(preprocess_wheel)}, #if HAVE_SDL2_GAMEPAD {"input-gamepad", OPT_BOOL(use_gamepad)}, #endif @@ -217,6 +217,7 @@ const struct m_sub_options input_config = { .builtin_bindings = true, .vo_key_input = true, .allow_win_drag = true, + .preprocess_wheel = true, }, .change_flags = UPDATE_INPUT, }; @@ -731,18 +732,23 @@ static void mp_input_feed_key(struct input_ctx *ictx, int code, double scale, if (!force_mouse && opts->doubleclick_time && MP_KEY_IS_MOUSE_BTN_DBL(unmod)) return; int units = 1; - if (MP_KEY_IS_WHEEL(unmod) && !process_wheel(ictx, unmod, &scale, &units)) + if (MP_KEY_IS_WHEEL(unmod) && opts->preprocess_wheel && !process_wheel(ictx, unmod, &scale, &units)) return; interpret_key(ictx, code, scale, units); if (code & MP_KEY_STATE_DOWN) { code &= ~MP_KEY_STATE_DOWN; if (ictx->last_doubleclick_key_down == code && - now - ictx->last_doubleclick_time < opts->doubleclick_time / 1000.0) + now - ictx->last_doubleclick_time < opts->doubleclick_time / 1000.0 && + code >= MP_MBTN_LEFT && code <= MP_MBTN_RIGHT) { - if (code >= MP_MBTN_LEFT && code <= MP_MBTN_RIGHT) { - interpret_key(ictx, code - MP_MBTN_BASE + MP_MBTN_DBL_BASE, - 1, 1); - } + now = 0; + interpret_key(ictx, code - MP_MBTN_BASE + MP_MBTN_DBL_BASE, + 1, 1); + } else if (code == MP_MBTN_LEFT) { + // This is a mouse left botton down event which isn't part of a doubleclick. + // Initialize vo dragging in this case. + mp_cmd_t *cmd = mp_input_parse_cmd(ictx, bstr0("begin-vo-dragging"), "<internal>"); + mp_input_queue_cmd(ictx, cmd); } ictx->last_doubleclick_key_down = code; ictx->last_doubleclick_time = now; @@ -756,10 +762,12 @@ void mp_input_put_key(struct input_ctx *ictx, int code) input_unlock(ictx); } -void mp_input_put_key_artificial(struct input_ctx *ictx, int code) +void mp_input_put_key_artificial(struct input_ctx *ictx, int code, double value) { + if (value == 0.0) + return; input_lock(ictx); - mp_input_feed_key(ictx, code, 1, true); + mp_input_feed_key(ictx, code, value, true); input_unlock(ictx); } @@ -1007,20 +1015,16 @@ void mp_input_enable_section(struct input_ctx *ictx, char *name, int flags) MP_TRACE(ictx, "enable section '%s'\n", name); - if (ictx->num_active_sections < MAX_ACTIVE_SECTIONS) { - int top = ictx->num_active_sections; - if (!(flags & MP_INPUT_ON_TOP)) { - // insert before the first top entry - for (top = 0; top < ictx->num_active_sections; top++) { - if (ictx->active_sections[top].flags & MP_INPUT_ON_TOP) - break; - } - for (int n = ictx->num_active_sections; n > top; n--) - ictx->active_sections[n] = ictx->active_sections[n - 1]; + int top = ictx->num_active_sections; + if (!(flags & MP_INPUT_ON_TOP)) { + // insert before the first top entry + for (top = 0; top < ictx->num_active_sections; top++) { + if (ictx->active_sections[top].flags & MP_INPUT_ON_TOP) + break; } - ictx->active_sections[top] = (struct active_section){name, flags}; - ictx->num_active_sections++; } + MP_TARRAY_INSERT_AT(ictx, ictx->active_sections, ictx->num_active_sections, + top, (struct active_section){name, flags}); MP_TRACE(ictx, "active section stack:\n"); for (int n = 0; n < ictx->num_active_sections; n++) { @@ -1273,9 +1277,9 @@ static int parse_config(struct input_ctx *ictx, bool builtin, bstr data, return n_binds; } -static int parse_config_file(struct input_ctx *ictx, char *file, bool warn) +static bool parse_config_file(struct input_ctx *ictx, char *file) { - int r = 0; + bool r = false; void *tmp = talloc_new(NULL); stream_t *s = NULL; @@ -1292,7 +1296,7 @@ static int parse_config_file(struct input_ctx *ictx, char *file, bool warn) MP_VERBOSE(ictx, "Parsing input config file %s\n", file); int num = parse_config(ictx, false, data, file, NULL); MP_VERBOSE(ictx, "Input config file %s parsed: %d binds\n", file, num); - r = 1; + r = true; } else { MP_ERR(ictx, "Error reading input config file %s\n", file); } @@ -1317,6 +1321,7 @@ struct input_ctx *mp_input_init(struct mpv_global *global, .opts_cache = m_config_cache_alloc(ictx, global, &input_config), .wakeup_cb = wakeup_cb, .wakeup_ctx = wakeup_ctx, + .active_sections = talloc_array(ictx, struct active_section, 0), }; ictx->opts = ictx->opts_cache->opts; @@ -1373,13 +1378,13 @@ void mp_input_load_config(struct input_ctx *ictx) bool config_ok = false; if (ictx->opts->config_file && ictx->opts->config_file[0]) - config_ok = parse_config_file(ictx, ictx->opts->config_file, true); + config_ok = parse_config_file(ictx, ictx->opts->config_file); if (!config_ok) { // Try global conf dir void *tmp = talloc_new(NULL); char **files = mp_find_all_config_files(tmp, ictx->global, "input.conf"); for (int n = 0; files && files[n]; n++) - parse_config_file(ictx, files[n], false); + parse_config_file(ictx, files[n]); talloc_free(tmp); } @@ -1392,6 +1397,14 @@ void mp_input_load_config(struct input_ctx *ictx) input_unlock(ictx); } +bool mp_input_load_config_file(struct input_ctx *ictx, char *file) +{ + input_lock(ictx); + bool result = parse_config_file(ictx, file); + input_unlock(ictx); + return result; +} + static void clear_queue(struct cmd_queue *queue) { while (queue->first) { diff --git a/input/input.h b/input/input.h index 5b5e7a9..7f6707e 100644 --- a/input/input.h +++ b/input/input.h @@ -83,7 +83,8 @@ void mp_input_src_feed_cmd_text(struct mp_input_src *src, char *buf, size_t len) void mp_input_put_key(struct input_ctx *ictx, int code); // Like mp_input_put_key(), but ignore mouse disable option for mouse buttons. -void mp_input_put_key_artificial(struct input_ctx *ictx, int code); +// value can be used like with mp_input_put_wheel(), use 1 if not applicable. +void mp_input_put_key_artificial(struct input_ctx *ictx, int code, double value); // Like mp_input_put_key(), but process all UTF-8 characters in the given // string as key events. @@ -177,8 +178,12 @@ struct input_ctx *mp_input_init(struct mpv_global *global, void (*wakeup_cb)(void *ctx), void *wakeup_ctx); +// Load the configured input.conf files. void mp_input_load_config(struct input_ctx *ictx); +// Load a specific input.conf file. +bool mp_input_load_config_file(struct input_ctx *ictx, char *file); + void mp_input_update_opts(struct input_ctx *ictx); void mp_input_uninit(struct input_ctx *ictx); diff --git a/input/keycodes.c b/input/keycodes.c index bca9e17..c412191 100644 --- a/input/keycodes.c +++ b/input/keycodes.c @@ -173,7 +173,8 @@ static const struct key_name key_names[] = { { MP_KEY_CHANNEL_DOWN,"CHANNEL_DOWN" }, { MP_KEY_PLAYONLY, "PLAYONLY" }, { MP_KEY_PAUSEONLY, "PAUSEONLY" }, - { MP_KEY_BACK, "BACK" }, + { MP_KEY_GO_BACK, "GO_BACK" }, + { MP_KEY_GO_FORWARD, "GO_FORWARD" }, { MP_KEY_TOOLS, "TOOLS" }, { MP_KEY_ZOOMIN, "ZOOMIN" }, { MP_KEY_ZOOMOUT, "ZOOMOUT" }, diff --git a/input/keycodes.h b/input/keycodes.h index a5a746a..1a21a3c 100644 --- a/input/keycodes.h +++ b/input/keycodes.h @@ -82,10 +82,11 @@ #define MP_KEY_CHANNEL_DOWN (MP_KEY_MM_BASE+22) #define MP_KEY_PLAYONLY (MP_KEY_MM_BASE+23) #define MP_KEY_PAUSEONLY (MP_KEY_MM_BASE+24) -#define MP_KEY_BACK (MP_KEY_MM_BASE+25) -#define MP_KEY_TOOLS (MP_KEY_MM_BASE+26) -#define MP_KEY_ZOOMIN (MP_KEY_MM_BASE+27) -#define MP_KEY_ZOOMOUT (MP_KEY_MM_BASE+28) +#define MP_KEY_GO_BACK (MP_KEY_MM_BASE+25) +#define MP_KEY_GO_FORWARD (MP_KEY_MM_BASE+26) +#define MP_KEY_TOOLS (MP_KEY_MM_BASE+27) +#define MP_KEY_ZOOMIN (MP_KEY_MM_BASE+28) +#define MP_KEY_ZOOMOUT (MP_KEY_MM_BASE+29) /* Function keys */ #define MP_KEY_F (MP_KEY_BASE+0x40) diff --git a/input/sdl_gamepad.c b/input/sdl_gamepad.c index 790c945..b61f7c9 100644 --- a/input/sdl_gamepad.c +++ b/input/sdl_gamepad.c @@ -200,7 +200,9 @@ static void remove_gamepad(struct mp_input_src *src, int id) static void read_gamepad_thread(struct mp_input_src *src, void *param) { +#if SDL_VERSION_ATLEAST(2, 0, 14) SDL_SetHint(SDL_HINT_JOYSTICK_THREAD, "1"); +#endif if (SDL_WasInit(SDL_INIT_EVENTS)) { MP_ERR(src, "Another component is using SDL already.\n"); diff --git a/libmpv/client.h b/libmpv/client.h index 0a548a5..8eb5e05 100644 --- a/libmpv/client.h +++ b/libmpv/client.h @@ -181,7 +181,7 @@ extern "C" { * filenames in the local 8 bit encoding. It does not use fopen() either; * it uses _wfopen(). * - * On OS X, filenames and other strings taken/returned by libmpv can have + * On macOS, filenames and other strings taken/returned by libmpv can have * inconsistent unicode normalization. This can sometimes lead to problems. * You have to hope for the best. * @@ -197,7 +197,7 @@ extern "C" { * * There is an older way to embed the native mpv window into your own. You have * to get the raw window handle, and set it as "wid" option. This works on X11, - * win32, and OSX only. It's much easier to use than the render API, but + * win32, and macOS only. It's much easier to use than the render API, but * also has various problems. * * Also see client API examples and the mpv manpage. There is an extensive @@ -248,7 +248,7 @@ extern "C" { * relational operators (<, >, <=, >=). */ #define MPV_MAKE_VERSION(major, minor) (((major) << 16) | (minor) | 0UL) -#define MPV_CLIENT_API_VERSION MPV_MAKE_VERSION(2, 2) +#define MPV_CLIENT_API_VERSION MPV_MAKE_VERSION(2, 3) /** * The API user is allowed to "#define MPV_ENABLE_DEPRECATED 0" before @@ -495,7 +495,7 @@ MPV_EXPORT mpv_handle *mpv_create(void); * - load-scripts * - script * - player-operation-mode - * - input-app-events (OSX) + * - input-app-events (macOS) * - all encoding mode options * * @return error code diff --git a/libmpv/render_gl.h b/libmpv/render_gl.h index a2c31f0..aa2719d 100644 --- a/libmpv/render_gl.h +++ b/libmpv/render_gl.h @@ -93,7 +93,7 @@ extern "C" { * MPV_RENDER_PARAM_WL_DISPLAY for Wayland) * - nVidia/Linux: Both GLX and EGL should work (GLX is required if vdpau is * used, e.g. due to old drivers.) - * - OSX: CGL is required (CGLGetCurrentContext() returning non-NULL) + * - macOS: CGL is required (CGLGetCurrentContext() returning non-NULL) * - iOS: EAGL is required (EAGLContext.currentContext returning non-nil) * * Once these things are setup, hardware decoding can be enabled/disabled at diff --git a/meson.build b/meson.build index fdfc526..2c2246b 100644 --- a/meson.build +++ b/meson.build @@ -7,7 +7,7 @@ project('mpv', 'buildtype=debugoptimized', 'b_lundef=false', 'c_std=c11', - 'warning_level=1', + 'warning_level=2', ] ) @@ -23,7 +23,8 @@ libavutil = dependency('libavutil', version: '>= 56.70.100') libswresample = dependency('libswresample', version: '>= 3.9.100') libswscale = dependency('libswscale', version: '>= 5.9.100') -libplacebo = dependency('libplacebo', version: '>=6.338.0') +libplacebo = dependency('libplacebo', version: '>=6.338.2', + default_options: ['default_library=static', 'demos=false']) libass = dependency('libass', version: '>= 0.12.2') @@ -46,7 +47,6 @@ features = { 'jpegxl': libavformat.version().version_compare('>= 59.27.100'), 'avif-muxer': libavformat.version().version_compare('>= 59.24.100'), 'libass': true, - 'threads': true, 'libplacebo': true, } @@ -133,10 +133,12 @@ sources = files( 'misc/bstr.c', 'misc/charset_conv.c', 'misc/dispatch.c', + 'misc/io_utils.c', 'misc/json.c', 'misc/language.c', 'misc/natural_sort.c', 'misc/node.c', + 'misc/path_utils.c', 'misc/random.c', 'misc/rendezvous.c', 'misc/thread_pool.c', @@ -246,7 +248,7 @@ sources = files( ## osdep 'osdep/io.c', - 'osdep/semaphore_osx.c', + 'osdep/semaphore-mac.c', 'osdep/subprocess.c', 'osdep/timer.c', @@ -260,10 +262,14 @@ sources = files( # compiler stuff cc = meson.get_compiler('c') -flags = ['-D_GNU_SOURCE', '-D_FILE_OFFSET_BITS=64'] +flags = ['-D_FILE_OFFSET_BITS=64'] link_flags = [] test_flags = ['-Werror=implicit-function-declaration', + '-Wno-missing-field-initializers', + '-Wno-sign-compare', + '-Wno-unused-parameter', + '-Wno-cast-function-type', '-Wempty-body', '-Wdisabled-optimization', '-Wstrict-prototypes', @@ -304,30 +310,30 @@ features += {'darwin': darwin} features += {'posix': posix} features += {'dos-paths': win32, 'win32': win32} -mswin_flags = ['-D_WIN32_WINNT=0x0602', '-DUNICODE', '-DCOBJMACROS', - '-DINITGUID', '-U__STRICT_ANSI__'] +mswin_flags = ['-D_WIN32_WINNT=0x0602', '-DWINVER=0x0602', '-DUNICODE', + '-DCOBJMACROS', '-DINITGUID', '-U__STRICT_ANSI__', '-DNOMINMAX', + '-D_USE_MATH_DEFINES', '-DWIN32_LEAN_AND_MEAN'] if host_machine.system() == 'windows' - flags += [mswin_flags, '-D__USE_MINGW_ANSI_STDIO=1'] + flags += [mswin_flags] endif if host_machine.system() == 'cygwin' flags += [mswin_flags, '-mwin32'] endif -noexecstack = false +if posix + flags += ['-D_GNU_SOURCE'] +endif + if cc.has_link_argument('-Wl,-z,noexecstack') link_flags += '-Wl,-z,noexecstack' - noexecstack = true endif if cc.has_link_argument('-Wl,--nxcompat,--no-seh,--dynamicbase') link_flags += '-Wl,--nxcompat,--no-seh,--dynamicbase' - noexecstack = true endif -features += {'noexecstack': noexecstack} - features += {'build-date': get_option('build-date')} if not features['build-date'] flags += '-DNO_BUILD_TIMESTAMPS' @@ -362,6 +368,7 @@ if not features['win32-threads'] features += {'pthread-condattr-setclock': cc.has_header_symbol('pthread.h', 'pthread_condattr_setclock', + args: '-D_GNU_SOURCE', dependencies: pthreads)} dependencies += pthreads endif @@ -373,6 +380,7 @@ pthread_debug = get_option('pthread-debug').require( features += {'pthread-debug': pthread_debug.allowed()} add_project_arguments(flags, language: 'c') +add_project_arguments(['-Wno-unused-parameter'], language: 'objc') add_project_link_arguments(link_flags, language: ['c', 'objc']) @@ -382,18 +390,20 @@ cocoa = dependency('appleframeworks', modules: ['Cocoa', 'IOKit', 'QuartzCore'], features += {'cocoa': cocoa.found()} if features['cocoa'] dependencies += cocoa - sources += files('osdep/apple_utils.c', - 'osdep/language-apple.c', - 'osdep/macosx_application.m', - 'osdep/macosx_events.m', - 'osdep/macosx_menubar.m', - 'osdep/main-fn-cocoa.c', - 'osdep/path-macosx.m') + sources += files('osdep/language-mac.c', + 'osdep/path-mac.m', + 'osdep/utils-mac.c', + 'osdep/mac/app_bridge.m') + main_fn_source = files('osdep/main-fn-mac.c') endif if posix path_source = files('osdep/path-unix.c') - subprocess_source = files('osdep/subprocess-posix.c') + if cc.has_function('fork', prefix : '#include <unistd.h>') + subprocess_source = files('osdep/subprocess-posix.c') + else + subprocess_source = files('osdep/subprocess-dummy.c') + endif sources += files('input/ipc-unix.c', 'osdep/poll_wrapper.c', 'osdep/terminal-unix.c', @@ -401,8 +411,8 @@ if posix endif if posix and not features['cocoa'] - sources += files('osdep/main-fn-unix.c', - 'osdep/language-posix.c') + sources += files('osdep/language-posix.c') + main_fn_source = files('osdep/main-fn-unix.c') endif if darwin @@ -479,21 +489,23 @@ if features['win32-desktop'] win32_desktop_libs = [cc.find_library('avrt'), cc.find_library('dwmapi'), cc.find_library('gdi32'), + cc.find_library('imm32'), + cc.find_library('ntdll'), cc.find_library('ole32'), cc.find_library('uuid'), cc.find_library('uxtheme'), - cc.find_library('version'), - cc.find_library('winmm')] + cc.find_library('version')] dependencies += win32_desktop_libs path_source = files('osdep/path-win.c') subprocess_source = files('osdep/subprocess-win.c') sources += files('input/ipc-win.c', 'osdep/language-win.c', - 'osdep/main-fn-win.c', 'osdep/terminal-win.c', 'video/out/w32_common.c', 'video/out/win32/displayconfig.c', - 'video/out/win32/droptarget.c') + 'video/out/win32/droptarget.c', + 'video/out/win32/menu.c') + main_fn_source = files('osdep/main-fn-win.c') endif if not posix and not features['win32-desktop'] @@ -504,7 +516,7 @@ endif features += {'glob-posix': cc.has_function('glob', prefix: '#include <glob.h>')} -features += {'glob-win32': win32 and not posix} +features += {'glob-win32': win32 and not features['glob-posix']} if features['glob-win32'] sources += files('osdep/glob-win.c') endif @@ -516,16 +528,16 @@ features += {'vt.h': cc.has_header_symbol('sys/vt.h', 'VT_GETMODE')} features += {'consio.h': not features['vt.h'] and cc.has_header_symbol('sys/consio.h', 'VT_GETMODE')} # macOS's pthread_setname_np is a special snowflake and differs from literally every other platform. -features += {'osx-thread-name': darwin} +features += {'mac-thread-name': darwin} features += {'glibc-thread-name': false} -if not features['osx-thread-name'] +if not features['mac-thread-name'] features += {'glibc-thread-name': posix and cc.has_function('pthread_setname_np', args: '-D_GNU_SOURCE', dependencies: pthreads, prefix: '#include <pthread.h>')} endif features += {'bsd-thread-name': false} -if not features['osx-thread-name'] and not features['glibc-thread-name'] +if not features['mac-thread-name'] and not features['glibc-thread-name'] features += {'bsd-thread-name': posix and cc.has_function('pthread_set_name_np', dependencies: pthreads, prefix: '#include <pthread.h>\n#include <pthread_np.h>')} endif @@ -593,11 +605,12 @@ if features['cdda'] sources += files('stream/stream_cdda.c') endif -dvbin = get_option('dvbin').require( +dvbin_opt = get_option('dvbin').require( get_option('gpl'), error_message: 'the build is not GPL!', ) -features += {'dvbin': dvbin.allowed()} +dvbin = cc.has_header('linux/dvb/frontend.h', required: dvbin_opt) +features += {'dvbin': dvbin} if features['dvbin'] sources += files('stream/dvb_tune.c', 'stream/stream_dvb.c') @@ -657,14 +670,12 @@ if features['libbluray'] endif libm = cc.find_library('m', required: false) -features += {'libm': libm.found()} -if features['libm'] +if libm.found() dependencies += libm endif librt = cc.find_library('rt', required: false) -features += {'librt': librt.found()} -if features['librt'] +if librt.found() dependencies += librt endif @@ -701,13 +712,13 @@ if features['lua'] sources += files('player/lua.c') endif if not features['lua'] and lua_opt == 'enabled' - error('lua enabled but no suitable lua version could be found!') + error('Lua enabled but no suitable Lua version could be found!') endif rubberband = dependency('rubberband', version: '>= 1.8.0', required: get_option('rubberband')) features += {'rubberband': rubberband.found()} -features += {'rubberband-3': rubberband.version().version_compare('>= 3.0.0')} if features['rubberband'] + features += {'rubberband-3': rubberband.version().version_compare('>= 3.0.0')} dependencies += rubberband sources += files('audio/filter/af_rubberband.c') endif @@ -775,11 +786,12 @@ if features['alsa'] sources += files('audio/out/ao_alsa.c') endif +audiounit_opt = get_option('audiounit').require(darwin) audiounit = { 'deps': dependency('appleframeworks', modules: ['Foundation', 'AudioToolbox'], - required: get_option('audiounit')), + required: audiounit_opt), 'symbol': cc.has_header_symbol('AudioToolbox/AudioToolbox.h', 'kAudioUnitSubType_RemoteIO', - required: get_option('audiounit')), + required: audiounit_opt), } features += {'audiounit': audiounit['deps'].found() and audiounit['symbol']} if features['audiounit'] @@ -787,6 +799,14 @@ if features['audiounit'] sources += files('audio/out/ao_audiounit.m') endif +avfoundation = dependency('appleframeworks', modules: ['CoreMedia', 'AVFoundation'], + required: get_option('avfoundation')) +features += {'avfoundation': avfoundation.found()} +if features['avfoundation'] + dependencies += avfoundation + sources += files('audio/out/ao_avfoundation.m') +endif + coreaudio = dependency('appleframeworks', modules: ['CoreFoundation', 'CoreAudio', 'AudioUnit', 'AudioToolbox'], required: get_option('coreaudio')) features += {'coreaudio': coreaudio.found()} @@ -797,9 +817,10 @@ if features['coreaudio'] 'audio/out/ao_coreaudio_properties.c') endif -if features['audiounit'] or features['coreaudio'] +if features['audiounit'] or features['coreaudio'] or features['avfoundation'] sources += files('audio/out/ao_coreaudio_chmap.c', - 'audio/out/ao_coreaudio_utils.c') + 'audio/out/ao_coreaudio_utils.c', + 'audio/out/ao_coreaudio_properties.c') endif jack_opt = get_option('jack').require( @@ -868,7 +889,8 @@ if features['sndio'] sources += files('audio/out/ao_sndio.c') endif -wasapi = cc.has_header_symbol('audioclient.h', 'IAudioClient', required: get_option('wasapi')) +wasapi = cc.has_header_symbol('audioclient.h', 'IAudioClient', required: + get_option('wasapi').require(win32)) features += {'wasapi': wasapi} if features['wasapi'] sources += files('audio/out/ao_wasapi.c', @@ -912,7 +934,11 @@ if features['drm'] 'video/out/vo_drm.c') endif -gbm = dependency('gbm', version: '>=17.1.0', required: get_option('gbm')) +gbm_opt = get_option('gbm').require( + features['drm'], + error_message: 'drm was not found!' +) +gbm = dependency('gbm', version: '>= 17.1.0', required: gbm_opt) features += {'gbm': gbm.found()} if features['gbm'] dependencies += gbm @@ -933,7 +959,8 @@ if features['sdl2-video'] sources += files('video/out/vo_sdl.c') endif -shaderc = dependency('shaderc', required: get_option('shaderc')) +shaderc = dependency('shaderc', required: + get_option('shaderc').require(features['win32-desktop'])) features += {'shaderc': shaderc.found()} if features['shaderc'] dependencies += shaderc @@ -952,7 +979,8 @@ if features['posix'] features += {'posix-shm': cc.has_function('shm_open', prefix: '#include <sys/mman.h>')} endif -spirv_cross = dependency('spirv-cross-c-shared', required: get_option('spirv-cross')) +spirv_cross = dependency('spirv-cross-c-shared', required: + get_option('spirv-cross').require(features['win32-desktop'])) features += {'spirv-cross': spirv_cross.found()} if features['spirv-cross'] dependencies += spirv_cross @@ -1049,7 +1077,7 @@ if features['xv'] sources += files('video/out/vo_xv.c') endif -if features['wayland'] or features['x11'] +if features['wayland'] or features['x11'] or features['drm'] sources += ('video/out/present_sync.c') endif @@ -1099,12 +1127,11 @@ if features['gl-x11'] sources += files('video/out/opengl/context_glx.c') endif -gl_dxinterop_d3d = gl_win32.allowed() and \ - cc.has_header_symbol('GL/wglext.h', 'WGL_ACCESS_READ_ONLY_NV', - prefix: '#include <GL/gl.h>') -gl_dxinterop_gl = features['gl-win32'] and cc.has_header_symbol('d3d9.h', 'IDirect3D9Ex') gl_dxinterop = get_option('gl-dxinterop').require( - gl_dxinterop_d3d and gl_dxinterop_gl and gl_win32.allowed(), + features['gl-win32'] and + cc.has_header_symbol('GL/wglext.h', 'WGL_ACCESS_READ_ONLY_NV', + prefix: '#include <GL/gl.h>') and + cc.has_header_symbol('d3d9.h', 'IDirect3D9Ex'), error_message: 'gl-dxinterop could not be found!', ) features += {'gl-dxinterop': gl_dxinterop.allowed()} @@ -1128,9 +1155,10 @@ if features['egl-angle'] sources += files('video/out/opengl/angle_dynamic.c') endif -egl_dep = cc.find_library('EGL', required: get_option('egl-angle-lib')) +egl_dep = cc.find_library('EGL', required: + get_option('egl-angle-lib').require(features['egl-angle'])) egl_angle_lib = get_option('egl-angle-lib').require( - features['egl-angle'] and cc.has_function('eglCreateWindowSurface', + egl_dep.found() and cc.has_function('eglCreateWindowSurface', dependencies: egl_dep, prefix: '#include <EGL/egl.h>'), error_message: 'egl-angle-lib could not be found!', @@ -1153,7 +1181,7 @@ if features['d3d11'] or features['egl-angle-win32'] sources += files('video/out/gpu/d3d11_helpers.c') endif -egl = dependency('egl', version: '> 1.4.0', required: get_option('egl')) +egl = dependency('egl', version: '>= 1.4.0', required: get_option('egl')) features += {'egl': egl.found() and gl_allowed} if features['egl'] dependencies += egl @@ -1172,7 +1200,7 @@ if features['egl-android'] endif egl_drm = get_option('egl-drm').require( - features['drm'] and features['egl'] and gbm.found() and gl_allowed, + features['drm'] and features['egl'] and features['gbm'] and gl_allowed, error_message: 'either drm, egl, or gbm could not be found!', ) features += {'egl-drm': egl_drm.allowed()} @@ -1181,8 +1209,12 @@ if features['egl-drm'] sources += files('video/out/opengl/context_drm_egl.c') endif -egl_wayland = dependency('wayland-egl', version: '>= 9.0.0', required: get_option('egl-wayland')) -features += {'egl-wayland': features['egl'] and egl_wayland.found() and gl_allowed and features['wayland']} +egl_wayland_opt = get_option('egl-wayland').require( + features['egl'] and features['wayland'] and gl_allowed, + error_message: 'either egl or wayland could not be found!', +) +egl_wayland = dependency('wayland-egl', version: '>= 9.0.0', required: egl_wayland_opt) +features += {'egl-wayland': egl_wayland.found()} if features['egl-wayland'] dependencies += egl_wayland features += {'gl': true} @@ -1207,21 +1239,11 @@ if plain_gl.allowed() features += {'gl': true} endif -rpi = dependency('/opt/vc/lib/pkgconfig/brcmegl.pc', 'brcmegl', required: get_option('rpi')) -features += {'rpi': gl_allowed and rpi.found()} -if features['rpi'] - dependencies += rpi - features += {'gl': true} - sources += files('video/out/opengl/context_rpi.c') -endif - -features += {'egl-helpers': features['egl'] or egl_android.found() or - egl_angle_win32.allowed() or features['rpi']} -if features['egl-helpers'] +if features['egl'] or features['egl-android'] or features['egl-angle-win32'] sources += files('video/out/opengl/egl_helpers.c') endif -if features['egl'] and features['egl-helpers'] +if features['gl'] and features['egl'] sources += files('video/filter/vf_gpu.c') endif @@ -1281,15 +1303,6 @@ if features['ffnvcodec'] sources += files('video/cuda.c') endif -android_media_ndk = get_option('android-media-ndk').require( - features['android'] and cc.has_header_symbol('media/NdkImageReader.h', 'AIMAGE_FORMAT_PRIVATE') -) -features += {'android-media-ndk': android_media_ndk.allowed()} -if features['android-media-ndk'] - # header only, library is dynamically loaded - sources += files('video/out/hwdec/hwdec_aimagereader.c') -endif - cuda_hwaccel = get_option('cuda-hwaccel').require( features['ffnvcodec'], error_message: 'ffnvcodec was not found!', @@ -1303,7 +1316,7 @@ cuda_interop = get_option('cuda-interop').require( features['cuda-hwaccel'] and (features['gl'] or features['vulkan']), error_message: 'cuda-hwaccel and either gl or vulkan were not found!', ) -features += {'cuda-interop': cuda_interop.allowed() and (features['gl'] or features['vulkan'])} +features += {'cuda-interop': cuda_interop.allowed()} if features['cuda-interop'] and features['gl'] sources += files('video/out/hwdec/hwdec_cuda_gl.c') endif @@ -1311,13 +1324,22 @@ if features['cuda-interop'] and features['vulkan'] sources += files('video/out/hwdec/hwdec_cuda_vk.c') endif +android_media_ndk = get_option('android-media-ndk').require( + features['android'] and cc.has_header_symbol('media/NdkImageReader.h', 'AIMAGE_FORMAT_PRIVATE') +) +features += {'android-media-ndk': android_media_ndk.allowed()} +if features['android-media-ndk'] + # header only, library is dynamically loaded + sources += files('video/out/hwdec/hwdec_aimagereader.c') +endif + vulkan_interop = get_option('vulkan-interop').require( features['vulkan'] and vulkan.version().version_compare('>=1.3.238') and libavutil.version().version_compare('>=58.11.100'), error_message: 'Vulkan Interop requires vulkan headers >= 1.3.238, and libavutil >= 58.11.100', ) features += {'vulkan-interop': vulkan_interop.allowed()} -if vulkan_interop.allowed() +if features['vulkan-interop'] sources += files('video/out/hwdec/hwdec_vulkan.c') endif @@ -1331,7 +1353,7 @@ if features['d3d-hwaccel'] 'video/filter/vf_d3d11vpp.c') endif -if features['d3d-hwaccel'] and egl_angle.allowed() +if features['d3d-hwaccel'] and features['egl-angle'] sources += files('video/out/opengl/hwdec_d3d11egl.c') endif if features['d3d-hwaccel'] and features['d3d11'] @@ -1359,50 +1381,48 @@ if features['gl-dxinterop-d3d9'] sources += files('video/out/opengl/hwdec_dxva2gldx.c') endif -ios_gl = cc.has_header_symbol('OpenGLES/ES3/glext.h', 'GL_RGB32F', required: get_option('ios-gl')) +# this is an arbitrary GLES 3.x symbol +ios_gl = cc.has_header_symbol('OpenGLES/ES3/glext.h', 'GL_RGB32F', required: + get_option('ios-gl').require(darwin)) features += {'ios-gl': ios_gl} if features['ios-gl'] sources += files('video/out/hwdec/hwdec_ios_gl.m') endif -rpi_mmal_opt = get_option('rpi-mmal').require( - features['rpi'], - error_message: 'rpi was not found!', -) -rpi_mmal = dependency('/opt/vc/lib/pkgconfig/mmal.pc', 'mmal', required: rpi_mmal_opt) -features += {'rpi-mmal': rpi_mmal.found()} -if features['rpi-mmal'] - dependencies += rpi_mmal - sources += files('video/out/opengl/hwdec_rpi.c', - 'video/out/vo_rpi.c') -endif - libva = dependency('libva', version: '>= 1.1.0', required: get_option('vaapi')) -vaapi_drm = dependency('libva-drm', version: '>= 1.1.0', - required: get_option('vaapi-drm').require(libva.found() and features['drm'])) +vaapi_drm = dependency('libva-drm', version: '>= 1.1.0', required: + get_option('vaapi-drm').require(libva.found() and features['drm'])) features += {'vaapi-drm': vaapi_drm.found()} if features['vaapi-drm'] dependencies += vaapi_drm endif -vaapi_wayland = dependency('libva-wayland', version: '>= 1.1.0', - required: get_option('vaapi-wayland').require(libva.found() and features['wayland'])) +vaapi_wayland = dependency('libva-wayland', version: '>= 1.1.0', required: + get_option('vaapi-wayland').require(libva.found() and features['wayland'])) features += {'vaapi-wayland': vaapi_wayland.found()} if features['vaapi-wayland'] dependencies += vaapi_wayland endif -vaapi_x11 = dependency('libva-x11', version: '>= 1.1.0', - required: get_option('vaapi-x11').require(libva.found() and features['x11'])) +vaapi_x11 = dependency('libva-x11', version: '>= 1.1.0', required: + get_option('vaapi-x11').require(libva.found() and features['x11'])) features += {'vaapi-x11': vaapi_x11.found()} if features['vaapi-x11'] dependencies += vaapi_x11 sources += files('video/out/vo_vaapi.c') endif +vaapi_win32 = dependency('libva-win32', required: + get_option('vaapi-win32').require(libva.found() and win32)) +features += {'vaapi-win32': vaapi_win32.found()} +if features['vaapi-win32'] + dependencies += vaapi_win32 +endif + vaapi = get_option('vaapi').require(libva.found() and (features['vaapi-drm'] or - features['vaapi-wayland'] or features['vaapi-x11'])) + features['vaapi-wayland'] or features['vaapi-x11'] or + features['vaapi-win32'])) features += {'vaapi': vaapi.allowed()} if features['vaapi'] @@ -1413,8 +1433,7 @@ if features['vaapi'] 'video/out/hwdec/dmabuf_interop_pl.c') endif -dmabuf_interop_gl = features['egl'] and features['drm'] -features += {'dmabuf-interop-gl': dmabuf_interop_gl} +features += {'dmabuf-interop-gl': features['egl'] and features['drm']} if features['dmabuf-interop-gl'] sources += files('video/out/hwdec/dmabuf_interop_gl.c') endif @@ -1433,7 +1452,7 @@ if features['vdpau'] 'video/vdpau_mixer.c') endif -features += {'vdpau-gl-x11': vdpau.found() and gl_x11.allowed()} +features += {'vdpau-gl-x11': features['vdpau'] and gl_x11.allowed()} if features['vdpau'] and features['vdpau-gl-x11'] sources += files('video/out/opengl/hwdec_vdpau.c') endif @@ -1443,13 +1462,14 @@ videotoolbox_gl = get_option('videotoolbox-gl').require( error_message: 'gl-cocoa nor ios-gl could be found!', ) features += {'videotoolbox-gl': videotoolbox_gl.allowed()} -corevideo = dependency('appleframeworks', modules: ['CoreVideo'], required: get_option('videotoolbox-pl')) +corevideo = dependency('appleframeworks', modules: ['CoreVideo'], required: + get_option('videotoolbox-pl').require(darwin)) videotoolbox_pl = get_option('videotoolbox-pl').require( features['vulkan'] and corevideo.found(), error_message: 'vulkan or CV metal support could be found!', ) features += {'videotoolbox-pl': videotoolbox_pl.allowed()} -if features['videotoolbox-gl'] or features['videotoolbox-pl'] +if features['videotoolbox-gl'] or features['videotoolbox-pl'] or features['ios-gl'] sources += files('video/out/hwdec/hwdec_vt.c') endif if features['videotoolbox-gl'] @@ -1512,12 +1532,19 @@ features += {'swift': swift.allowed()} swift_sources = [] if features['cocoa'] and features['swift'] - swift_sources += files('osdep/macos/libmpv_helper.swift', - 'osdep/macos/log_helper.swift', - 'osdep/macos/mpv_helper.swift', - 'osdep/macos/precise_timer.swift', - 'osdep/macos/swift_compat.swift', - 'osdep/macos/swift_extensions.swift', + swift_sources += files('osdep/mac/application.swift', + 'osdep/mac/app_hub.swift', + 'osdep/mac/event_helper.swift', + 'osdep/mac/input_helper.swift', + 'osdep/mac/libmpv_helper.swift', + 'osdep/mac/log_helper.swift', + 'osdep/mac/menu_bar.swift', + 'osdep/mac/option_helper.swift', + 'osdep/mac/precise_timer.swift', + 'osdep/mac/presentation.swift', + 'osdep/mac/swift_compat.swift', + 'osdep/mac/swift_extensions.swift', + 'osdep/mac/type_helper.swift', 'video/out/mac/common.swift', 'video/out/mac/title_bar.swift', 'video/out/mac/view.swift', @@ -1545,11 +1572,7 @@ macos_media_player = get_option('macos-media-player').require( ) features += {'macos-media-player': macos_media_player.allowed()} if features['macos-media-player'] - swift_sources += files('osdep/macos/remote_command_center.swift') -endif - -if features['swift'] and swift_sources.length() > 0 - subdir('osdep') + swift_sources += files('osdep/mac/remote_command_center.swift') endif macos_touchbar = get_option('macos-touchbar').require( @@ -1558,15 +1581,18 @@ macos_touchbar = get_option('macos-touchbar').require( ) features += {'macos-touchbar': macos_touchbar.allowed()} if features['macos-touchbar'] - sources += files('osdep/macosx_touchbar.m') + swift_sources += files('osdep/mac/touch_bar.swift') +endif + +if features['swift'] and swift_sources.length() > 0 + subdir('osdep/mac') endif # manpages manpage = 'DOCS/man/mpv.rst' rst2man = find_program('rst2man', 'rst2man.py', required: get_option('manpage-build')) -features += {'manpage-build': rst2man.found()} -if features['manpage-build'] +if rst2man.found() mandir = get_option('mandir') custom_target('manpages', input: manpage, @@ -1583,8 +1609,7 @@ if features['manpage-build'] endif rst2html = find_program('rst2html', 'rst2html.py', required: get_option('html-build')) -features += {'html-build': rst2html.found()} -if features['html-build'] +if rst2html.found() datadir = get_option('datadir') custom_target('html-manpages', input: manpage, @@ -1600,8 +1625,7 @@ if features['html-build'] endif rst2pdf = find_program('rst2pdf', required: get_option('pdf-build')) -features += {'pdf-build': rst2pdf.found()} -if features['pdf-build'] +if rst2pdf.found() dependency_file = rst2pdf.version().version_compare('>=0.100') datadir = get_option('datadir') custom_target('pdf-manpages', @@ -1746,8 +1770,23 @@ if get_option('cplayer') rename: 'mpv.svg') install_data('etc/mpv-symbolic.svg', install_dir: join_paths(hicolor_dir, 'symbolic', 'apps')) - mpv = executable('mpv', objects: libmpv.extract_all_objects(recursive: true), dependencies: dependencies, + mpv = executable('mpv', main_fn_source, objects: libmpv.extract_all_objects(recursive: true), dependencies: dependencies, win_subsystem: 'windows,6.0', install: true) + + # Older meson versions generate this in the player subdirectory. + if win32 and meson.version().version_compare('>= 1.3.0') + wrapper_sources= 'osdep/win32-console-wrapper.c' + executable('mpv', wrapper_sources, c_args: '-municode', link_args: '-municode', + name_suffix: 'com', install: true) + endif + + if darwin + osxbundle = find_program(join_paths(tools_directory, 'osxbundle.py'), required: true) + custom_target('macos-bundle', + output: 'mpv.app', + command: [osxbundle, mpv.full_path(), '@SOURCE_ROOT@'], + ) + endif endif if get_option('tests') diff --git a/meson_options.txt b/meson_options.txt index b0edb80..e488f6e 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -2,7 +2,7 @@ option('gpl', type: 'boolean', value: true, description: 'GPL (version 2 or later) build') option('cplayer', type: 'boolean', value: true, description: 'mpv CLI player') option('libmpv', type: 'boolean', value: false, description: 'libmpv library') -option('build-date', type: 'boolean', value: true, description: 'whether to include binary compile time') +option('build-date', type: 'boolean', value: true, description: 'include compile timestamp in binary') option('tests', type: 'boolean', value: false, description: 'meson unit tests') # Reminder: normally always built, but enabled by MPV_LEAK_REPORT. # Building it can be disabled only by defining NDEBUG through CFLAGS. @@ -11,10 +11,11 @@ option('ta-leak-report', type: 'boolean', value: false, description: 'enable ta # misc features option('cdda', type: 'feature', value: 'disabled', description: 'cdda support (libcdio)') option('cplugins', type: 'feature', value: 'auto', description: 'C plugins') -option('dvbin', type: 'feature', value: 'disabled', description: 'DVB input module') +option('dvbin', type: 'feature', value: 'auto', description: 'DVB input module') option('dvdnav', type: 'feature', value: 'disabled', description: 'dvdnav support') option('iconv', type: 'feature', value: 'auto', description: 'iconv') option('javascript', type: 'feature', value: 'auto', description: 'Javascript (MuJS backend)') +option('jpeg', type: 'feature', value: 'auto', description: 'libjpeg image writer') option('lcms2', type: 'feature', value: 'auto', description: 'LCMS2 support') option('libarchive', type: 'feature', value: 'auto', description: 'libarchive wrapper for reading zip files and more') option('libavdevice', type: 'feature', value: 'auto', description: 'libavdevice') @@ -34,14 +35,15 @@ option('uchardet', type: 'feature', value: 'auto', description: 'uchardet suppor option('uwp', type: 'feature', value: 'disabled', description: 'Universal Windows Platform') option('vapoursynth', type: 'feature', value: 'auto', description: 'VapourSynth filter bridge') option('vector', type: 'feature', value: 'auto', description: 'GCC vector instructions') -option('win32-threads', type: 'feature', value: 'auto', description: 'win32 threads') +option('win32-threads', type: 'feature', value: 'auto', description: 'win32 native threading') option('zimg', type: 'feature', value: 'auto', description: 'libzimg support (high quality software scaler)') option('zlib', type: 'feature', value: 'auto', description: 'zlib') # audio output features option('alsa', type: 'feature', value: 'auto', description: 'ALSA audio output') -option('audiounit', type: 'feature', value: 'auto', description: 'AudioUnit output for iOS') +option('audiounit', type: 'feature', value: 'auto', description: 'AudioUnit output (iOS)') option('coreaudio', type: 'feature', value: 'auto', description: 'CoreAudio audio output') +option('avfoundation', type: 'feature', value: 'auto', description: 'AVFoundation audio output') option('jack', type: 'feature', value: 'auto', description: 'JACK audio output') option('openal', type: 'feature', value: 'disabled', description: 'OpenAL audio output') option('opensles', type: 'feature', value: 'auto', description: 'OpenSL ES audio output') @@ -58,26 +60,24 @@ option('cocoa', type: 'feature', value: 'auto', description: 'Cocoa') option('d3d11', type: 'feature', value: 'auto', description: 'Direct3D 11 video output') option('direct3d', type: 'feature', value: 'auto', description: 'Direct3D support') option('dmabuf-wayland', type: 'feature', value: 'auto', description: 'dmabuf-wayland video output') -option('drm', type: 'feature', value: 'auto', description: 'DRM') +option('drm', type: 'feature', value: 'auto', description: 'Direct Rendering Manager (DRM)') option('egl', type: 'feature', value: 'auto', description: 'EGL 1.4') option('egl-android', type: 'feature', value: 'auto', description: 'Android EGL support') option('egl-angle', type: 'feature', value: 'auto', description: 'OpenGL ANGLE headers') option('egl-angle-lib', type: 'feature', value: 'auto', description: 'OpenGL Win32 ANGLE library') -option('egl-angle-win32', type: 'feature', value: 'auto', description: 'OpenGL Win32 ANGLE Backend') -option('egl-drm', type: 'feature', value: 'auto', description: 'OpenGL DRM EGL Backend') -option('egl-wayland', type: 'feature', value: 'auto', description: 'OpenGL Wayland Backend') -option('egl-x11', type: 'feature', value: 'auto', description: 'OpenGL X11 EGL Backend') -option('gbm', type: 'feature', value: 'auto', description: 'GBM') +option('egl-angle-win32', type: 'feature', value: 'auto', description: 'OpenGL Win32 ANGLE backend') +option('egl-drm', type: 'feature', value: 'auto', description: 'OpenGL DRM EGL backend') +option('egl-wayland', type: 'feature', value: 'auto', description: 'OpenGL Wayland backend') +option('egl-x11', type: 'feature', value: 'auto', description: 'OpenGL X11 EGL backend') +option('gbm', type: 'feature', value: 'auto', description: 'Generic Buffer Manager (GBM)') option('gl', type: 'feature', value: 'enabled', description: 'OpenGL context support') -option('gl-cocoa', type: 'feature', value: 'auto', description: 'gl-cocoa') -option('gl-dxinterop', type: 'feature', value: 'auto', description: 'OpenGL/DirectX Interop Backend') -option('gl-win32', type: 'feature', value: 'auto', description: 'OpenGL Win32 Backend') +option('gl-cocoa', type: 'feature', value: 'auto', description: 'OpenGL Cocoa backend') +option('gl-dxinterop', type: 'feature', value: 'auto', description: 'OpenGL/DirectX Interop backend') +option('gl-win32', type: 'feature', value: 'auto', description: 'OpenGL Win32 backend') option('gl-x11', type: 'feature', value: 'disabled', description: 'OpenGL X11/GLX (deprecated/legacy)') -option('jpeg', type: 'feature', value: 'auto', description: 'JPEG support') -option('rpi', type: 'feature', value: 'disabled', description: 'Raspberry Pi support') option('sdl2-video', type: 'feature', value: 'auto', description: 'SDL2 video output') option('shaderc', type: 'feature', value: 'auto', description: 'libshaderc SPIR-V compiler') -option('sixel', type: 'feature', value:'auto', description: 'Sixel') +option('sixel', type: 'feature', value:'auto', description: 'Sixel video output') option('spirv-cross', type: 'feature', value: 'auto', description: 'SPIRV-Cross SPIR-V shader converter') option('plain-gl', type: 'feature', value: 'auto', description: 'OpenGL without platform-specific code (e.g. for libmpv)') option('vdpau', type: 'feature', value: 'auto', description: 'VDPAU acceleration') @@ -85,6 +85,7 @@ option('vdpau-gl-x11', type: 'feature', value: 'auto', description: 'VDPAU with option('vaapi', type: 'feature', value: 'auto', description: 'VAAPI acceleration') option('vaapi-drm', type: 'feature', value: 'auto', description: 'VAAPI (DRM support)') option('vaapi-wayland', type: 'feature', value: 'auto', description: 'VAAPI (Wayland support)') +option('vaapi-win32', type: 'feature', value: 'auto', description: 'VAAPI (Windows support)') option('vaapi-x11', type: 'feature', value: 'auto', description: 'VAAPI (X11 support)') option('vulkan', type: 'feature', value: 'auto', description: 'Vulkan context support') option('wayland', type: 'feature', value: 'auto', description: 'Wayland') @@ -97,9 +98,8 @@ option('cuda-hwaccel', type: 'feature', value: 'auto', description: 'CUDA accele option('cuda-interop', type: 'feature', value: 'auto', description: 'CUDA with graphics interop') option('d3d-hwaccel', type: 'feature', value: 'auto', description: 'D3D11VA hwaccel') option('d3d9-hwaccel', type: 'feature', value: 'auto', description: 'DXVA2 hwaccel') -option('gl-dxinterop-d3d9', type: 'feature', value: 'auto', description: 'OpenGL/DirectX Interop Backend DXVA2 interop') -option('ios-gl', type: 'feature', value: 'auto', description: 'iOS OpenGL ES hardware decoding interop support') -option('rpi-mmal', type: 'feature', value: 'auto', description: 'Raspberry Pi MMAL hwaccel') +option('gl-dxinterop-d3d9', type: 'feature', value: 'auto', description: 'OpenGL/DirectX DXVA2 hwaccel') +option('ios-gl', type: 'feature', value: 'auto', description: 'iOS OpenGL ES interop support') option('videotoolbox-gl', type: 'feature', value: 'auto', description: 'Videotoolbox with OpenGL') option('videotoolbox-pl', type: 'feature', value: 'auto', description: 'Videotoolbox with libplacebo') option('vulkan-interop', type: 'feature', value: 'auto', description: 'Vulkan graphics interop') @@ -112,6 +112,6 @@ option('swift-build', type: 'feature', value: 'auto', description: 'macOS Swift option('swift-flags', type: 'string', description: 'Optional Swift compiler flags') # manpages -option('html-build', type: 'feature', value: 'disabled', description: 'html manual generation') +option('html-build', type: 'feature', value: 'disabled', description: 'HTML manual generation') option('manpage-build', type: 'feature', value: 'auto', description: 'manpage generation') -option('pdf-build', type: 'feature', value: 'disabled', description: 'pdf manual generation') +option('pdf-build', type: 'feature', value: 'disabled', description: 'PDF manual generation') diff --git a/misc/bstr.c b/misc/bstr.c index 4f1e862..abe688b 100644 --- a/misc/bstr.c +++ b/misc/bstr.c @@ -467,3 +467,23 @@ bool bstr_decode_hex(void *talloc_ctx, struct bstr hex, struct bstr *out) *out = (struct bstr){ .start = arr, .len = len }; return true; } + +#ifdef _WIN32 + +#include <windows.h> + +int bstr_to_wchar(void *talloc_ctx, struct bstr s, wchar_t **ret) +{ + int count = MultiByteToWideChar(CP_UTF8, 0, s.start, s.len, NULL, 0); + if (count <= 0) + abort(); + wchar_t *wbuf = *ret; + if (!wbuf || ta_get_size(wbuf) < (count + 1) * sizeof(wchar_t)) + wbuf = talloc_realloc(talloc_ctx, wbuf, wchar_t, count + 1); + MultiByteToWideChar(CP_UTF8, 0, s.start, s.len, wbuf, count); + wbuf[count] = L'\0'; + *ret = wbuf; + return count; +} + +#endif diff --git a/misc/bstr.h b/misc/bstr.h index dc8ad40..aaae7d6 100644 --- a/misc/bstr.h +++ b/misc/bstr.h @@ -56,6 +56,8 @@ static inline struct bstr bstrdup(void *talloc_ctx, struct bstr str) return r; } +#define bstr0_lit(s) {(unsigned char *)(s), sizeof("" s) - 1} + static inline struct bstr bstr0(const char *s) { return (struct bstr){(unsigned char *)s, s ? strlen(s) : 0}; @@ -223,6 +225,12 @@ static inline bool bstr_eatend0(struct bstr *s, const char *prefix) return bstr_eatend(s, bstr0(prefix)); } +#ifdef _WIN32 + +int bstr_to_wchar(void *talloc_ctx, struct bstr s, wchar_t **ret); + +#endif + // create a pair (not single value!) for "%.*s" printf syntax #define BSTR_P(bstr) (int)((bstr).len), ((bstr).start ? (char*)(bstr).start : "") diff --git a/misc/io_utils.c b/misc/io_utils.c new file mode 100644 index 0000000..c973cee --- /dev/null +++ b/misc/io_utils.c @@ -0,0 +1,87 @@ +/* + * I/O utility functions + * + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <errno.h> +#include <stdint.h> +#include <stdlib.h> +#include <sys/types.h> +#include <limits.h> +#include <unistd.h> + +#include "mpv_talloc.h" +#include "config.h" +#include "misc/random.h" +#include "misc/io_utils.h" +#include "osdep/io.h" + +int mp_mkostemps(char *template, int suffixlen, int flags) +{ + size_t len = strlen(template); + char *t = len >= 6 + suffixlen ? &template[len - (6 + suffixlen)] : NULL; + if (!t || strncmp(t, "XXXXXX", 6) != 0) { + errno = EINVAL; + return -1; + } + + for (size_t fuckshit = 0; fuckshit < UINT32_MAX; fuckshit++) { + // Using a random value may make it require fewer iterations (even if + // not truly random; just a counter would be sufficient). + size_t fuckmess = mp_rand_next(); + char crap[7] = ""; + snprintf(crap, sizeof(crap), "%06zx", fuckmess); + memcpy(t, crap, 6); + + int res = open(template, O_RDWR | O_CREAT | O_EXCL | flags, 0600); + if (res >= 0 || errno != EEXIST) + return res; + } + + errno = EEXIST; + return -1; +} + +bool mp_save_to_file(const char *filepath, const void *data, size_t size) +{ + assert(filepath && data && size); + + bool result = false; + char *tmp = talloc_asprintf(NULL, "%sXXXXXX", filepath); + int fd = mkstemp(tmp); + if (fd < 0) + goto done; + FILE *cache = fdopen(fd, "wb"); + if (!cache) { + close(fd); + unlink(tmp); + goto done; + } + size_t written = fwrite(data, size, 1, cache); + int ret = fclose(cache); + if (written > 0 && !ret) { + ret = rename(tmp, filepath); + result = !ret; + } else { + unlink(tmp); + } + +done: + talloc_free(tmp); + return result; +} diff --git a/osdep/macosx_menubar_objc.h b/misc/io_utils.h index 072fef8..012f242 100644 --- a/osdep/macosx_menubar_objc.h +++ b/misc/io_utils.h @@ -1,4 +1,6 @@ /* + * I/O utility functions + * * This file is part of mpv. * * mpv is free software; you can redistribute it and/or @@ -15,11 +17,9 @@ * License along with mpv. If not, see <http://www.gnu.org/licenses/>. */ -#import <Cocoa/Cocoa.h> -#include "osdep/macosx_menubar.h" - -@interface MenuBar : NSObject +#pragma once -- (void)registerSelector:(SEL)action forKey:(MPMenuKey)key; +#include <stddef.h> -@end +int mp_mkostemps(char *template, int suffixlen, int flags); +bool mp_save_to_file(const char *filepath, const void *data, size_t size); @@ -20,11 +20,10 @@ */ #include <libavcodec/jni.h> -#include <libavutil/mem.h> -#include <libavutil/bprint.h> #include <stdlib.h> #include "jni.h" +#include "mpv_talloc.h" #include "osdep/threads.h" static JavaVM *java_vm; @@ -46,13 +45,11 @@ static void jni_create_pthread_key(void) JNIEnv *mp_jni_get_env(struct mp_log *log) { - int ret = 0; JNIEnv *env = NULL; mp_mutex_lock(&lock); - if (java_vm == NULL) { + if (!java_vm) java_vm = av_jni_get_java_vm(NULL); - } if (!java_vm) { mp_err(log, "No Java virtual machine has been registered\n"); @@ -61,11 +58,10 @@ JNIEnv *mp_jni_get_env(struct mp_log *log) mp_exec_once(&once, jni_create_pthread_key); - if ((env = pthread_getspecific(current_env)) != NULL) { + if ((env = pthread_getspecific(current_env)) != NULL) goto done; - } - ret = (*java_vm)->GetEnv(java_vm, (void **)&env, JNI_VERSION_1_6); + int ret = (*java_vm)->GetEnv(java_vm, (void **)&env, JNI_VERSION_1_6); switch(ret) { case JNI_EDETACHED: if ((*java_vm)->AttachCurrentThread(java_vm, &env, NULL) != 0) { @@ -92,39 +88,27 @@ done: char *mp_jni_jstring_to_utf_chars(JNIEnv *env, jstring string, struct mp_log *log) { - char *ret = NULL; - const char *utf_chars = NULL; - - jboolean copy = 0; - - if (!string) { + if (!string) return NULL; - } - utf_chars = (*env)->GetStringUTFChars(env, string, ©); + const char *utf_chars = (*env)->GetStringUTFChars(env, string, NULL); if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); - mp_err(log, "String.getStringUTFChars() threw an exception\n"); + mp_err(log, "getStringUTFChars() threw an exception\n"); return NULL; } - ret = av_strdup(utf_chars); + char *ret = talloc_strdup(NULL, utf_chars); (*env)->ReleaseStringUTFChars(env, string, utf_chars); - if ((*env)->ExceptionCheck(env)) { - (*env)->ExceptionClear(env); - mp_err(log, "String.releaseStringUTFChars() threw an exception\n"); - return NULL; - } return ret; } -jstring mp_jni_utf_chars_to_jstring(JNIEnv *env, const char *utf_chars, struct mp_log *log) +jstring mp_jni_utf_chars_to_jstring(JNIEnv *env, const char *utf_chars, + struct mp_log *log) { - jstring ret; - - ret = (*env)->NewStringUTF(env, utf_chars); + jstring ret = (*env)->NewStringUTF(env, utf_chars); if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); mp_err(log, "NewStringUTF() threw an exception\n"); @@ -134,24 +118,19 @@ jstring mp_jni_utf_chars_to_jstring(JNIEnv *env, const char *utf_chars, struct m return ret; } -int mp_jni_exception_get_summary(JNIEnv *env, jthrowable exception, char **error, struct mp_log *log) +int mp_jni_exception_get_summary(JNIEnv *env, jthrowable exception, + char **error, struct mp_log *log) { int ret = 0; - AVBPrint bp; - char *name = NULL; char *message = NULL; jclass class_class = NULL; - jmethodID get_name_id = NULL; - jclass exception_class = NULL; - jmethodID get_message_id = NULL; - jstring string = NULL; - av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC); + *error = NULL; exception_class = (*env)->GetObjectClass(env, exception); if ((*env)->ExceptionCheck(env)) { @@ -169,7 +148,7 @@ int mp_jni_exception_get_summary(JNIEnv *env, jthrowable exception, char **error goto done; } - get_name_id = (*env)->GetMethodID(env, class_class, "getName", "()Ljava/lang/String;"); + jmethodID get_name_id = (*env)->GetMethodID(env, class_class, "getName", "()Ljava/lang/String;"); if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); mp_err(log, "Could not find method Class.getName()\n"); @@ -187,14 +166,13 @@ int mp_jni_exception_get_summary(JNIEnv *env, jthrowable exception, char **error if (string) { name = mp_jni_jstring_to_utf_chars(env, string, log); - (*env)->DeleteLocalRef(env, string); - string = NULL; + MP_JNI_LOCAL_FREEP(&string); } - get_message_id = (*env)->GetMethodID(env, exception_class, "getMessage", "()Ljava/lang/String;"); + jmethodID get_message_id = (*env)->GetMethodID(env, exception_class, "getMessage", "()Ljava/lang/String;"); if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); - mp_err(log, "Could not find method java/lang/Throwable.getMessage()\n"); + mp_err(log, "Could not find method Throwable.getMessage()\n"); ret = -1; goto done; } @@ -209,164 +187,145 @@ int mp_jni_exception_get_summary(JNIEnv *env, jthrowable exception, char **error if (string) { message = mp_jni_jstring_to_utf_chars(env, string, log); - (*env)->DeleteLocalRef(env, string); - string = NULL; + MP_JNI_LOCAL_FREEP(&string); } if (name && message) { - av_bprintf(&bp, "%s: %s", name, message); + *error = talloc_asprintf(NULL, "%s: %s", name, message); } else if (name && !message) { - av_bprintf(&bp, "%s occurred", name); + *error = talloc_asprintf(NULL, "%s occurred", name); } else if (!name && message) { - av_bprintf(&bp, "Exception: %s", message); + *error = talloc_asprintf(NULL, "Exception: %s", message); } else { mp_warn(log, "Could not retrieve exception name and message\n"); - av_bprintf(&bp, "Exception occurred"); + *error = talloc_strdup(NULL, "Exception occurred"); } - ret = av_bprint_finalize(&bp, error); done: - av_free(name); - av_free(message); - - if (class_class) { - (*env)->DeleteLocalRef(env, class_class); - } - - if (exception_class) { - (*env)->DeleteLocalRef(env, exception_class); - } + talloc_free(name); + talloc_free(message); - if (string) { - (*env)->DeleteLocalRef(env, string); - } + MP_JNI_LOCAL_FREEP(&class_class); + MP_JNI_LOCAL_FREEP(&exception_class); + MP_JNI_LOCAL_FREEP(&string); return ret; } int mp_jni_exception_check(JNIEnv *env, int logging, struct mp_log *log) { - int ret; - - jthrowable exception; - - char *message = NULL; - - if (!(*(env))->ExceptionCheck((env))) { + if (!(*env)->ExceptionCheck(env)) return 0; - } if (!logging) { - (*(env))->ExceptionClear((env)); + (*env)->ExceptionClear(env); return -1; } - exception = (*env)->ExceptionOccurred(env); - (*(env))->ExceptionClear((env)); + jthrowable exception = (*env)->ExceptionOccurred(env); + (*env)->ExceptionClear(env); - if ((ret = mp_jni_exception_get_summary(env, exception, &message, log)) < 0) { - (*env)->DeleteLocalRef(env, exception); + char *message = NULL; + int ret = mp_jni_exception_get_summary(env, exception, &message, log); + MP_JNI_LOCAL_FREEP(&exception); + if (ret < 0) return ret; - } - - (*env)->DeleteLocalRef(env, exception); mp_err(log, "%s\n", message); - av_free(message); - + talloc_free(message); return -1; } -int mp_jni_init_jfields(JNIEnv *env, void *jfields, const struct MPJniField *jfields_mapping, int global, struct mp_log *log) +#define CHECK_EXC_MANDATORY() do { \ + if ((ret = mp_jni_exception_check(env, mandatory, log)) < 0 && \ + mandatory) { \ + goto done; \ + } \ + } while (0) + +int mp_jni_init_jfields(JNIEnv *env, void *jfields, + const struct MPJniField *jfields_mapping, + int global, struct mp_log *log) { - int i, ret = 0; + int ret = 0; jclass last_clazz = NULL; - for (i = 0; jfields_mapping[i].name; i++) { - int mandatory = jfields_mapping[i].mandatory; + for (int i = 0; jfields_mapping[i].name; i++) { + bool mandatory = !!jfields_mapping[i].mandatory; enum MPJniFieldType type = jfields_mapping[i].type; - if (type == MP_JNI_CLASS) { - jclass clazz; + void *jfield = (uint8_t*)jfields + jfields_mapping[i].offset; + if (type == MP_JNI_CLASS) { last_clazz = NULL; - clazz = (*env)->FindClass(env, jfields_mapping[i].name); - if ((ret = mp_jni_exception_check(env, mandatory, log)) < 0 && mandatory) { - goto done; - } + jclass clazz = (*env)->FindClass(env, jfields_mapping[i].name); + CHECK_EXC_MANDATORY(); - last_clazz = *(jclass*)((uint8_t*)jfields + jfields_mapping[i].offset) = + last_clazz = *(jclass*)jfield = global ? (*env)->NewGlobalRef(env, clazz) : clazz; - if (global) { - (*env)->DeleteLocalRef(env, clazz); - } - - } else { + if (global) + MP_JNI_LOCAL_FREEP(&clazz); - if (!last_clazz) { - ret = -1; - break; - } + continue; + } - switch(type) { - case MP_JNI_FIELD: { - jfieldID field_id = (*env)->GetFieldID(env, last_clazz, jfields_mapping[i].method, jfields_mapping[i].signature); - if ((ret = mp_jni_exception_check(env, mandatory, log)) < 0 && mandatory) { - goto done; - } + if (!last_clazz) { + ret = -1; + break; + } - *(jfieldID*)((uint8_t*)jfields + jfields_mapping[i].offset) = field_id; - break; - } - case MP_JNI_STATIC_FIELD_AS_INT: - case MP_JNI_STATIC_FIELD: { - jfieldID field_id = (*env)->GetStaticFieldID(env, last_clazz, jfields_mapping[i].method, jfields_mapping[i].signature); - if ((ret = mp_jni_exception_check(env, mandatory, log)) < 0 && mandatory) { - goto done; - } + switch (type) { + case MP_JNI_FIELD: { + jfieldID field_id = (*env)->GetFieldID(env, last_clazz, + jfields_mapping[i].name, jfields_mapping[i].signature); + CHECK_EXC_MANDATORY(); - if (type == MP_JNI_STATIC_FIELD_AS_INT) { - if (field_id) { - jint value = (*env)->GetStaticIntField(env, last_clazz, field_id); - if ((ret = mp_jni_exception_check(env, mandatory, log)) < 0 && mandatory) { - goto done; - } - *(jint*)((uint8_t*)jfields + jfields_mapping[i].offset) = value; - } - } else { - *(jfieldID*)((uint8_t*)jfields + jfields_mapping[i].offset) = field_id; - } - break; - } - case MP_JNI_METHOD: { - jmethodID method_id = (*env)->GetMethodID(env, last_clazz, jfields_mapping[i].method, jfields_mapping[i].signature); - if ((ret = mp_jni_exception_check(env, mandatory, log)) < 0 && mandatory) { - goto done; + *(jfieldID*)jfield = field_id; + break; + } + case MP_JNI_STATIC_FIELD_AS_INT: + case MP_JNI_STATIC_FIELD: { + jfieldID field_id = (*env)->GetStaticFieldID(env, last_clazz, + jfields_mapping[i].name, jfields_mapping[i].signature); + CHECK_EXC_MANDATORY(); + + if (type == MP_JNI_STATIC_FIELD_AS_INT) { + if (field_id) { + jint value = (*env)->GetStaticIntField(env, last_clazz, field_id); + CHECK_EXC_MANDATORY(); + *(jint*)jfield = value; } - - *(jmethodID*)((uint8_t*)jfields + jfields_mapping[i].offset) = method_id; - break; + } else { + *(jfieldID*)jfield = field_id; } - case MP_JNI_STATIC_METHOD: { - jmethodID method_id = (*env)->GetStaticMethodID(env, last_clazz, jfields_mapping[i].method, jfields_mapping[i].signature); - if ((ret = mp_jni_exception_check(env, mandatory, log)) < 0 && mandatory) { - goto done; - } + break; + } + case MP_JNI_METHOD: { + jmethodID method_id = (*env)->GetMethodID(env, last_clazz, + jfields_mapping[i].name, jfields_mapping[i].signature); + CHECK_EXC_MANDATORY(); - *(jmethodID*)((uint8_t*)jfields + jfields_mapping[i].offset) = method_id; - break; - } - default: - mp_err(log, "Unknown JNI field type\n"); - ret = -1; - goto done; - } + *(jmethodID*)jfield = method_id; + break; + } + case MP_JNI_STATIC_METHOD: { + jmethodID method_id = (*env)->GetStaticMethodID(env, last_clazz, + jfields_mapping[i].name, jfields_mapping[i].signature); + CHECK_EXC_MANDATORY(); - ret = 0; + *(jmethodID*)jfield = method_id; + break; + } + default: + mp_err(log, "Unknown JNI field type\n"); + ret = -1; + goto done; } + + ret = 0; } done: @@ -378,48 +337,43 @@ done: return ret; } -int mp_jni_reset_jfields(JNIEnv *env, void *jfields, const struct MPJniField *jfields_mapping, int global, struct mp_log *log) -{ - int i; +#undef CHECK_EXC_MANDATORY - for (i = 0; jfields_mapping[i].name; i++) { +int mp_jni_reset_jfields(JNIEnv *env, void *jfields, + const struct MPJniField *jfields_mapping, + int global, struct mp_log *log) +{ + for (int i = 0; jfields_mapping[i].name; i++) { enum MPJniFieldType type = jfields_mapping[i].type; - switch(type) { + void *jfield = (uint8_t*)jfields + jfields_mapping[i].offset; + + switch (type) { case MP_JNI_CLASS: { - jclass clazz = *(jclass*)((uint8_t*)jfields + jfields_mapping[i].offset); + jclass clazz = *(jclass*)jfield; if (!clazz) continue; if (global) { - (*env)->DeleteGlobalRef(env, clazz); + MP_JNI_GLOBAL_FREEP(&clazz); } else { - (*env)->DeleteLocalRef(env, clazz); + MP_JNI_LOCAL_FREEP(&clazz); } - *(jclass*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL; - break; - } - case MP_JNI_FIELD: { - *(jfieldID*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL; - break; - } - case MP_JNI_STATIC_FIELD: { - *(jfieldID*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL; + *(jclass*)jfield = NULL; break; } - case MP_JNI_STATIC_FIELD_AS_INT: { - *(jint*)((uint8_t*)jfields + jfields_mapping[i].offset) = 0; + case MP_JNI_FIELD: + case MP_JNI_STATIC_FIELD: + *(jfieldID*)jfield = NULL; break; - } - case MP_JNI_METHOD: { - *(jmethodID*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL; + case MP_JNI_STATIC_FIELD_AS_INT: + *(jint*)jfield = 0; break; - } - case MP_JNI_STATIC_METHOD: { - *(jmethodID*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL; + case MP_JNI_METHOD: + case MP_JNI_STATIC_METHOD: + *(jmethodID*)jfield = NULL; break; - } default: mp_err(log, "Unknown JNI field type\n"); } @@ -22,6 +22,7 @@ #ifndef MP_JNI_H #define MP_JNI_H +#include <stdbool.h> #include <jni.h> #include "common/msg.h" @@ -29,7 +30,7 @@ #define MP_JNI_GET_ENV(obj) mp_jni_get_env((obj)->log) #define MP_JNI_EXCEPTION_CHECK() mp_jni_exception_check(env, 0, NULL) #define MP_JNI_EXCEPTION_LOG(obj) mp_jni_exception_check(env, 1, (obj)->log) -#define MP_JNI_DO(what, obj, method, ...) (*env)->what(env, obj, method, ##__VA_ARGS__) +#define MP_JNI_DO(what, obj, ...) (*env)->what(env, obj, ##__VA_ARGS__) #define MP_JNI_NEW(clazz, method, ...) MP_JNI_DO(NewObject, clazz, method, ##__VA_ARGS__) #define MP_JNI_CALL_INT(obj, method, ...) MP_JNI_DO(CallIntMethod, obj, method, ##__VA_ARGS__) #define MP_JNI_CALL_BOOL(obj, method, ...) MP_JNI_DO(CallBooleanMethod, obj, method, ##__VA_ARGS__) @@ -39,6 +40,16 @@ #define MP_JNI_GET_INT(obj, field) MP_JNI_DO(GetIntField, obj, field) #define MP_JNI_GET_LONG(obj, field) MP_JNI_DO(GetLongField, obj, field) #define MP_JNI_GET_BOOL(obj, field) MP_JNI_DO(GetBoolField, obj, field) +#define MP_JNI_LOCAL_FREEP(objp) do { \ + if (*(objp)) \ + MP_JNI_DO(DeleteLocalRef, *(objp)); \ + *(objp) = NULL; \ + } while (0) +#define MP_JNI_GLOBAL_FREEP(objp) do { \ + if (*(objp)) \ + MP_JNI_DO(DeleteGlobalRef, *(objp)); \ + *(objp) = NULL; \ + } while (0) /* * Attach permanently a JNI environment to the current thread and retrieve it. @@ -118,11 +129,10 @@ enum MPJniFieldType { struct MPJniField { const char *name; - const char *method; const char *signature; enum MPJniFieldType type; int offset; - int mandatory; + bool mandatory; }; diff --git a/misc/path_utils.c b/misc/path_utils.c new file mode 100644 index 0000000..14b4a97 --- /dev/null +++ b/misc/path_utils.c @@ -0,0 +1,219 @@ +/* + * Path utility functions + * + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdbool.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> + +#include "config.h" + +#include "mpv_talloc.h" +#include "osdep/io.h" +#include "misc/ctype.h" +#include "misc/path_utils.h" + +char *mp_basename(const char *path) +{ + char *s; + +#if HAVE_DOS_PATHS + if (!mp_is_url(bstr0(path))) { + s = strrchr(path, '\\'); + if (s) + path = s + 1; + s = strrchr(path, ':'); + if (s) + path = s + 1; + } +#endif + s = strrchr(path, '/'); + return s ? s + 1 : (char *)path; +} + +struct bstr mp_dirname(const char *path) +{ + struct bstr ret = { + (uint8_t *)path, mp_basename(path) - path + }; + if (ret.len == 0) + return bstr0("."); + return ret; +} + + +#if HAVE_DOS_PATHS +static const char mp_path_separators[] = "\\/"; +#else +static const char mp_path_separators[] = "/"; +#endif + +// Mutates path and removes a trailing '/' (or '\' on Windows) +void mp_path_strip_trailing_separator(char *path) +{ + size_t len = strlen(path); + if (len > 0 && strchr(mp_path_separators, path[len - 1])) + path[len - 1] = '\0'; +} + +char *mp_splitext(const char *path, bstr *root) +{ + assert(path); + int skip = (*path == '.'); // skip leading dot for "hidden" unix files + const char *split = strrchr(path + skip, '.'); + if (!split || !split[1] || strchr(split, '/')) + return NULL; + if (root) + *root = (bstr){(char *)path, split - path}; + return (char *)split + 1; +} + +bool mp_path_is_absolute(struct bstr path) +{ + if (path.len && strchr(mp_path_separators, path.start[0])) + return true; + +#if HAVE_DOS_PATHS + // Note: "X:filename" is a path relative to the current working directory + // of drive X, and thus is not an absolute path. It needs to be + // followed by \ or /. + if (path.len >= 3 && path.start[1] == ':' && + strchr(mp_path_separators, path.start[2])) + return true; +#endif + + return false; +} + +char *mp_path_join_bstr(void *talloc_ctx, struct bstr p1, struct bstr p2) +{ + if (p1.len == 0) + return bstrdup0(talloc_ctx, p2); + if (p2.len == 0) + return bstrdup0(talloc_ctx, p1); + + if (mp_path_is_absolute(p2)) + return bstrdup0(talloc_ctx, p2); + + bool have_separator = strchr(mp_path_separators, p1.start[p1.len - 1]); +#if HAVE_DOS_PATHS + // "X:" only => path relative to "X:" current working directory. + if (p1.len == 2 && p1.start[1] == ':') + have_separator = true; +#endif + + return talloc_asprintf(talloc_ctx, "%.*s%s%.*s", BSTR_P(p1), + have_separator ? "" : "/", BSTR_P(p2)); +} + +char *mp_path_join(void *talloc_ctx, const char *p1, const char *p2) +{ + return mp_path_join_bstr(talloc_ctx, bstr0(p1), bstr0(p2)); +} + +char *mp_getcwd(void *talloc_ctx) +{ + char *e_wd = getenv("PWD"); + if (e_wd) + return talloc_strdup(talloc_ctx, e_wd); + + char *wd = talloc_array(talloc_ctx, char, 20); + while (getcwd(wd, talloc_get_size(wd)) == NULL) { + if (errno != ERANGE) { + talloc_free(wd); + return NULL; + } + wd = talloc_realloc(talloc_ctx, wd, char, talloc_get_size(wd) * 2); + } + return wd; +} + +char *mp_normalize_path(void *talloc_ctx, const char *path) +{ + if (mp_is_url(bstr0(path))) + return talloc_strdup(talloc_ctx, path); + + return mp_path_join(talloc_ctx, mp_getcwd(talloc_ctx), path); +} + +bool mp_path_exists(const char *path) +{ + struct stat st; + return path && stat(path, &st) == 0; +} + +bool mp_path_isdir(const char *path) +{ + struct stat st; + return stat(path, &st) == 0 && S_ISDIR(st.st_mode); +} + +// Return false if it's considered a normal local filesystem path. +bool mp_is_url(bstr path) +{ + int proto = bstr_find0(path, "://"); + if (proto < 1) + return false; + // Per RFC3986, the first character of the protocol must be alphabetic. + // The rest must be alphanumeric plus -, + and . + for (int i = 0; i < proto; i++) { + unsigned char c = path.start[i]; + if ((i == 0 && !mp_isalpha(c)) || + (!mp_isalnum(c) && c != '.' && c != '-' && c != '+')) + { + return false; + } + } + return true; +} + +// Return the protocol part of path, e.g. "http" if path is "http://...". +// On success, out_url (if not NULL) is set to the part after the "://". +bstr mp_split_proto(bstr path, bstr *out_url) +{ + if (!mp_is_url(path)) + return (bstr){0}; + bstr r; + bstr_split_tok(path, "://", &r, out_url ? out_url : &(bstr){0}); + return r; +} + +void mp_mkdirp(const char *dir) +{ + char *path = talloc_strdup(NULL, dir); + char *cdir = path + 1; + + while (cdir) { + cdir = strchr(cdir, '/'); + if (cdir) + *cdir = 0; + + mkdir(path, 0700); + + if (cdir) + *cdir++ = '/'; + } + + talloc_free(path); +} diff --git a/misc/path_utils.h b/misc/path_utils.h new file mode 100644 index 0000000..1e6c16a --- /dev/null +++ b/misc/path_utils.h @@ -0,0 +1,64 @@ +/* + * Path utility functions + * + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stdbool.h> +#include "misc/bstr.h" + +// Return pointer to filename part of path + +char *mp_basename(const char *path); + +/* Return file extension, excluding the '.'. If root is not NULL, set it to the + * part of the path without extension. So: path == root + "." + extension + * Don't consider it a file extension if the only '.' is the first character. + * Return NULL if no extension and don't set *root in this case. + */ +char *mp_splitext(const char *path, bstr *root); + +/* Return struct bstr referencing directory part of path, or if that + * would be empty, ".". + */ +struct bstr mp_dirname(const char *path); + +void mp_path_strip_trailing_separator(char *path); + +/* Join two path components and return a newly allocated string + * for the result. '/' is inserted between the components if needed. + * If p2 is an absolute path then the value of p1 is ignored. + */ +char *mp_path_join(void *talloc_ctx, const char *p1, const char *p2); +char *mp_path_join_bstr(void *talloc_ctx, struct bstr p1, struct bstr p2); + +// Return whether the path is absolute. +bool mp_path_is_absolute(struct bstr path); + +char *mp_getcwd(void *talloc_ctx); + +char *mp_normalize_path(void *talloc_ctx, const char *path); + +bool mp_path_exists(const char *path); +bool mp_path_isdir(const char *path); + +bool mp_is_url(bstr path); + +bstr mp_split_proto(bstr path, bstr *out_url); + +void mp_mkdirp(const char *dir); diff --git a/misc/rendezvous.c b/misc/rendezvous.c index 1fe5724..cb1cde6 100644 --- a/misc/rendezvous.c +++ b/misc/rendezvous.c @@ -28,7 +28,7 @@ struct waiter { * of _all_ waiters in the process, and temporarily wakes up _all_ waiters on * each second call). * - * This is inspired by: http://9atom.org/magic/man2html/2/rendezvous */ + * This is inspired by: https://man.cat-v.org/plan_9/2/rendezvous */ intptr_t mp_rendezvous(void *tag, intptr_t value) { struct waiter wait = { .tag = tag, .value = &value }; diff --git a/options/m_config.h b/options/m_config.h index d2ce2b4..a6bade9 100644 --- a/options/m_config.h +++ b/options/m_config.h @@ -1 +1 @@ -#include "m_config_core.h"
\ No newline at end of file +#include "m_config_core.h" diff --git a/options/m_config_frontend.c b/options/m_config_frontend.c index 9b54389..d800cdb 100644 --- a/options/m_config_frontend.c +++ b/options/m_config_frontend.c @@ -871,7 +871,7 @@ void m_config_print_option_list(const struct m_config *config, const char *name) if (!defptr) defptr = &m_option_value_default; if (defptr) - def = m_option_pretty_print(opt, defptr); + def = m_option_pretty_print(opt, defptr, false); if (def) { MP_INFO(config, " (default: %s)", def); talloc_free(def); diff --git a/options/m_option.c b/options/m_option.c index 1b1ac0a..4646510 100644 --- a/options/m_option.c +++ b/options/m_option.c @@ -1023,17 +1023,12 @@ static char *print_double(const m_option_t *opt, const void *val) return talloc_asprintf(NULL, "%f", f); } -static char *print_double_7g(const m_option_t *opt, const void *val) +static char *pretty_print_double(const m_option_t *opt, const void *val) { double f = VAL(val); if (isnan(f)) return print_double(opt, val); - // Truncate anything < 1e-4 to avoid switching to scientific notation - if (fabs(f) < 1e-4) { - return talloc_strdup(NULL, "0"); - } else { - return talloc_asprintf(NULL, "%.7g", f); - } + return mp_format_double(NULL, f, 4, false, false, !(opt->flags & M_OPT_FIXED_LEN_PRINT)); } static void add_double(const m_option_t *opt, void *val, double add, bool wrap) @@ -1105,7 +1100,7 @@ const m_option_type_t m_option_type_double = { .size = sizeof(double), .parse = parse_double, .print = print_double, - .pretty_print = print_double_7g, + .pretty_print = pretty_print_double, .copy = copy_opt, .add = add_double, .multiply = multiply_double, @@ -1131,7 +1126,7 @@ const m_option_type_t m_option_type_aspect = { .flags = M_OPT_TYPE_CHOICE | M_OPT_TYPE_USES_RANGE, .parse = parse_double_aspect, .print = print_double, - .pretty_print = print_double_7g, + .pretty_print = pretty_print_double, .copy = copy_opt, .add = add_double, .multiply = multiply_double, @@ -1159,10 +1154,10 @@ static char *print_float(const m_option_t *opt, const void *val) return print_double(opt, &tmp); } -static char *print_float_f3(const m_option_t *opt, const void *val) +static char *pretty_print_float(const m_option_t *opt, const void *val) { double tmp = VAL(val); - return print_double_7g(opt, &tmp); + return pretty_print_double(opt, &tmp); } static void add_float(const m_option_t *opt, void *val, double add, bool wrap) @@ -1207,7 +1202,7 @@ const m_option_type_t m_option_type_float = { .size = sizeof(float), .parse = parse_float, .print = print_float, - .pretty_print = print_float_f3, + .pretty_print = pretty_print_float, .copy = copy_opt, .add = add_float, .multiply = multiply_float, @@ -1517,6 +1512,7 @@ static char *print_str_list(const m_option_t *opt, const void *src) { char **lst = NULL; char *ret = NULL; + const char sep = opt->priv ? *(char *)opt->priv : OPTION_LIST_SEPARATOR; if (!(src && VAL(src))) return talloc_strdup(NULL, ""); @@ -1524,7 +1520,7 @@ static char *print_str_list(const m_option_t *opt, const void *src) for (int i = 0; lst[i]; i++) { if (ret) - ret = talloc_strdup_append_buffer(ret, ","); + ret = talloc_strndup_append_buffer(ret, &sep, 1); ret = talloc_strdup_append_buffer(ret, lst[i]); } return ret; @@ -2826,8 +2822,7 @@ static char *print_rel_time(const m_option_t *opt, const void *val) case REL_TIME_ABSOLUTE: return talloc_asprintf(NULL, "%g", t->pos); case REL_TIME_RELATIVE: - return talloc_asprintf(NULL, "%s%g", - (t->pos >= 0) ? "+" : "-", fabs(t->pos)); + return talloc_asprintf(NULL, "%+g", t->pos); case REL_TIME_CHAPTER: return talloc_asprintf(NULL, "#%g", t->pos); case REL_TIME_PERCENT: @@ -3398,15 +3393,21 @@ static int parse_obj_settings_list(struct mp_log *log, const m_option_t *opt, if (r == 0) { r = parse_obj_settings(log, name, op, ¶m, ol, dst ? &res : NULL); } - if (r < 0) + if (r < 0) { + free_obj_settings_list(&res); return r; + } if (param.len > 0) { const char sep[2] = {OPTION_LIST_SEPARATOR, 0}; - if (!bstr_eatstart0(¶m, sep)) + if (!bstr_eatstart0(¶m, sep)) { + free_obj_settings_list(&res); return M_OPT_INVALID; + } if (param.len == 0) { - if (!ol->allow_trailer) + if (!ol->allow_trailer) { + free_obj_settings_list(&res); return M_OPT_INVALID; + } if (dst) { m_obj_settings_t item = { .name = talloc_strdup(NULL, ""), @@ -3421,6 +3422,7 @@ static int parse_obj_settings_list(struct mp_log *log, const m_option_t *opt, if (op == OP_APPEND) { mp_err(log, "Option %.*s: -append takes only 1 filter (no ',').\n", BSTR_P(name)); + free_obj_settings_list(&res); return M_OPT_INVALID; } mp_warn(log, "Passing more than 1 argument to %.*s is deprecated!\n", @@ -3784,7 +3786,7 @@ static void dup_node(void *ta_parent, struct mpv_node *node) static void copy_node(const m_option_t *opt, void *dst, const void *src) { - assert(sizeof(struct mpv_node) <= sizeof(union m_option_value)); + static_assert(sizeof(struct mpv_node) <= sizeof(union m_option_value), ""); if (!(dst && src)) return; diff --git a/options/m_option.h b/options/m_option.h index e62fa0f..530c0a3 100644 --- a/options/m_option.h +++ b/options/m_option.h @@ -189,15 +189,20 @@ struct m_opt_choice_alternatives { const char *m_opt_choice_str(const struct m_opt_choice_alternatives *choices, int value); -// 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, 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); - +#define OPT_FUNC(name) name +#define OPT_FUNC_IN(name, suffix) name ## _ ## suffix +#define OPT_VALIDATE_FUNC(func, value_type, suffix) \ +int OPT_FUNC(func)(struct mp_log *log, const m_option_t *opt, \ + struct bstr name, value_type value); \ +static inline int OPT_FUNC_IN(func, suffix)(struct mp_log *log, const m_option_t *opt, \ + struct bstr name, void *value) { \ + return OPT_FUNC(func)(log, opt, name, value); \ +} \ +int OPT_FUNC(func)(struct mp_log *log, const m_option_t *opt, \ + struct bstr name, value_type value) // m_option.priv points to this if OPT_SUBSTRUCT is used struct m_sub_options { @@ -442,7 +447,8 @@ char *format_file_size(int64_t size); #define UPDATE_DVB_PROG (1 << 21) // some --dvbin-... #define UPDATE_SUB_HARD (1 << 22) // subtitle opts. that need full reinit #define UPDATE_SUB_EXTS (1 << 23) // update internal list of sub exts -#define UPDATE_OPT_LAST (1 << 23) +#define UPDATE_VIDEO (1 << 24) // force redraw if needed +#define UPDATE_OPT_LAST (1 << 24) // All bits between _FIRST and _LAST (inclusive) #define UPDATE_OPTS_MASK \ @@ -457,6 +463,9 @@ char *format_file_size(int64_t size); // type channels: disallow "auto" (still accept ""), limit list to at most 1 item. #define M_OPT_CHANNELS_LIMITED (1 << 27) +// type_float/type_double: controls if pretty print should trim trailing zeros +#define M_OPT_FIXED_LEN_PRINT (1 << 28) + // Like M_OPT_TYPE_OPTIONAL_PARAM. #define M_OPT_OPTIONAL_PARAM (1 << 30) @@ -530,12 +539,16 @@ static inline char *m_option_print(const m_option_t *opt, const void *val_ptr) } static inline char *m_option_pretty_print(const m_option_t *opt, - const void *val_ptr) + const void *val_ptr, + bool fixed_len) { + m_option_t o = *opt; + if (fixed_len) + o.flags |= M_OPT_FIXED_LEN_PRINT; if (opt->type->pretty_print) - return opt->type->pretty_print(opt, val_ptr); + return opt->type->pretty_print(&o, val_ptr); else - return m_option_print(opt, val_ptr); + return m_option_print(&o, val_ptr); } // Helper around \ref m_option_type::copy. @@ -684,15 +697,17 @@ 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_FUNC(func) OPT_VALIDATE_FUNC(func, const int *, int) + #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) + .validate = OPT_FUNC_IN(validate_fn, int) + +#define OPT_STRING_VALIDATE_FUNC(func) OPT_VALIDATE_FUNC(func, const char **, str) #define OPT_STRING_VALIDATE(field, validate_fn) \ OPT_TYPED_FIELD(m_option_type_string, char*, field), \ - .validate = (m_opt_generic_validate_fn) \ - MP_EXPECT_TYPE(m_opt_string_validate_fn, validate_fn) + .validate = OPT_FUNC_IN(validate_fn, str) #define M_CHOICES(...) \ .priv = (void *)&(const struct m_opt_choice_alternatives[]){ __VA_ARGS__, {0}} diff --git a/options/m_property.c b/options/m_property.c index 1b76f05..eb3f78e 100644 --- a/options/m_property.c +++ b/options/m_property.c @@ -108,13 +108,14 @@ int m_property_do(struct mp_log *log, const struct m_property *prop_list, assert(opt.type); switch (action) { + case M_PROPERTY_FIXED_LEN_PRINT: case M_PROPERTY_PRINT: { - if ((r = do_action(prop_list, name, M_PROPERTY_PRINT, arg, ctx)) >= 0) + if ((r = do_action(prop_list, name, action, arg, ctx)) >= 0) return r; // Fallback to m_option if ((r = do_action(prop_list, name, M_PROPERTY_GET, &val, ctx)) <= 0) return r; - char *str = m_option_pretty_print(&opt, &val); + char *str = m_option_pretty_print(&opt, &val, action == M_PROPERTY_FIXED_LEN_PRINT); m_option_free(&opt, &val); *(char **)arg = str; return str != NULL; @@ -258,11 +259,13 @@ static int expand_property(const struct m_property *prop_list, char **ret, bool cond_no = !cond_yes && bstr_eatstart0(&prop, "!"); bool test = cond_yes || cond_no; bool raw = bstr_eatstart0(&prop, "="); + bool fixed_len = !raw && bstr_eatstart0(&prop, ">"); bstr comp_with = {0}; bool comp = test && bstr_split_tok(prop, "==", &prop, &comp_with); if (test && !comp) raw = true; int method = raw ? M_PROPERTY_GET_STRING : M_PROPERTY_PRINT; + method = fixed_len ? M_PROPERTY_FIXED_LEN_PRINT : method; char *s = NULL; int r = m_property_do_bstr(prop_list, prop, method, &s, ctx); diff --git a/options/m_property.h b/options/m_property.h index 0dce246..9fa01e0 100644 --- a/options/m_property.h +++ b/options/m_property.h @@ -48,6 +48,12 @@ enum mp_property_action { // arg: char** M_PROPERTY_PRINT, + // Get human readable fixed length string representing the current value. + // If unimplemented, the property wrapper uses the property type as + // fallback. + // arg: char** + M_PROPERTY_FIXED_LEN_PRINT, + // Like M_PROPERTY_GET_TYPE, but get a type that is compatible to the real // type, but reflect practical limits, such as runtime-available values. // This is mostly used for "UI" related things. diff --git a/options/options.c b/options/options.c index 7c6ffa5..8640ecb 100644 --- a/options/options.c +++ b/options/options.c @@ -42,6 +42,7 @@ #include "input/event.h" #include "stream/stream.h" #include "video/csputils.h" +#include "video/filter/refqueue.h" #include "video/hwdec.h" #include "video/image_writer.h" #include "sub/osd.h" @@ -75,6 +76,7 @@ extern const struct m_sub_options input_config; extern const struct m_sub_options encode_config; extern const struct m_sub_options ra_ctx_conf; extern const struct m_sub_options gl_video_conf; +extern const struct m_sub_options gl_next_conf; extern const struct m_sub_options ao_alsa_conf; extern const struct m_sub_options demux_conf; @@ -111,7 +113,9 @@ static const m_option_t mp_vo_opt_list[] = { {"vo", OPT_SETTINGSLIST(video_driver_list, &vo_obj_list)}, {"taskbar-progress", OPT_BOOL(taskbar_progress)}, {"drag-and-drop", OPT_CHOICE(drag_and_drop, {"no", -2}, {"auto", -1}, - {"replace", DND_REPLACE}, {"append", DND_APPEND})}, + {"replace", DND_REPLACE}, + {"append", DND_APPEND}, + {"insert-next", DND_INSERT_NEXT})}, {"snap-window", OPT_BOOL(snap_window)}, {"ontop", OPT_BOOL(ontop)}, {"ontop-level", OPT_CHOICE(ontop_level, {"window", -1}, {"system", -2}, @@ -127,7 +131,8 @@ static const m_option_t mp_vo_opt_list[] = { {"window-scale", OPT_DOUBLE(window_scale), M_RANGE(0.001, 100)}, {"window-minimized", OPT_BOOL(window_minimized)}, {"window-maximized", OPT_BOOL(window_maximized)}, - {"focus-on-open", OPT_BOOL(focus_on_open)}, + {"focus-on-open", OPT_REMOVED("Replaced by --focus-on")}, + {"focus-on", OPT_CHOICE(focus_on, {"never", 0}, {"open", 1}, {"all", 2})}, {"force-render", OPT_BOOL(force_render)}, {"force-window-position", OPT_BOOL(force_window_position)}, {"x11-name", OPT_STRING(winname)}, @@ -236,7 +241,6 @@ const struct m_sub_options vo_sub_opts = { .auto_window_resize = true, .keepaspect = true, .keepaspect_window = true, - .hidpi_window_scale = true, .native_fs = true, .taskbar_progress = true, .border = true, @@ -251,7 +255,7 @@ const struct m_sub_options vo_sub_opts = { .ontop_level = -1, .timing_offset = 0.050, .swapchain_depth = 3, - .focus_on_open = true, + .focus_on = 1, }, }; @@ -262,6 +266,7 @@ const struct m_sub_options mp_sub_filter_opts = { .opts = (const struct m_option[]){ {"sub-filter-sdh", OPT_BOOL(sub_filter_SDH)}, {"sub-filter-sdh-harder", OPT_BOOL(sub_filter_SDH_harder)}, + {"sub-filter-sdh-enclosures", OPT_STRING(sub_filter_SDH_enclosures)}, {"sub-filter-regex-enable", OPT_BOOL(rf_enable)}, {"sub-filter-regex-plain", OPT_BOOL(rf_plain)}, {"sub-filter-regex", OPT_STRINGLIST(rf_items)}, @@ -271,6 +276,7 @@ const struct m_sub_options mp_sub_filter_opts = { }, .size = sizeof(OPT_BASE_STRUCT), .defaults = &(OPT_BASE_STRUCT){ + .sub_filter_SDH_enclosures = "([\uFF08", .rf_enable = true, }, .change_flags = UPDATE_SUB_FILT, @@ -281,18 +287,14 @@ const struct m_sub_options mp_sub_filter_opts = { const struct m_sub_options mp_subtitle_sub_opts = { .opts = (const struct m_option[]){ - {"sub-delay", OPT_FLOAT(sub_delay)}, {"sub-fps", OPT_FLOAT(sub_fps)}, {"sub-speed", OPT_FLOAT(sub_speed)}, - {"sub-visibility", OPT_BOOL(sub_visibility)}, - {"secondary-sub-visibility", OPT_BOOL(sec_sub_visibility)}, {"sub-forced-events-only", OPT_BOOL(sub_forced_events_only)}, {"stretch-dvd-subs", OPT_BOOL(stretch_dvd_subs)}, {"stretch-image-subs-to-screen", OPT_BOOL(stretch_image_subs)}, {"image-subs-video-resolution", OPT_BOOL(image_subs_video_res)}, {"sub-fix-timing", OPT_BOOL(sub_fix_timing)}, {"sub-stretch-durations", OPT_BOOL(sub_stretch_durations)}, - {"sub-pos", OPT_FLOAT(sub_pos), M_RANGE(0.0, 150.0)}, {"sub-gauss", OPT_FLOAT(sub_gauss), M_RANGE(0.0, 3.0)}, {"sub-gray", OPT_BOOL(sub_gray)}, {"sub-ass", OPT_BOOL(ass_enabled), .flags = UPDATE_SUB_HARD}, @@ -315,35 +317,29 @@ const struct m_sub_options mp_subtitle_sub_opts = { {"sub-ass-shaper", OPT_CHOICE(ass_shaper, {"simple", 0}, {"complex", 1})}, {"sub-ass-justify", OPT_BOOL(ass_justify)}, - {"sub-ass-override", OPT_CHOICE(ass_style_override, - {"no", 0}, {"yes", 1}, {"force", 3}, {"scale", 4}, {"strip", 5}), - .flags = UPDATE_SUB_HARD}, {"sub-scale-by-window", OPT_BOOL(sub_scale_by_window)}, {"sub-scale-with-window", OPT_BOOL(sub_scale_with_window)}, {"sub-ass-scale-with-window", OPT_BOOL(ass_scale_with_window)}, {"sub", OPT_SUBSTRUCT(sub_style, sub_style_conf)}, {"sub-clear-on-seek", OPT_BOOL(sub_clear_on_seek)}, - {"teletext-page", OPT_INT(teletext_page), M_RANGE(1, 999)}, + {"teletext-page", OPT_INT(teletext_page), M_RANGE(-1, 999)}, {"sub-past-video-end", OPT_BOOL(sub_past_video_end)}, {"sub-ass-force-style", OPT_REPLACED("sub-ass-style-overrides")}, + {"sub-lavc-o", OPT_KEYVALUELIST(sub_avopts)}, {0} }, .size = sizeof(OPT_BASE_STRUCT), .defaults = &(OPT_BASE_STRUCT){ - .sub_visibility = true, - .sec_sub_visibility = true, - .sub_pos = 100, .sub_speed = 1.0, .ass_enabled = true, .sub_scale_by_window = true, .sub_use_margins = true, .sub_scale_with_window = true, - .teletext_page = 100, + .teletext_page = 0, .sub_scale = 1, .ass_vsfilter_aspect_compat = true, .ass_vsfilter_color_compat = 1, .ass_vsfilter_blur_compat = true, - .ass_style_override = 1, .ass_shaper = 1, .use_embedded_fonts = true, }, @@ -351,6 +347,36 @@ const struct m_sub_options mp_subtitle_sub_opts = { }; #undef OPT_BASE_STRUCT +#define OPT_BASE_STRUCT struct mp_subtitle_shared_opts + +const struct m_sub_options mp_subtitle_shared_sub_opts = { + .opts = (const struct m_option[]){ + {"sub-delay", OPT_FLOAT(sub_delay[0])}, + {"secondary-sub-delay", OPT_FLOAT(sub_delay[1])}, + {"sub-pos", OPT_FLOAT(sub_pos[0]), M_RANGE(0.0, 150.0)}, + {"secondary-sub-pos", OPT_FLOAT(sub_pos[1]), M_RANGE(0.0, 150.0)}, + {"sub-visibility", OPT_BOOL(sub_visibility[0])}, + {"secondary-sub-visibility", OPT_BOOL(sub_visibility[1])}, + {"sub-ass-override", OPT_CHOICE(ass_style_override[0], + {"no", 0}, {"yes", 1}, {"force", 3}, {"scale", 4}, {"strip", 5}), + .flags = UPDATE_SUB_HARD}, + {"secondary-sub-ass-override", OPT_CHOICE(ass_style_override[1], + {"no", 0}, {"yes", 1}, {"force", 3}, {"scale", 4}, {"strip", 5}), + .flags = UPDATE_SUB_HARD}, + {0} + }, + .size = sizeof(OPT_BASE_STRUCT), + .defaults = &(OPT_BASE_STRUCT){ + .sub_visibility[0] = true, + .sub_visibility[1] = true, + .sub_pos[0] = 100, + .ass_style_override[0] = 1, + .ass_style_override[1] = 5, + }, + .change_flags = UPDATE_OSD, +}; + +#undef OPT_BASE_STRUCT #define OPT_BASE_STRUCT struct mp_osd_render_opts const struct m_sub_options mp_osd_render_sub_opts = { @@ -359,6 +385,7 @@ const struct m_sub_options mp_osd_render_sub_opts = { {"osd-bar-align-y", OPT_FLOAT(osd_bar_align_y), M_RANGE(-1.0, +1.0)}, {"osd-bar-w", OPT_FLOAT(osd_bar_w), M_RANGE(1, 100)}, {"osd-bar-h", OPT_FLOAT(osd_bar_h), M_RANGE(0.1, 50)}, + {"osd-bar-border-size", OPT_FLOAT(osd_bar_border_size), M_RANGE(0, 1000.0)}, {"osd", OPT_SUBSTRUCT(osd_style, osd_style_conf)}, {"osd-scale", OPT_FLOAT(osd_scale), M_RANGE(0, 100)}, {"osd-scale-by-window", OPT_BOOL(osd_scale_by_window)}, @@ -370,6 +397,7 @@ const struct m_sub_options mp_osd_render_sub_opts = { .osd_bar_align_y = 0.5, .osd_bar_w = 75.0, .osd_bar_h = 3.125, + .osd_bar_border_size = 0.5, .osd_scale = 1, .osd_scale_by_window = true, }, @@ -412,10 +440,18 @@ const struct m_sub_options dvd_conf = { const struct m_sub_options filter_conf = { .opts = (const struct m_option[]){ - {"deinterlace", OPT_BOOL(deinterlace)}, + {"deinterlace", OPT_CHOICE(deinterlace, + {"no", 0}, {"yes", 1}, {"auto", -1})}, + {"deinterlace-field-parity", OPT_CHOICE(field_parity, + {"tff", MP_FIELD_PARITY_TFF}, + {"bff", MP_FIELD_PARITY_BFF}, + {"auto", MP_FIELD_PARITY_AUTO})}, {0} }, .size = sizeof(OPT_BASE_STRUCT), + .defaults = &(const struct filter_opts){ + .field_parity = MP_FIELD_PARITY_AUTO, + }, .change_flags = UPDATE_IMGPAR, }; @@ -562,7 +598,8 @@ static const m_option_t mp_opts[] = { {"slang", OPT_STRINGLIST(stream_lang[STREAM_SUB])}, {"vlang", OPT_STRINGLIST(stream_lang[STREAM_VIDEO])}, {"track-auto-selection", OPT_BOOL(stream_auto_sel)}, - {"subs-with-matching-audio", OPT_BOOL(subs_with_matching_audio)}, + {"subs-with-matching-audio", OPT_CHOICE(subs_with_matching_audio, {"no", 0}, + {"forced", 1}, {"yes", 2})}, {"subs-match-os-language", OPT_BOOL(subs_match_os_language)}, {"subs-fallback", OPT_CHOICE(subs_fallback, {"no", 0}, {"default", 1}, {"yes", 2})}, {"subs-fallback-forced", OPT_CHOICE(subs_fallback_forced, {"no", 0}, @@ -662,6 +699,7 @@ static const m_option_t mp_opts[] = { {"cover-art-whitelist", OPT_BOOL(coverart_whitelist)}, {"", OPT_SUBSTRUCT(subs_rend, mp_subtitle_sub_opts)}, + {"", OPT_SUBSTRUCT(subs_shared, mp_subtitle_shared_sub_opts)}, {"", OPT_SUBSTRUCT(subs_filt, mp_sub_filter_opts)}, {"", OPT_SUBSTRUCT(osd_rend, mp_osd_render_sub_opts)}, @@ -680,6 +718,10 @@ static const m_option_t mp_opts[] = { // values <0 for volume and mute are legacy and ignored {"volume", OPT_FLOAT(softvol_volume), .flags = UPDATE_VOL, M_RANGE(-1, 1000)}, + {"volume-gain-max", OPT_FLOAT(softvol_gain_max), M_RANGE(0, 150)}, + {"volume-gain-min", OPT_FLOAT(softvol_gain_min), M_RANGE(-150, 0)}, + {"volume-gain", OPT_FLOAT(softvol_gain), .flags = UPDATE_VOL, + M_RANGE(-150, 150)}, {"mute", OPT_CHOICE(softvol_mute, {"no", 0}, {"auto", 0}, @@ -794,7 +836,6 @@ static const m_option_t mp_opts[] = { {"term-osd-bar", OPT_BOOL(term_osd_bar), .flags = UPDATE_OSD}, {"term-osd-bar-chars", OPT_STRING(term_osd_bar_chars), .flags = UPDATE_OSD}, - {"term-remaining-playtime", OPT_BOOL(term_remaining_playtime), .flags = UPDATE_OSD}, {"term-title", OPT_STRING(term_title), .flags = UPDATE_OSD}, {"term-playing-msg", OPT_STRING(playing_msg)}, @@ -812,6 +853,7 @@ static const m_option_t mp_opts[] = { {"idle", OPT_CHOICE(player_idle_mode, {"no", 0}, {"once", 1}, {"yes", 2})}, + {"input-commands", OPT_STRINGLIST(input_commands)}, {"input-terminal", OPT_BOOL(consolecontrols), .flags = UPDATE_TERM}, {"input-ipc-server", OPT_STRING(ipc_path), .flags = M_OPT_FILE}, @@ -837,6 +879,7 @@ static const m_option_t mp_opts[] = { {"", OPT_SUBSTRUCT(ra_ctx_opts, ra_ctx_conf)}, {"", OPT_SUBSTRUCT(gl_video_opts, gl_video_conf)}, + {"", OPT_SUBSTRUCT(gl_next_opts, gl_next_conf)}, {"", OPT_SUBSTRUCT(spirv_opts, spirv_conf)}, #if HAVE_GL @@ -903,6 +946,9 @@ static const struct MPOpts mp_default_opts = { .msg_color = true, .softvol_max = 130, .softvol_volume = 100, + .softvol_gain_max = 12, + .softvol_gain_min = -96, + .softvol_gain = 0, .gapless_audio = -1, .wintitle = "${?media-title:${media-title}}${!media-title:No file} - mpv", .stop_screensaver = 1, @@ -946,7 +992,6 @@ static const struct MPOpts mp_default_opts = { .frame_dropping = 1, .term_osd = 2, .term_osd_bar_chars = "[-+-]", - .term_remaining_playtime = true, .consolecontrols = true, .playlist_pos = -1, .play_frames = -1, @@ -960,7 +1005,7 @@ static const struct MPOpts mp_default_opts = { [STREAM_VIDEO] = -2, [STREAM_SUB] = -2, }, }, .stream_auto_sel = true, - .subs_with_matching_audio = true, + .subs_with_matching_audio = 2, .subs_match_os_language = true, .subs_fallback = 1, .subs_fallback_forced = 1, @@ -1063,12 +1108,14 @@ static const struct MPOpts mp_default_opts = { "sub-delay", "sub-speed", "sub-pos", + "secondary-sub-pos", "sub-visibility", "sub-scale", "sub-use-margins", "sub-ass-force-margins", "sub-ass-vsfilter-aspect-compat", "sub-ass-override", + "secondary-sub-ass-override", "secondary-sub-visibility", "ab-loop-a", "ab-loop-b", diff --git a/options/options.h b/options/options.h index aa071b2..69a50b3 100644 --- a/options/options.h +++ b/options/options.h @@ -20,7 +20,7 @@ typedef struct mp_vo_opts { bool all_workspaces; bool window_minimized; bool window_maximized; - bool focus_on_open; + int focus_on; int screen_id; char *screen_name; @@ -82,10 +82,6 @@ typedef struct mp_vo_opts { // Subtitle options needed by the subtitle decoders/renderers. struct mp_subtitle_opts { - bool sub_visibility; - bool sec_sub_visibility; - float sub_pos; - float sub_delay; float sub_fps; float sub_speed; bool sub_forced_events_only; @@ -111,18 +107,27 @@ struct mp_subtitle_opts { bool use_embedded_fonts; char **ass_style_override_list; char *ass_styles_file; - int ass_style_override; int ass_hinting; int ass_shaper; bool ass_justify; bool sub_clear_on_seek; int teletext_page; bool sub_past_video_end; + char **sub_avopts; +}; + +// Options for both primary and secondary subs. +struct mp_subtitle_shared_opts { + float sub_delay[2]; + float sub_pos[2]; + bool sub_visibility[2]; + int ass_style_override[2]; }; struct mp_sub_filter_opts { bool sub_filter_SDH; bool sub_filter_SDH_harder; + char *sub_filter_SDH_enclosures; bool rf_enable; bool rf_plain; char **rf_items; @@ -135,6 +140,7 @@ struct mp_osd_render_opts { float osd_bar_align_y; float osd_bar_w; float osd_bar_h; + float osd_bar_border_size; float osd_scale; bool osd_scale_by_window; struct osd_style_opts *osd_style; @@ -181,6 +187,9 @@ typedef struct MPOpts { float rgain_fallback; int softvol_mute; float softvol_max; + float softvol_gain; + float softvol_gain_min; + float softvol_gain_max; int gapless_audio; mp_vo_opts *vo; @@ -196,6 +205,7 @@ typedef struct MPOpts { bool cursor_autohide_fs; struct mp_subtitle_opts *subs_rend; + struct mp_subtitle_shared_opts *subs_shared; struct mp_sub_filter_opts *subs_filt; struct mp_osd_render_opts *osd_rend; @@ -238,7 +248,6 @@ typedef struct MPOpts { int term_osd; bool term_osd_bar; char *term_osd_bar_chars; - bool term_remaining_playtime; char *term_title; char *playing_msg; char *osd_playing_msg; @@ -247,6 +256,7 @@ typedef struct MPOpts { char *osd_status_msg; char *osd_msg[3]; int player_idle_mode; + char **input_commands; bool consolecontrols; int playlist_pos; struct m_rel_time play_start; @@ -273,7 +283,7 @@ typedef struct MPOpts { int stream_id[2][STREAM_TYPE_COUNT]; char **stream_lang[STREAM_TYPE_COUNT]; bool stream_auto_sel; - bool subs_with_matching_audio; + int subs_with_matching_audio; bool subs_match_os_language; int subs_fallback; int subs_fallback_forced; @@ -357,6 +367,7 @@ typedef struct MPOpts { struct ra_ctx_opts *ra_ctx_opts; struct gl_video_opts *gl_video_opts; + struct gl_next_opts *gl_next_opts; struct angle_opts *angle_opts; struct opengl_opts *opengl_opts; struct vulkan_opts *vulkan_opts; @@ -388,13 +399,15 @@ struct dvd_opts { }; struct filter_opts { - bool deinterlace; + int deinterlace; + int field_parity; }; extern const struct m_sub_options vo_sub_opts; extern const struct m_sub_options cuda_conf; extern const struct m_sub_options dvd_conf; extern const struct m_sub_options mp_subtitle_sub_opts; +extern const struct m_sub_options mp_subtitle_shared_sub_opts; extern const struct m_sub_options mp_sub_filter_opts; extern const struct m_sub_options mp_osd_render_sub_opts; extern const struct m_sub_options filter_conf; diff --git a/options/parse_commandline.c b/options/parse_commandline.c index 93120d3..ded8531 100644 --- a/options/parse_commandline.c +++ b/options/parse_commandline.c @@ -103,10 +103,10 @@ static void process_non_option(struct playlist *files, const char *arg) // Glob filenames on Windows (cmd.exe doesn't do this automatically) if (glob(arg, 0, NULL, &gg)) { - playlist_add_file(files, arg); + playlist_append_file(files, arg); } else { for (int i = 0; i < gg.gl_pathc; i++) - playlist_add_file(files, gg.gl_pathv[i]); + playlist_append_file(files, gg.gl_pathv[i]); globfree(&gg); } @@ -114,7 +114,7 @@ static void process_non_option(struct playlist *files, const char *arg) #else static void process_non_option(struct playlist *files, const char *arg) { - playlist_add_file(files, arg); + playlist_append_file(files, arg); } #endif @@ -201,7 +201,6 @@ int m_config_parse_mp_command_line(m_config_t *config, struct playlist *files, goto err_out; } playlist_transfer_entries(files, pl); - playlist_populate_playlist_path(files, param0); talloc_free(param0); talloc_free(pl); continue; diff --git a/options/path.c b/options/path.c index 52dc113..7c7d31c 100644 --- a/options/path.c +++ b/options/path.c @@ -29,6 +29,7 @@ #include "config.h" +#include "misc/path_utils.h" #include "common/common.h" #include "common/global.h" #include "common/msg.h" @@ -42,7 +43,7 @@ // In order of decreasing priority: the first has highest priority. static const mp_get_platform_path_cb path_resolvers[] = { #if HAVE_COCOA - mp_get_platform_path_osx, + mp_get_platform_path_mac, #endif #if HAVE_DARWIN mp_get_platform_path_darwin, @@ -75,10 +76,14 @@ static const char *mp_get_platform_path(void *talloc_ctx, assert(talloc_ctx); if (global->configdir) { + // Return NULL for all platform paths if --no-config is passed + if (!global->configdir[0]) + return NULL; + // force all others to NULL, only first returns the path for (int n = 0; n < MP_ARRAY_SIZE(config_dirs); n++) { if (strcmp(config_dirs[n], type) == 0) - return (n == 0 && global->configdir[0]) ? global->configdir : NULL; + return (n == 0) ? global->configdir : NULL; } } @@ -217,190 +222,6 @@ char *mp_get_user_path(void *talloc_ctx, struct mpv_global *global, return res; } -char *mp_basename(const char *path) -{ - char *s; - -#if HAVE_DOS_PATHS - if (!mp_is_url(bstr0(path))) { - s = strrchr(path, '\\'); - if (s) - path = s + 1; - s = strrchr(path, ':'); - if (s) - path = s + 1; - } -#endif - s = strrchr(path, '/'); - return s ? s + 1 : (char *)path; -} - -struct bstr mp_dirname(const char *path) -{ - struct bstr ret = { - (uint8_t *)path, mp_basename(path) - path - }; - if (ret.len == 0) - return bstr0("."); - return ret; -} - - -#if HAVE_DOS_PATHS -static const char mp_path_separators[] = "\\/"; -#else -static const char mp_path_separators[] = "/"; -#endif - -// Mutates path and removes a trailing '/' (or '\' on Windows) -void mp_path_strip_trailing_separator(char *path) -{ - size_t len = strlen(path); - if (len > 0 && strchr(mp_path_separators, path[len - 1])) - path[len - 1] = '\0'; -} - -char *mp_splitext(const char *path, bstr *root) -{ - assert(path); - int skip = (*path == '.'); // skip leading dot for "hidden" unix files - const char *split = strrchr(path + skip, '.'); - if (!split || !split[1] || strchr(split, '/')) - return NULL; - if (root) - *root = (bstr){(char *)path, split - path}; - return (char *)split + 1; -} - -bool mp_path_is_absolute(struct bstr path) -{ - if (path.len && strchr(mp_path_separators, path.start[0])) - return true; - -#if HAVE_DOS_PATHS - // Note: "X:filename" is a path relative to the current working directory - // of drive X, and thus is not an absolute path. It needs to be - // followed by \ or /. - if (path.len >= 3 && path.start[1] == ':' && - strchr(mp_path_separators, path.start[2])) - return true; -#endif - - return false; -} - -char *mp_path_join_bstr(void *talloc_ctx, struct bstr p1, struct bstr p2) -{ - if (p1.len == 0) - return bstrdup0(talloc_ctx, p2); - if (p2.len == 0) - return bstrdup0(talloc_ctx, p1); - - if (mp_path_is_absolute(p2)) - return bstrdup0(talloc_ctx, p2); - - bool have_separator = strchr(mp_path_separators, p1.start[p1.len - 1]); -#if HAVE_DOS_PATHS - // "X:" only => path relative to "X:" current working directory. - if (p1.len == 2 && p1.start[1] == ':') - have_separator = true; -#endif - - return talloc_asprintf(talloc_ctx, "%.*s%s%.*s", BSTR_P(p1), - have_separator ? "" : "/", BSTR_P(p2)); -} - -char *mp_path_join(void *talloc_ctx, const char *p1, const char *p2) -{ - return mp_path_join_bstr(talloc_ctx, bstr0(p1), bstr0(p2)); -} - -char *mp_getcwd(void *talloc_ctx) -{ - char *e_wd = getenv("PWD"); - if (e_wd) - return talloc_strdup(talloc_ctx, e_wd); - - char *wd = talloc_array(talloc_ctx, char, 20); - while (getcwd(wd, talloc_get_size(wd)) == NULL) { - if (errno != ERANGE) { - talloc_free(wd); - return NULL; - } - wd = talloc_realloc(talloc_ctx, wd, char, talloc_get_size(wd) * 2); - } - return wd; -} - -char *mp_normalize_path(void *talloc_ctx, const char *path) -{ - if (mp_is_url(bstr0(path))) - return talloc_strdup(talloc_ctx, path); - - return mp_path_join(talloc_ctx, mp_getcwd(talloc_ctx), path); -} - -bool mp_path_exists(const char *path) -{ - struct stat st; - return path && stat(path, &st) == 0; -} - -bool mp_path_isdir(const char *path) -{ - struct stat st; - return stat(path, &st) == 0 && S_ISDIR(st.st_mode); -} - -// Return false if it's considered a normal local filesystem path. -bool mp_is_url(bstr path) -{ - int proto = bstr_find0(path, "://"); - if (proto < 1) - return false; - // Per RFC3986, the first character of the protocol must be alphabetic. - // The rest must be alphanumeric plus -, + and . - for (int i = 0; i < proto; i++) { - unsigned char c = path.start[i]; - if ((i == 0 && !mp_isalpha(c)) || - (!mp_isalnum(c) && c != '.' && c != '-' && c != '+')) - { - return false; - } - } - return true; -} - -// Return the protocol part of path, e.g. "http" if path is "http://...". -// On success, out_url (if not NULL) is set to the part after the "://". -bstr mp_split_proto(bstr path, bstr *out_url) -{ - if (!mp_is_url(path)) - return (bstr){0}; - bstr r; - bstr_split_tok(path, "://", &r, out_url ? out_url : &(bstr){0}); - return r; -} - -void mp_mkdirp(const char *dir) -{ - char *path = talloc_strdup(NULL, dir); - char *cdir = path + 1; - - while (cdir) { - cdir = strchr(cdir, '/'); - if (cdir) - *cdir = 0; - - mkdir(path, 0700); - - if (cdir) - *cdir++ = '/'; - } - - talloc_free(path); -} - void mp_mk_user_dir(struct mpv_global *global, const char *type, char *subdir) { char *dir = mp_find_user_file(NULL, global, type, subdir); diff --git a/options/path.h b/options/path.h index 7ec8f7b..a665e67 100644 --- a/options/path.h +++ b/options/path.h @@ -22,6 +22,7 @@ #include <stdbool.h> #include "misc/bstr.h" +#include "misc/path_utils.h" struct mpv_global; struct MPOpts; @@ -30,7 +31,7 @@ void mp_init_paths(struct mpv_global *global, struct MPOpts *opts); // Search for the input filename in several paths. These include user and global // config locations by default. Some platforms may implement additional platform -// related lookups (i.e.: OSX inside an application bundle). +// related lookups (i.e.: macOS inside an application bundle). char *mp_find_config_file(void *talloc_ctx, struct mpv_global *global, const char *filename); @@ -53,46 +54,6 @@ char **mp_find_all_config_files(void *talloc_ctx, struct mpv_global *global, char *mp_get_user_path(void *talloc_ctx, struct mpv_global *global, const char *path); -// Return pointer to filename part of path - -char *mp_basename(const char *path); - -/* Return file extension, excluding the '.'. If root is not NULL, set it to the - * part of the path without extension. So: path == root + "." + extension - * Don't consider it a file extension if the only '.' is the first character. - * Return NULL if no extension and don't set *root in this case. - */ -char *mp_splitext(const char *path, bstr *root); - -/* Return struct bstr referencing directory part of path, or if that - * would be empty, ".". - */ -struct bstr mp_dirname(const char *path); - -void mp_path_strip_trailing_separator(char *path); - -/* Join two path components and return a newly allocated string - * for the result. '/' is inserted between the components if needed. - * If p2 is an absolute path then the value of p1 is ignored. - */ -char *mp_path_join(void *talloc_ctx, const char *p1, const char *p2); -char *mp_path_join_bstr(void *talloc_ctx, struct bstr p1, struct bstr p2); - -// Return whether the path is absolute. -bool mp_path_is_absolute(struct bstr path); - -char *mp_getcwd(void *talloc_ctx); - -char *mp_normalize_path(void *talloc_ctx, const char *path); - -bool mp_path_exists(const char *path); -bool mp_path_isdir(const char *path); - -bool mp_is_url(bstr path); - -bstr mp_split_proto(bstr path, bstr *out_url); - -void mp_mkdirp(const char *dir); void mp_mk_user_dir(struct mpv_global *global, const char *type, char *subdir); #endif /* MPLAYER_PATH_H */ @@ -189,7 +189,7 @@ static bool get_file_ids_win8(HANDLE h, dev_t *dev, ino_t *ino) // SDK, but we can ignore that by just memcpying it. This will also // truncate the file ID on 32-bit Windows, which doesn't support __int128. // 128-bit file IDs are only used for ReFS, so that should be okay. - assert(sizeof(*ino) <= sizeof(ii.FileId)); + static_assert(sizeof(*ino) <= sizeof(ii.FileId), ""); memcpy(ino, &ii.FileId, sizeof(*ino)); return true; } @@ -298,62 +298,53 @@ int mp_fstat(int fd, struct mp_stat *buf) return hstat(h, buf); } -#if HAVE_UWP -static int mp_vfprintf(FILE *stream, const char *format, va_list args) +static inline HANDLE get_handle(FILE *stream) { - return vfprintf(stream, format, args); -} -#else -static int mp_check_console(HANDLE wstream) -{ - if (wstream != INVALID_HANDLE_VALUE) { - unsigned int filetype = GetFileType(wstream); + HANDLE wstream = INVALID_HANDLE_VALUE; - if (!((filetype == FILE_TYPE_UNKNOWN) && - (GetLastError() != ERROR_SUCCESS))) - { - filetype &= ~(FILE_TYPE_REMOTE); + if (stream == stdout || stream == stderr) { + wstream = GetStdHandle(stream == stdout ? + STD_OUTPUT_HANDLE : STD_ERROR_HANDLE); + } + return wstream; +} - if (filetype == FILE_TYPE_CHAR) { - DWORD ConsoleMode; - int ret = GetConsoleMode(wstream, &ConsoleMode); +size_t mp_fwrite(const void *restrict buffer, size_t size, size_t count, + FILE *restrict stream) +{ + if (!size || !count) + return 0; - if (!(!ret && (GetLastError() == ERROR_INVALID_HANDLE))) { - // This seems to be a console - return 1; - } - } + HANDLE wstream = get_handle(stream); + if (mp_check_console(wstream)) { + unsigned char *start = (unsigned char *)buffer; + size_t c = 0; + for (; c < count; ++c) { + if (mp_console_write(wstream, (bstr){start, size}) <= 0) + break; + start += size; } + return c; } - return 0; +#undef fwrite + return fwrite(buffer, size, count, stream); } +#if HAVE_UWP static int mp_vfprintf(FILE *stream, const char *format, va_list args) { - int done = 0; - - HANDLE wstream = INVALID_HANDLE_VALUE; - - if (stream == stdout || stream == stderr) { - wstream = GetStdHandle(stream == stdout ? - STD_OUTPUT_HANDLE : STD_ERROR_HANDLE); - } - - if (mp_check_console(wstream)) { - size_t len = vsnprintf(NULL, 0, format, args) + 1; - char *buf = talloc_array(NULL, char, len); + return vfprintf(stream, format, args); +} +#else - if (buf) { - done = vsnprintf(buf, len, format, args); - mp_write_console_ansi(wstream, buf); - } - talloc_free(buf); - } else { - done = vfprintf(stream, format, args); - } +static int mp_vfprintf(FILE *stream, const char *format, va_list args) +{ + HANDLE wstream = get_handle(stream); + if (mp_check_console(wstream)) + return mp_console_vfprintf(wstream, format, args); - return done; + return vfprintf(stream, format, args); } #endif @@ -558,7 +549,9 @@ FILE *mp_fopen(const char *filename, const char *mode) // Thus we need MP_PATH_MAX as the UTF-8/char version of PATH_MAX. // Also make sure there's free space for the terminating \0. // (For codepoints encoded as UTF-16 surrogate pairs, UTF-8 has the same length.) -#define MP_PATH_MAX (FILENAME_MAX * 3 + 1) +// Lastly, note that neither _wdirent nor WIN32_FIND_DATA can store filenames +// longer than this, so long-path support for readdir() is impossible. +#define MP_FILENAME_MAX (FILENAME_MAX * 3 + 1) struct mp_dir { DIR crap; // must be first member @@ -568,9 +561,9 @@ struct mp_dir { // dirent has space only for FILENAME_MAX bytes. _wdirent has space for // FILENAME_MAX wchar_t, which might end up bigger as UTF-8 in some // cases. Guarantee we can always hold _wdirent.d_name converted to - // UTF-8 (see MP_PATH_MAX). + // UTF-8 (see above). // This works because dirent.d_name is the last member of dirent. - char space[MP_PATH_MAX]; + char space[MP_FILENAME_MAX]; }; }; @@ -620,6 +613,14 @@ int mp_mkdir(const char *path, int mode) return res; } +int mp_unlink(const char *path) +{ + wchar_t *wpath = mp_from_utf8(NULL, path); + int res = _wunlink(wpath); + talloc_free(wpath); + return res; +} + char *mp_win32_getcwd(char *buf, size_t size) { if (size >= SIZE_MAX / 3 - 1) { @@ -736,9 +737,23 @@ static void mp_dl_init(void) void *mp_dlopen(const char *filename, int flag) { - wchar_t *wfilename = mp_from_utf8(NULL, filename); - HMODULE lib = LoadLibraryW(wfilename); - talloc_free(wfilename); + HMODULE lib = NULL; + void *ta_ctx = talloc_new(NULL); + wchar_t *wfilename = mp_from_utf8(ta_ctx, filename); + + DWORD len = GetFullPathNameW(wfilename, 0, NULL, NULL); + if (!len) + goto err; + + wchar_t *path = talloc_array(ta_ctx, wchar_t, len); + len = GetFullPathNameW(wfilename, len, path, NULL); + if (!len) + goto err; + + lib = LoadLibraryW(path); + +err: + talloc_free(ta_ctx); mp_dl_result.errcode = GetLastError(); return (void *)lib; } @@ -876,29 +891,3 @@ void freelocale(locale_t locobj) } #endif // __MINGW32__ - -int mp_mkostemps(char *template, int suffixlen, int flags) -{ - size_t len = strlen(template); - char *t = len >= 6 + suffixlen ? &template[len - (6 + suffixlen)] : NULL; - if (!t || strncmp(t, "XXXXXX", 6) != 0) { - errno = EINVAL; - return -1; - } - - for (size_t fuckshit = 0; fuckshit < UINT32_MAX; fuckshit++) { - // Using a random value may make it require fewer iterations (even if - // not truly random; just a counter would be sufficient). - size_t fuckmess = mp_rand_next(); - char crap[7] = ""; - snprintf(crap, sizeof(crap), "%06zx", fuckmess); - memcpy(t, crap, 6); - - int res = open(template, O_RDWR | O_CREAT | O_EXCL | flags, 0600); - if (res >= 0 || errno != EEXIST) - return res; - } - - errno = EEXIST; - return -1; -} @@ -29,6 +29,8 @@ #include <fcntl.h> #include <locale.h> +#include "compiler.h" + #if HAVE_GLOB_POSIX #include <glob.h> #endif @@ -78,9 +80,19 @@ int mp_make_wakeup_pipe(int pipes[2]); void mp_flush_wakeup_pipe(int pipe_end); #ifdef _WIN32 + #include <wchar.h> wchar_t *mp_from_utf8(void *talloc_ctx, const char *s); char *mp_to_utf8(void *talloc_ctx, const wchar_t *s); + +// Use this in win32-specific code rather than PATH_MAX or MAX_PATH. +// This is necessary because we declare long-path aware support which raises +// the effective limit without affecting any defines. +// The actual limit is 32767 but there's a few edge cases that reduce +// it. So pick this nice round number. +// Note that this is wchars, not chars. +#define MP_PATH_MAX (32000) + #endif #ifdef __CYGWIN__ @@ -94,8 +106,10 @@ char *mp_to_utf8(void *talloc_ctx, const wchar_t *s); #include <sys/stat.h> #include <fcntl.h> -int mp_printf(const char *format, ...); -int mp_fprintf(FILE *stream, const char *format, ...); +size_t mp_fwrite(const void *restrict buffer, size_t size, size_t count, + FILE *restrict stream); +int mp_printf(const char *format, ...) PRINTF_ATTRIBUTE(1, 2); +int mp_fprintf(FILE *stream, const char *format, ...) PRINTF_ATTRIBUTE(2, 3); int mp_open(const char *filename, int oflag, ...); int mp_creat(const char *filename, int mode); int mp_rename(const char *oldpath, const char *newpath); @@ -104,6 +118,7 @@ DIR *mp_opendir(const char *path); struct dirent *mp_readdir(DIR *dir); int mp_closedir(DIR *dir); int mp_mkdir(const char *path, int mode); +int mp_unlink(const char *path); char *mp_win32_getcwd(char *buf, size_t size); char *mp_getenv(const char *name); @@ -161,6 +176,7 @@ int mp_glob(const char *restrict pattern, int flags, int (*errfunc)(const char*, int), mp_glob_t *restrict pglob); void mp_globfree(mp_glob_t *pglob); +#define fwrite(...) mp_fwrite(__VA_ARGS__) #define printf(...) mp_printf(__VA_ARGS__) #define fprintf(...) mp_fprintf(__VA_ARGS__) #define open(...) mp_open(__VA_ARGS__) @@ -171,6 +187,7 @@ void mp_globfree(mp_glob_t *pglob); #define readdir(...) mp_readdir(__VA_ARGS__) #define closedir(...) mp_closedir(__VA_ARGS__) #define mkdir(...) mp_mkdir(__VA_ARGS__) +#define unlink(...) mp_unlink(__VA_ARGS__) #define getcwd(...) mp_win32_getcwd(__VA_ARGS__) #define getenv(...) mp_getenv(__VA_ARGS__) @@ -227,6 +244,4 @@ extern char **environ; #endif /* __MINGW32__ */ -int mp_mkostemps(char *template, int suffixlen, int flags); - #endif diff --git a/osdep/language-apple.c b/osdep/language-mac.c index dc83fb5..32a94ae 100644 --- a/osdep/language-apple.c +++ b/osdep/language-mac.c @@ -19,7 +19,7 @@ #include "misc/language.h" -#include "apple_utils.h" +#include "utils-mac.h" #include "mpv_talloc.h" char **mp_get_user_langs(void) diff --git a/osdep/macosx_application.h b/osdep/mac/app_bridge.h index 753b9f0..db03c8e 100644 --- a/osdep/macosx_application.h +++ b/osdep/mac/app_bridge.h @@ -15,25 +15,27 @@ * License along with mpv. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef MPV_MACOSX_APPLICATION -#define MPV_MACOSX_APPLICATION +#pragma once -#include "osdep/macosx_menubar.h" +#include <stdbool.h> #include "options/m_option.h" +struct input_ctx; +struct mpv_handle; + enum { FRAME_VISIBLE = 0, FRAME_WHOLE, }; enum { - RENDER_TIMER_CALLBACK = 0, - RENDER_TIMER_PRECISE, + RENDER_TIMER_PRESENTATION_FEEDBACK = -1, RENDER_TIMER_SYSTEM, + RENDER_TIMER_CALLBACK, + RENDER_TIMER_PRECISE, }; struct macos_opts { - int macos_title_bar_style; int macos_title_bar_appearance; int macos_title_bar_material; struct m_color macos_title_bar_color; @@ -46,10 +48,12 @@ struct macos_opts { bool cocoa_cb_10bit_context; }; +void cocoa_init_media_keys(void); +void cocoa_uninit_media_keys(void); +void cocoa_set_input_context(struct input_ctx *input_context); +void cocoa_set_mpv_handle(struct mpv_handle *ctx); +void cocoa_init_cocoa_cb(void); // multithreaded wrapper for mpv_main int cocoa_main(int argc, char *argv[]); -void cocoa_register_menu_item_action(MPMenuKey key, void* action); extern const struct m_sub_options macos_conf; - -#endif /* MPV_MACOSX_APPLICATION */ diff --git a/osdep/mac/app_bridge.m b/osdep/mac/app_bridge.m new file mode 100644 index 0000000..bf39efe --- /dev/null +++ b/osdep/mac/app_bridge.m @@ -0,0 +1,123 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#import "osdep/mac/app_bridge_objc.h" + +#if HAVE_SWIFT +#include "osdep/mac/swift.h" +#endif + +#define OPT_BASE_STRUCT struct macos_opts +const struct m_sub_options macos_conf = { + .opts = (const struct m_option[]) { + {"macos-title-bar-appearance", OPT_CHOICE(macos_title_bar_appearance, + {"auto", 0}, {"aqua", 1}, {"darkAqua", 2}, + {"vibrantLight", 3}, {"vibrantDark", 4}, + {"aquaHighContrast", 5}, {"darkAquaHighContrast", 6}, + {"vibrantLightHighContrast", 7}, + {"vibrantDarkHighContrast", 8})}, + {"macos-title-bar-material", OPT_CHOICE(macos_title_bar_material, + {"titlebar", 0}, {"selection", 1}, {"menu", 2}, + {"popover", 3}, {"sidebar", 4}, {"headerView", 5}, + {"sheet", 6}, {"windowBackground", 7}, {"hudWindow", 8}, + {"fullScreen", 9}, {"toolTip", 10}, {"contentBackground", 11}, + {"underWindowBackground", 12}, {"underPageBackground", 13}, + {"dark", 14}, {"light", 15}, {"mediumLight", 16}, + {"ultraDark", 17})}, + {"macos-title-bar-color", OPT_COLOR(macos_title_bar_color)}, + {"macos-fs-animation-duration", + OPT_CHOICE(macos_fs_animation_duration, {"default", -1}), + M_RANGE(0, 1000)}, + {"macos-force-dedicated-gpu", OPT_BOOL(macos_force_dedicated_gpu)}, + {"macos-app-activation-policy", OPT_CHOICE(macos_app_activation_policy, + {"regular", 0}, {"accessory", 1}, {"prohibited", 2})}, + {"macos-geometry-calculation", OPT_CHOICE(macos_geometry_calculation, + {"visible", FRAME_VISIBLE}, {"whole", FRAME_WHOLE})}, + {"macos-render-timer", OPT_CHOICE(macos_render_timer, + {"callback", RENDER_TIMER_CALLBACK}, {"precise", RENDER_TIMER_PRECISE}, + {"system", RENDER_TIMER_SYSTEM}, {"feedback", RENDER_TIMER_PRESENTATION_FEEDBACK})}, + {"cocoa-cb-sw-renderer", OPT_CHOICE(cocoa_cb_sw_renderer, + {"auto", -1}, {"no", 0}, {"yes", 1})}, + {"cocoa-cb-10bit-context", OPT_BOOL(cocoa_cb_10bit_context)}, + {0} + }, + .size = sizeof(struct macos_opts), + .defaults = &(const struct macos_opts){ + .macos_title_bar_color = {0, 0, 0, 0}, + .macos_fs_animation_duration = -1, + .macos_render_timer = RENDER_TIMER_CALLBACK, + .cocoa_cb_sw_renderer = -1, + .cocoa_cb_10bit_context = true + }, +}; + +static const char app_icon[] = +#include "TOOLS/osxbundle/icon.icns.inc" +; + +NSData *app_bridge_icon(void) +{ + return [NSData dataWithBytesNoCopy:(void *)app_icon length:sizeof(app_icon) - 1 freeWhenDone:NO]; +} + +void app_bridge_tarray_append(void *t, char ***a, int *i, char *s) +{ + MP_TARRAY_APPEND(t, *a, *i, s); +} + +const struct m_sub_options *app_bridge_mac_conf(void) +{ + return &macos_conf; +} + +const struct m_sub_options *app_bridge_vo_conf(void) +{ + return &vo_sub_opts; +} + +void cocoa_init_media_keys(void) +{ + [[AppHub shared] startRemote]; +} + +void cocoa_uninit_media_keys(void) +{ + [[AppHub shared] stopRemote]; +} + +void cocoa_set_input_context(struct input_ctx *input_context) +{ + [[AppHub shared] initInput:input_context]; +} + +void cocoa_set_mpv_handle(struct mpv_handle *ctx) +{ + [[AppHub shared] initMpv:ctx]; +} + +void cocoa_init_cocoa_cb(void) +{ + [[AppHub shared] initCocoaCb]; +} + +int cocoa_main(int argc, char *argv[]) +{ + return [(Application *)[Application sharedApplication] main:argc :argv]; +} + diff --git a/osdep/macOS_swift_bridge.h b/osdep/mac/app_bridge_objc.h index 9407b6f..6020198 100644 --- a/osdep/macOS_swift_bridge.h +++ b/osdep/mac/app_bridge_objc.h @@ -15,9 +15,7 @@ * License along with mpv. If not, see <http://www.gnu.org/licenses/>. */ -// including frameworks here again doesn't make sense, but otherwise the swift -// compiler doesn't include the needed headers in our generated header file -#import <IOKit/pwr_mgt/IOPMLib.h> +#import <Cocoa/Cocoa.h> #import <QuartzCore/QuartzCore.h> #include "player/client.h" @@ -27,13 +25,14 @@ #include "options/m_config.h" #include "player/core.h" #include "input/input.h" +#include "input/event.h" +#include "input/keycodes.h" #include "video/out/win_state.h" -#include "osdep/macosx_application_objc.h" -#include "osdep/macosx_events_objc.h" +#include "osdep/main-fn.h" +#include "osdep/mac/app_bridge.h" - -// complex macros won't get imported to Swift so we have to reassign them +// complex macros won't get imported to swift so we have to reassign them static int SWIFT_MBTN_LEFT = MP_MBTN_LEFT; static int SWIFT_MBTN_MID = MP_MBTN_MID; static int SWIFT_MBTN_RIGHT = MP_MBTN_RIGHT; @@ -48,10 +47,10 @@ static int SWIFT_MBTN9 = MP_MBTN9; static int SWIFT_KEY_MOUSE_LEAVE = MP_KEY_MOUSE_LEAVE; static int SWIFT_KEY_MOUSE_ENTER = MP_KEY_MOUSE_ENTER; -// only used from Swift files and therefore seen as unused by the c compiler -static void SWIFT_TARRAY_STRING_APPEND(void *t, char ***a, int *i, char *s) __attribute__ ((unused)); +static const char *swift_mpv_version = mpv_version; +static const char *swift_mpv_copyright = mpv_copyright; -static void SWIFT_TARRAY_STRING_APPEND(void *t, char ***a, int *i, char *s) -{ - MP_TARRAY_APPEND(t, *a, *i, s); -} +NSData *app_bridge_icon(void); +void app_bridge_tarray_append(void *t, char ***a, int *i, char *s); +const struct m_sub_options *app_bridge_mac_conf(void); +const struct m_sub_options *app_bridge_vo_conf(void); diff --git a/osdep/mac/app_hub.swift b/osdep/mac/app_hub.swift new file mode 100644 index 0000000..997cc33 --- /dev/null +++ b/osdep/mac/app_hub.swift @@ -0,0 +1,130 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +import Cocoa + +class AppHub: NSObject { + @objc static let shared = AppHub() + + var mpv: OpaquePointer? + var input: InputHelper + var log: LogHelper + var option: OptionHelper? + var event: EventHelper? + var menu: MenuBar? +#if HAVE_MACOS_MEDIA_PLAYER + var remote: RemoteCommandCenter? +#endif +#if HAVE_MACOS_TOUCHBAR + var touchBar: TouchBar? +#endif +#if HAVE_MACOS_COCOA_CB + var cocoaCb: CocoaCB? +#endif + + let MPV_PROTOCOL: String = "mpv://" + var isApplication: Bool { get { NSApp is Application } } + var openEvents: Int = 0 + + private override init() { + input = InputHelper() + log = LogHelper() + super.init() + if isApplication { menu = MenuBar(self) } +#if HAVE_MACOS_MEDIA_PLAYER + remote = RemoteCommandCenter(self) +#endif + log.verbose("AppHub initialised") + } + + @objc func initMpv(_ mpv: OpaquePointer) { + event = EventHelper(self, mpv) + if let mpv = event?.mpv { + self.mpv = mpv + log.log = mp_log_new(nil, mp_client_get_log(mpv), "app") + option = OptionHelper(UnsafeMutablePointer(mpv), mp_client_get_global(mpv)) + input.option = option + } + +#if HAVE_MACOS_MEDIA_PLAYER + remote?.registerEvents() +#endif +#if HAVE_MACOS_TOUCHBAR + touchBar = TouchBar(self) +#endif + log.verbose("AppHub functionality initialised") + } + + @objc func initInput(_ input: OpaquePointer?) { + log.verbose("Initialising Input") + self.input.signal(input: input) + } + + @objc func initCocoaCb() { +#if HAVE_MACOS_COCOA_CB + if !isApplication { return } + log.verbose("Initialising CocoaCB") + DispatchQueue.main.sync { + self.cocoaCb = self.cocoaCb ?? CocoaCB(mpv_create_client(mpv, "cocoacb")) + } +#endif + } + + @objc func startRemote() { +#if HAVE_MACOS_MEDIA_PLAYER + log.verbose("Starting RemoteCommandCenter") + remote?.start() +#endif + } + + @objc func stopRemote() { +#if HAVE_MACOS_MEDIA_PLAYER + log.verbose("Stoping RemoteCommandCenter") + remote?.stop() +#endif + } + + func open(urls: [URL]) { + let files = urls.map { + if $0.isFileURL { return $0.path } + var path = $0.absoluteString + if path.hasPrefix(MPV_PROTOCOL) { path.removeFirst(MPV_PROTOCOL.count) } + return path.removingPercentEncoding ?? path + }.sorted { (strL: String, strR: String) -> Bool in + return strL.localizedStandardCompare(strR) == .orderedAscending + } + log.verbose("\(openEvents > 0 ? "Appending" : "Opening") dropped files: \(files)") + input.open(files: files, append: openEvents > 0) + openEvents += 1 + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { self.openEvents -= 1 } + } + + func getIcon() -> NSImage { + guard let iconData = app_bridge_icon(), let icon = NSImage(data: iconData) else { + return NSImage(size: NSSize(width: 1, height: 1)) + } + return icon + } + + func getMacConf() -> UnsafePointer<m_sub_options>? { + return app_bridge_mac_conf() + } + + func getVoConf() -> UnsafePointer<m_sub_options>? { + return app_bridge_vo_conf() + } +} diff --git a/osdep/mac/application.swift b/osdep/mac/application.swift new file mode 100644 index 0000000..30f37a6 --- /dev/null +++ b/osdep/mac/application.swift @@ -0,0 +1,123 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +import Cocoa + +class Application: NSApplication, NSApplicationDelegate { + var appHub: AppHub { get { return AppHub.shared } } + var eventManager: NSAppleEventManager { get { return NSAppleEventManager.shared() } } + var isBundle: Bool { get { return ProcessInfo.processInfo.environment["MPVBUNDLE"] == "true" } } + var playbackThreadId: mp_thread! + var argc: Int32? + var argv: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>? + + override init() { + super.init() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func sendEvent(_ event: NSEvent) { + if modalWindow != nil || !appHub.input.processKey(event: event) { + super.sendEvent(event) + } + appHub.input.wakeup() + } + +#if HAVE_MACOS_TOUCHBAR + override func makeTouchBar() -> NSTouchBar? { + return appHub.touchBar + } +#endif + + func application(_ application: NSApplication, open urls: [URL]) { + appHub.open(urls: urls) + } + + func applicationWillFinishLaunching(_ notification: Notification) { + // register quit and exit events + eventManager.setEventHandler( + self, + andSelector: #selector(handleQuit(event:replyEvent:)), + forEventClass: AEEventClass(kCoreEventClass), + andEventID: kAEQuitApplication + ) + atexit_b({ + // clean up after exit() was called + DispatchQueue.main.async { + NSApp.hide(NSApp) + NSApp.setActivationPolicy(.prohibited) + self.eventManager.removeEventHandler(forEventClass: AEEventClass(kCoreEventClass), andEventID: kAEQuitApplication) + } + }) + } + + // quit from App icon, external quit from NSWorkspace + @objc func handleQuit(event: NSAppleEventDescriptor?, replyEvent: NSAppleEventDescriptor?) { + // send quit to core, terminates mpv_main called in playbackThread, + if !appHub.input.command("quit") { + appHub.log.warning("Could not properly shut down mpv") + exit(1) + } + } + + func setupBundle() { + if !isBundle { return } + + // started from finder the first argument after the binary may start with -psn_ + if CommandLine.argc > 1 && CommandLine.arguments[1].hasPrefix("-psn_") { + argc? = 1 + argv?[1] = nil + } + + let path = (ProcessInfo.processInfo.environment["PATH"] ?? "") + + ":/usr/local/bin:/usr/local/sbin:/opt/local/bin:/opt/local/sbin" + appHub.log.verbose("Setting Bundle $PATH to: \(path)") + _ = path.withCString { setenv("PATH", $0, 1) } + } + + let playbackThread: @convention(c) (UnsafeMutableRawPointer) -> UnsafeMutableRawPointer? = { (ptr: UnsafeMutableRawPointer) in + let application: Application = TypeHelper.bridge(ptr: ptr) + mp_thread_set_name("core/playback") + let exitCode: Int32 = mpv_main(application.argc ?? 1, application.argv) + // exit of any proper shut down + exit(exitCode) + } + + @objc func main(_ argc: Int32, _ argv: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>) -> Int { + self.argc = argc + self.argv = argv + + NSApp = self + NSApp.delegate = self + NSApp.setActivationPolicy(isBundle ? .regular : .accessory) + setupBundle() + pthread_create(&playbackThreadId, nil, playbackThread, TypeHelper.bridge(obj: self)) + appHub.input.wait() + NSApp.run() + + // should never be reached + print(""" + There was either a problem initializing Cocoa or the Runloop was stopped unexpectedly. \ + Please report this issues to a developer.\n + """) + pthread_join(playbackThreadId, nil) + return 1 + } +} diff --git a/osdep/mac/event_helper.swift b/osdep/mac/event_helper.swift new file mode 100644 index 0000000..277a9aa --- /dev/null +++ b/osdep/mac/event_helper.swift @@ -0,0 +1,147 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +import Cocoa + +protocol EventSubscriber: AnyObject { + var uid: Int { get } + func handle(event: EventHelper.Event) +} + +extension EventSubscriber { + var uid: Int { get { return Int(bitPattern: ObjectIdentifier(self)) }} +} + +extension EventHelper { + typealias wakeup_cb = (@convention(c) (UnsafeMutableRawPointer?) -> Void)? + + struct Event { + var id: String { + get { name + (name.starts(with: "MPV_EVENT_") ? "" : String(format.rawValue)) } + } + var idReset: String { + get { name + (name.starts(with: "MPV_EVENT_") ? "" : String(MPV_FORMAT_NONE.rawValue)) } + } + let name: String + let format: mpv_format + let string: String? + let bool: Bool? + let double: Double? + + init( + name: String = "", + format: mpv_format = MPV_FORMAT_NONE, + string: String? = nil, + bool: Bool? = nil, + double: Double? = nil + + ) { + self.name = name + self.format = format + self.string = string + self.bool = bool + self.double = double + } + } +} + +class EventHelper { + unowned let appHub: AppHub + var mpv: OpaquePointer? + var events: [String:[Int:EventSubscriber]] = [:] + + init?(_ appHub: AppHub, _ mpv: OpaquePointer) { + if !appHub.isApplication { + mpv_destroy(mpv) + return nil + } + + self.appHub = appHub + self.mpv = mpv + mpv_set_wakeup_callback(mpv, wakeup, TypeHelper.bridge(obj: self)) + } + + func subscribe(_ subscriber: any EventSubscriber, event: Event) { + guard let mpv = mpv else { return } + + if !event.name.isEmpty { + if !events.keys.contains(event.idReset) { + events[event.idReset] = [:] + } + if !events.keys.contains(event.id) { + mpv_observe_property(mpv, 0, event.name, event.format) + events[event.id] = [:] + } + events[event.idReset]?[subscriber.uid] = subscriber + events[event.id]?[subscriber.uid] = subscriber + } + } + + let wakeup: wakeup_cb = { ( ctx ) in + let event = unsafeBitCast(ctx, to: EventHelper.self) + DispatchQueue.main.async { event.eventLoop() } + } + + func eventLoop() { + while let mpv = mpv, let event = mpv_wait_event(mpv, 0) { + if event.pointee.event_id == MPV_EVENT_NONE { break } + handle(event: event) + } + } + + func handle(event: UnsafeMutablePointer<mpv_event>) { + switch event.pointee.event_id { + case MPV_EVENT_PROPERTY_CHANGE: + handle(property: event) + default: + for (_, subscriber) in events[String(describing: event.pointee.event_id)] ?? [:] { + subscriber.handle(event: .init(name: String(describing: event.pointee.event_id))) + } + } + + if event.pointee.event_id == MPV_EVENT_SHUTDOWN { + mpv_destroy(mpv) + mpv = nil + } + } + + func handle(property mpvEvent: UnsafeMutablePointer<mpv_event>) { + let pData = OpaquePointer(mpvEvent.pointee.data) + guard let property = UnsafePointer<mpv_event_property>(pData)?.pointee else { + return + } + + let name = String(cString: property.name) + let format = property.format + for (_, subscriber) in events[name + String(format.rawValue)] ?? [:] { + var event: Event? = nil + switch format { + case MPV_FORMAT_STRING: + event = .init(name: name, format: format, string: TypeHelper.toString(property.data)) + case MPV_FORMAT_FLAG: + event = .init(name: name, format: format, bool: TypeHelper.toBool(property.data)) + case MPV_FORMAT_DOUBLE: + event = .init(name: name, format: format, double: TypeHelper.toDouble(property.data)) + case MPV_FORMAT_NONE: + event = .init(name: name, format: format) + default: break + } + + if let e = event { subscriber.handle(event: e) } + } + } +} diff --git a/osdep/mac/input_helper.swift b/osdep/mac/input_helper.swift new file mode 100644 index 0000000..0e4ec1f --- /dev/null +++ b/osdep/mac/input_helper.swift @@ -0,0 +1,275 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +import Cocoa +import Carbon.HIToolbox + +class InputHelper: NSObject { + var option: OptionHelper? + var lock = NSCondition() + private var input: OpaquePointer? + + let keymap: [mp_keymap] = [ + // special keys + .init(kVK_Return, MP_KEY_ENTER), .init(kVK_Escape, MP_KEY_ESC), + .init(kVK_Delete, MP_KEY_BACKSPACE), .init(kVK_Tab, MP_KEY_TAB), + .init(kVK_VolumeUp, MP_KEY_VOLUME_UP), .init(kVK_VolumeDown, MP_KEY_VOLUME_DOWN), + .init(kVK_Mute, MP_KEY_MUTE), + + // cursor keys + .init(kVK_UpArrow, MP_KEY_UP), .init(kVK_DownArrow, MP_KEY_DOWN), + .init(kVK_LeftArrow, MP_KEY_LEFT), .init(kVK_RightArrow, MP_KEY_RIGHT), + + // navigation block + .init(kVK_Help, MP_KEY_INSERT), .init(kVK_ForwardDelete, MP_KEY_DELETE), + .init(kVK_Home, MP_KEY_HOME), .init(kVK_End, MP_KEY_END), + .init(kVK_PageUp, MP_KEY_PAGE_UP), .init(kVK_PageDown, MP_KEY_PAGE_DOWN), + + // F-keys + .init(kVK_F1, MP_KEY_F + 1), .init(kVK_F2, MP_KEY_F + 2), .init(kVK_F3, MP_KEY_F + 3), + .init(kVK_F4, MP_KEY_F + 4), .init(kVK_F5, MP_KEY_F + 5), .init(kVK_F6, MP_KEY_F + 6), + .init(kVK_F7, MP_KEY_F + 7), .init(kVK_F8, MP_KEY_F + 8), .init(kVK_F9, MP_KEY_F + 9), + .init(kVK_F10, MP_KEY_F + 10), .init(kVK_F11, MP_KEY_F + 11), .init(kVK_F12, MP_KEY_F + 12), + .init(kVK_F13, MP_KEY_F + 13), .init(kVK_F14, MP_KEY_F + 14), .init(kVK_F15, MP_KEY_F + 15), + .init(kVK_F16, MP_KEY_F + 16), .init(kVK_F17, MP_KEY_F + 17), .init(kVK_F18, MP_KEY_F + 18), + .init(kVK_F19, MP_KEY_F + 19), .init(kVK_F20, MP_KEY_F + 20), + + // numpad + .init(kVK_ANSI_KeypadPlus, Int32(Character("+").asciiValue ?? 0)), + .init(kVK_ANSI_KeypadMinus, Int32(Character("-").asciiValue ?? 0)), + .init(kVK_ANSI_KeypadMultiply, Int32(Character("*").asciiValue ?? 0)), + .init(kVK_ANSI_KeypadDivide, Int32(Character("/").asciiValue ?? 0)), + .init(kVK_ANSI_KeypadEnter, MP_KEY_KPENTER), .init(kVK_ANSI_KeypadDecimal, MP_KEY_KPDEC), + .init(kVK_ANSI_Keypad0, MP_KEY_KP0), .init(kVK_ANSI_Keypad1, MP_KEY_KP1), + .init(kVK_ANSI_Keypad2, MP_KEY_KP2), .init(kVK_ANSI_Keypad3, MP_KEY_KP3), + .init(kVK_ANSI_Keypad4, MP_KEY_KP4), .init(kVK_ANSI_Keypad5, MP_KEY_KP5), + .init(kVK_ANSI_Keypad6, MP_KEY_KP6), .init(kVK_ANSI_Keypad7, MP_KEY_KP7), + .init(kVK_ANSI_Keypad8, MP_KEY_KP8), .init(kVK_ANSI_Keypad9, MP_KEY_KP9), + + .init(0, 0) + ] + + init(_ input: OpaquePointer? = nil, _ option: OptionHelper? = nil) { + super.init() + self.input = input + self.option = option + } + + func put( + key: Int32, + modifiers: NSEvent.ModifierFlags = .init(rawValue: 0), + type: NSEvent.EventType = .applicationDefined + ) { + lock.withLock { + putKey(key, modifiers: modifiers, type: type) + } + } + + private func putKey( + _ key: Int32, + modifiers: NSEvent.ModifierFlags = .init(rawValue: 0), + type: NSEvent.EventType = .applicationDefined + ) { + if key < 1 { return } + + guard let input = input else { return } + let code = key | mapModifier(modifiers) | mapType(type) + mp_input_put_key(input, code) + + if type == .keyUp { + mp_input_put_key(input, MP_INPUT_RELEASE_ALL) + } + } + + @objc func processKey(event: NSEvent) -> Bool { + if event.type != .keyDown && event.type != .keyUp { return false } + if NSApp.mainMenu?.performKeyEquivalent(with: event) ?? false || event.isARepeat { return true } + + return lock.withLock { + let mpkey = lookup_keymap_table(keymap, Int32(event.keyCode)) + if mpkey > 0 { + putKey(mpkey, modifiers: event.modifierFlags, type: event.type) + return true + } + + guard let chars = event.characters, let charsNoMod = event.charactersIgnoringModifiers else { return false } + let key = (useAltGr() && event.modifierFlags.contains(.optionRight)) ? chars : charsNoMod + key.withCString { + var bstr = bstr0($0) + putKey(bstr_decode_utf8(bstr, &bstr), modifiers: event.modifierFlags, type: event.type) + } + return true + } + } + + func processMouse(event: NSEvent) { + if !mouseEnabled() { return } + lock.withLock { + putKey(map(button: event.buttonNumber), modifiers: event.modifierFlags, type: event.type) + if event.clickCount > 1 { + putKey(map(button: event.buttonNumber), modifiers: event.modifierFlags, type: .keyUp) + } + } + } + + func processWheel(event: NSEvent) { + if !mouseEnabled() { return } + lock.withLock { + guard let input = input else { return } + let modifiers = event.modifierFlags + let precise = event.hasPreciseScrollingDeltas + var deltaX = event.deltaX * 0.1 + var deltaY = event.deltaY * 0.1 + + if !precise { + deltaX = modifiers.contains(.shift) ? event.scrollingDeltaY : event.scrollingDeltaX + deltaY = modifiers.contains(.shift) ? event.scrollingDeltaX : event.scrollingDeltaY + } + + var key = deltaY > 0 ? SWIFT_WHEEL_UP : SWIFT_WHEEL_DOWN + var delta = Double(deltaY) + if abs(deltaX) > abs(deltaY) { + key = deltaX > 0 ? SWIFT_WHEEL_LEFT : SWIFT_WHEEL_RIGHT + delta = Double(deltaX) + } + + mp_input_put_wheel(input, key | mapModifier(modifiers), precise ? abs(delta) : 1) + } + } + + func draggable(at pos: NSPoint) -> Bool { + lock.withLock { + guard let input = input else { return false } + return !mp_input_test_dragging(input, Int32(pos.x), Int32(pos.y)) + } + } + + func mouseEnabled() -> Bool { + lock.withLock { + guard let input = input else { return true } + return mp_input_mouse_enabled(input) + } + } + + func setMouse(position: NSPoint) { + if !mouseEnabled() { return } + lock.withLock { + guard let input = input else { return } + mp_input_set_mouse_pos(input, Int32(position.x), Int32(position.y)) + } + } + + @discardableResult @objc func command(_ cmd: String) -> Bool { + lock.withLock { + guard let input = input else { return false } + let cCmd = UnsafePointer<Int8>(strdup(cmd)) + let mpvCmd = mp_input_parse_cmd(input, bstr0(cCmd), "") + mp_input_queue_cmd(input, mpvCmd) + free(UnsafeMutablePointer(mutating: cCmd)) + return true + } + } + + private func mapType(_ type: NSEvent.EventType) -> Int32 { + let typeMapping: [NSEvent.EventType:UInt32] = [ + .keyDown: MP_KEY_STATE_DOWN, + .keyUp: MP_KEY_STATE_UP, + .leftMouseDown: MP_KEY_STATE_DOWN, + .leftMouseUp: MP_KEY_STATE_UP, + .rightMouseDown: MP_KEY_STATE_DOWN, + .rightMouseUp: MP_KEY_STATE_UP, + .otherMouseDown: MP_KEY_STATE_DOWN, + .otherMouseUp: MP_KEY_STATE_UP, + ] + + return Int32(typeMapping[type] ?? 0); + } + + private func mapModifier(_ modifiers: NSEvent.ModifierFlags) -> Int32 { + var mask: UInt32 = 0; + + if modifiers.contains(.shift) { + mask |= MP_KEY_MODIFIER_SHIFT + } + if modifiers.contains(.control) { + mask |= MP_KEY_MODIFIER_CTRL + } + if modifiers.contains(.command) { + mask |= MP_KEY_MODIFIER_META + } + if modifiers.contains(.optionLeft) || modifiers.contains(.optionRight) && !useAltGr() { + mask |= MP_KEY_MODIFIER_ALT + } + + return Int32(mask) + } + + private func map(button: Int) -> Int32 { + let buttonMapping: [Int:Int32] = [ + 0: SWIFT_MBTN_LEFT, + 1: SWIFT_MBTN_RIGHT, + 2: SWIFT_MBTN_MID, + 3: SWIFT_MBTN_FORWARD, + 4: SWIFT_MBTN_BACK, + ] + + return Int32(buttonMapping[button] ?? SWIFT_MBTN9 + Int32(button - 5)); + } + + @objc func open(files: [String], append: Bool = false) { + lock.withLock { + guard let input = input else { return } + if (option?.vo.drag_and_drop ?? -1) == -2 { return } + + var action = DND_APPEND + if !append { + action = NSEvent.modifierFlags.contains(.shift) ? DND_APPEND : DND_REPLACE + if (option?.vo.drag_and_drop ?? -1) >= 0 { + action = mp_dnd_action(UInt32(option?.vo.drag_and_drop ?? Int32(DND_REPLACE.rawValue))) + } + } + + let filesClean = files.map{ $0.hasPrefix("file:///.file/id=") ? (URL(string: $0)?.path ?? $0) : $0 } + var filesPtr = filesClean.map { UnsafeMutablePointer<CChar>(strdup($0)) } + mp_event_drop_files(input, Int32(files.count), &filesPtr, action) + for charPtr in filesPtr { free(UnsafeMutablePointer(mutating: charPtr)) } + } + } + + private func useAltGr() -> Bool { + guard let input = input else { return false } + return mp_input_use_alt_gr(input) + } + + @objc func wakeup() { + lock.withLock { + guard let input = input else { return } + mp_input_wakeup(input) + } + } + + func signal(input: OpaquePointer? = nil) { + lock.withLock { + self.input = input + if input != nil { lock.signal() } + } + } + + @objc func wait() { + lock.withLock { while input == nil { lock.wait() } } + } +} diff --git a/osdep/macos/libmpv_helper.swift b/osdep/mac/libmpv_helper.swift index 8b1c697..23953eb 100644 --- a/osdep/macos/libmpv_helper.swift +++ b/osdep/mac/libmpv_helper.swift @@ -23,27 +23,14 @@ let glDummy: @convention(c) () -> Void = {} class LibmpvHelper { var log: LogHelper - var mpvHandle: OpaquePointer? + var mpv: OpaquePointer? var mpvRenderContext: OpaquePointer? - var macOptsPtr: UnsafeMutableRawPointer? - var macOpts: macos_opts = macos_opts() var fbo: GLint = 1 - let deinitLock = NSLock() - - init(_ mpv: OpaquePointer, _ mpLog: OpaquePointer?) { - mpvHandle = mpv - log = LogHelper(mpLog) - - guard let app = NSApp as? Application, - let ptr = mp_get_config_group(nil, - mp_client_get_global(mpvHandle), - app.getMacOSConf()) else - { - log.sendError("macOS config group couldn't be retrieved'") - exit(1) - } - macOptsPtr = ptr - macOpts = UnsafeMutablePointer<macos_opts>(OpaquePointer(ptr)).pointee + let uninitLock = NSLock() + + init(_ mpv: OpaquePointer, _ log: LogHelper) { + self.mpv = mpv + self.log = log } func initRender() { @@ -52,7 +39,7 @@ class LibmpvHelper { let pAddress = mpv_opengl_init_params(get_proc_address: getProcAddress, get_proc_address_ctx: nil) - MPVHelper.withUnsafeMutableRawPointers([pAddress, advanced]) { (pointers: [UnsafeMutableRawPointer?]) in + TypeHelper.withUnsafeMutableRawPointers([pAddress, advanced]) { (pointers: [UnsafeMutableRawPointer?]) in var params: [mpv_render_param] = [ mpv_render_param(type: MPV_RENDER_PARAM_API_TYPE, data: api), mpv_render_param(type: MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, data: pointers[0]), @@ -60,12 +47,11 @@ class LibmpvHelper { mpv_render_param() ] - if (mpv_render_context_create(&mpvRenderContext, mpvHandle, ¶ms) < 0) { - log.sendError("Render context init has failed.") + if (mpv_render_context_create(&mpvRenderContext, mpv, ¶ms) < 0) { + log.error("Render context init has failed.") exit(1) } } - } let getProcAddress: (@convention(c) (UnsafeMutableRawPointer?, UnsafePointer<Int8>?) @@ -87,17 +73,17 @@ class LibmpvHelper { func setRenderUpdateCallback(_ callback: @escaping mpv_render_update_fn, context object: AnyObject) { if mpvRenderContext == nil { - log.sendWarning("Init mpv render context first.") + log.warning("Init mpv render context first.") } else { - mpv_render_context_set_update_callback(mpvRenderContext, callback, MPVHelper.bridge(obj: object)) + mpv_render_context_set_update_callback(mpvRenderContext, callback, TypeHelper.bridge(obj: object)) } } func setRenderControlCallback(_ callback: @escaping mp_render_cb_control_fn, context object: AnyObject) { if mpvRenderContext == nil { - log.sendWarning("Init mpv render context first.") + log.warning("Init mpv render context first.") } else { - mp_render_context_set_control_callback(mpvRenderContext, callback, MPVHelper.bridge(obj: object)) + mp_render_context_set_control_callback(mpvRenderContext, callback, TypeHelper.bridge(obj: object)) } } @@ -107,18 +93,18 @@ class LibmpvHelper { } func isRenderUpdateFrame() -> Bool { - deinitLock.lock() + uninitLock.lock() if mpvRenderContext == nil { - deinitLock.unlock() + uninitLock.unlock() return false } let flags: UInt64 = mpv_render_context_update(mpvRenderContext) - deinitLock.unlock() + uninitLock.unlock() return flags & UInt64(MPV_RENDER_UPDATE_FRAME.rawValue) > 0 } func drawRender(_ surface: NSSize, _ depth: GLint, _ ctx: CGLContextObj, skip: Bool = false) { - deinitLock.lock() + uninitLock.lock() if mpvRenderContext != nil { var i: GLint = 0 let flip: CInt = 1 @@ -134,7 +120,7 @@ class LibmpvHelper { h: Int32(surface.height), internal_format: 0) - MPVHelper.withUnsafeMutableRawPointers([data, flip, ditherDepth, skip]) { (pointers: [UnsafeMutableRawPointer?]) in + TypeHelper.withUnsafeMutableRawPointers([data, flip, ditherDepth, skip]) { (pointers: [UnsafeMutableRawPointer?]) in var params: [mpv_render_param] = [ mpv_render_param(type: MPV_RENDER_PARAM_OPENGL_FBO, data: pointers[0]), mpv_render_param(type: MPV_RENDER_PARAM_FLIP_Y, data: pointers[1]), @@ -151,13 +137,13 @@ class LibmpvHelper { if !skip { CGLFlushDrawable(ctx) } - deinitLock.unlock() + uninitLock.unlock() } func setRenderICCProfile(_ profile: NSColorSpace) { if mpvRenderContext == nil { return } guard var iccData = profile.iccProfileData else { - log.sendWarning("Invalid ICC profile data.") + log.warning("Invalid ICC profile data.") return } iccData.withUnsafeMutableBytes { (ptr: UnsafeMutableRawBufferPointer) in @@ -182,69 +168,14 @@ class LibmpvHelper { } } - func commandAsync(_ cmd: [String?], id: UInt64 = 1) { - if mpvHandle == nil { return } - var mCmd = cmd - mCmd.append(nil) - var cargs = mCmd.map { $0.flatMap { UnsafePointer<Int8>(strdup($0)) } } - mpv_command_async(mpvHandle, id, &cargs) - for ptr in cargs { free(UnsafeMutablePointer(mutating: ptr)) } - } - - // Unsafe function when called while using the render API - func command(_ cmd: String) { - if mpvHandle == nil { return } - mpv_command_string(mpvHandle, cmd) - } - - func getBoolProperty(_ name: String) -> Bool { - if mpvHandle == nil { return false } - var value = Int32() - mpv_get_property(mpvHandle, name, MPV_FORMAT_FLAG, &value) - return value > 0 - } - - func getIntProperty(_ name: String) -> Int { - if mpvHandle == nil { return 0 } - var value = Int64() - mpv_get_property(mpvHandle, name, MPV_FORMAT_INT64, &value) - return Int(value) - } - - func getStringProperty(_ name: String) -> String? { - guard let mpv = mpvHandle else { return nil } - guard let value = mpv_get_property_string(mpv, name) else { return nil } - let str = String(cString: value) - mpv_free(value) - return str - } - - func deinitRender() { + func uninit() { mpv_render_context_set_update_callback(mpvRenderContext, nil, nil) mp_render_context_set_control_callback(mpvRenderContext, nil, nil) - deinitLock.lock() + uninitLock.lock() mpv_render_context_free(mpvRenderContext) mpvRenderContext = nil - deinitLock.unlock() - } - - func deinitMPV(_ destroy: Bool = false) { - if destroy { - mpv_destroy(mpvHandle) - } - ta_free(macOptsPtr) - macOptsPtr = nil - mpvHandle = nil - } - - // *(char **) MPV_FORMAT_STRING on mpv_event_property - class func mpvStringArrayToString(_ obj: UnsafeMutableRawPointer) -> String? { - let cstr = UnsafeMutablePointer<UnsafeMutablePointer<Int8>>(OpaquePointer(obj)) - return String(cString: cstr[0]) - } - - // MPV_FORMAT_FLAG - class func mpvFlagToBool(_ obj: UnsafeMutableRawPointer) -> Bool? { - return UnsafePointer<Bool>(OpaquePointer(obj))?.pointee + mpv_destroy(mpv) + mpv = nil + uninitLock.unlock() } } diff --git a/osdep/mac/log_helper.swift b/osdep/mac/log_helper.swift new file mode 100644 index 0000000..3d349a4 --- /dev/null +++ b/osdep/mac/log_helper.swift @@ -0,0 +1,67 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +import Cocoa +import os + +class LogHelper { + var log: OpaquePointer? + let logger = Logger(subsystem: "io.mpv", category: "mpv") + + let loggerMapping: [Int:OSLogType] = [ + MSGL_V: .debug, + MSGL_INFO: .info, + MSGL_WARN: .error, + MSGL_ERR: .fault, + ] + + init(_ log: OpaquePointer? = nil) { + self.log = log + } + + func verbose(_ message: String) { + send(message: message, type: MSGL_V) + } + + func info(_ message: String) { + send(message: message, type: MSGL_INFO) + } + + func warning(_ message: String) { + send(message: message, type: MSGL_WARN) + } + + func error(_ message: String) { + send(message: message, type: MSGL_ERR) + } + + func send(message: String, type: Int) { + guard let log = log else { + logger.log(level: loggerMapping[type] ?? .default, "\(message, privacy: .public)") + return + } + + let args: [CVarArg] = [(message as NSString).utf8String ?? "NO MESSAGE"] + mp_msg_va(log, Int32(type), "%s\n", getVaList(args)) + } + + deinit { + // only a manual dereferencing will trigger this, cleanup properly in that case + ta_free(UnsafeMutablePointer(log)) + log = nil + } +} diff --git a/osdep/mac/menu_bar.swift b/osdep/mac/menu_bar.swift new file mode 100644 index 0000000..b58367a --- /dev/null +++ b/osdep/mac/menu_bar.swift @@ -0,0 +1,397 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +import Cocoa + +extension MenuBar { + class MenuItem: NSMenuItem { + var config: Config? + } + + enum `Type`: Comparable { + case menu + case menuServices + case separator + case item + case itemNormalSize + case itemHalfSize + case itemDoubleSize + case itemMinimize + case itemZoom + } + + struct Config { + let name: String + let key: String + let modifiers: NSEvent.ModifierFlags + let type: Type + let action: Selector? + let target: AnyObject? + let command: String + let url: String + var configs: [Config] + + init( + name: String = "", + key: String = "", + modifiers: NSEvent.ModifierFlags = .command, + type: Type = .item, + action: Selector? = nil, + target: AnyObject? = nil, + command: String = "", + url: String = "", + configs: [Config] = [] + ) { + self.name = name + self.key = key + self.modifiers = modifiers + self.type = configs.isEmpty ? type : .menu + self.action = action + self.target = target + self.command = command + self.url = url + self.configs = configs + } + } +} + +class MenuBar: NSObject { + unowned let appHub: AppHub + let mainMenu = NSMenu(title: "Main") + let servicesMenu = NSMenu(title: "Services") + var menuConfigs: [Config] = [] + var dynamicMenuItems: [Type:[MenuItem]] = [:] + let appIcon: NSImage + + @objc init(_ appHub: AppHub) { + self.appHub = appHub + UserDefaults.standard.set(false, forKey: "NSFullScreenMenuItemEverywhere") + UserDefaults.standard.set(true, forKey: "NSDisabledDictationMenuItem") + UserDefaults.standard.set(true, forKey: "NSDisabledCharacterPaletteMenuItem") + NSWindow.allowsAutomaticWindowTabbing = false + appIcon = appHub.getIcon() + + super.init() + + let appMenuConfigs = [ + Config(name: "About mpv", action: #selector(about), target: self), + Config(type: .separator), + Config( + name: "Settings…", + key: ",", + action: #selector(settings(_:)), + target: self, + url: "mpv.conf" + ), + Config( + name: "Keyboard Shortcuts Config…", + action: #selector(settings(_:)), + target: self, + url: "input.conf" + ), + Config(type: .separator), + Config(name: "Services", type: .menuServices), + Config(type: .separator), + Config(name: "Hide mpv", key: "h", action: #selector(NSApp.hide(_:))), + Config(name: "Hide Others", key: "h", modifiers: [.command, .option], action: #selector(NSApp.hideOtherApplications(_:))), + Config(name: "Show All", action: #selector(NSApp.unhideAllApplications(_:))), + Config(type: .separator), + Config(name: "Quit and Remember Position", action: #selector(command(_:)), target: self, command: "quit-watch-later"), + Config(name: "Quit mpv", key: "q", action: #selector(command(_:)), target: self, command: "quit"), + ] + + let fileMenuConfigs = [ + Config(name: "Open File…", key: "o", action: #selector(openFiles), target: self), + Config(name: "Open URL…", key: "O", action: #selector(openUrl), target: self), + Config(name: "Open Playlist…", action: #selector(openPlaylist), target: self), + Config(type: .separator), + Config(name: "Close", key: "w", action: #selector(NSWindow.performClose(_:))), + Config(name: "Save Screenshot", action: #selector(command(_:)), target: self, command: "async screenshot"), + ] + + let editMenuConfigs = [ + Config(name: "Undo", key: "z", action: Selector(("undo:"))), + Config(name: "Redo", key: "Z", action: Selector(("redo:"))), + Config(type: .separator), + Config(name: "Cut", key: "x", action: #selector(NSText.cut(_:))), + Config(name: "Copy", key: "c", action: #selector(NSText.copy(_:))), + Config(name: "Paste", key: "v", action: #selector(NSText.paste(_:))), + Config(name: "Select All", key: "a", action: #selector(NSResponder.selectAll(_:))), + ] + + var viewMenuConfigs = [ + Config(name: "Toggle Fullscreen", action: #selector(command(_:)), target: self, command: "cycle fullscreen"), + Config(name: "Toggle Float on Top", action: #selector(command(_:)), target: self, command: "cycle ontop"), + Config( + name: "Toggle Visibility on All Workspaces", + action: #selector(command(_:)), + target: self, + command: "cycle on-all-workspaces" + ), + ] +#if HAVE_MACOS_TOUCHBAR + viewMenuConfigs += [ + Config(type: .separator), + Config(name: "Customize Touch Bar…", action: #selector(NSApp.toggleTouchBarCustomizationPalette(_:))), + ] +#endif + + let videoMenuConfigs = [ + Config(name: "Zoom Out", action: #selector(command(_:)), target: self, command: "add panscan -0.1"), + Config(name: "Zoom In", action: #selector(command(_:)), target: self, command: "add panscan 0.1"), + Config(name: "Reset Zoom", action: #selector(command(_:)), target: self, command: "set panscan 0"), + Config(type: .separator), + Config(name: "Aspect Ratio 4:3", action: #selector(command(_:)), target: self, command: "set video-aspect-override \"4:3\""), + Config(name: "Aspect Ratio 16:9", action: #selector(command(_:)), target: self, command: "set video-aspect-override \"16:9\""), + Config(name: "Aspect Ratio 1.85:1", action: #selector(command(_:)), target: self, command: "set video-aspect-override \"1.85:1\""), + Config(name: "Aspect Ratio 2.35:1", action: #selector(command(_:)), target: self, command: "set video-aspect-override \"2.35:1\""), + Config(name: "Reset Aspect Ratio", action: #selector(command(_:)), target: self, command: "set video-aspect-override \"-1\""), + Config(type: .separator), + Config(name: "Rotate Left", action: #selector(command(_:)), target: self, command: "cycle-values video-rotate 0 270 180 90"), + Config(name: "Rotate Right", action: #selector(command(_:)), target: self, command: "cycle-values video-rotate 90 180 270 0"), + Config(name: "Reset Rotation", action: #selector(command(_:)), target: self, command: "set video-rotate 0"), + Config(type: .separator), + Config(name: "Half Size", key: "0", type: .itemHalfSize), + Config(name: "Normal Size", key: "1", type: .itemNormalSize), + Config(name: "Double Size", key: "2", type: .itemDoubleSize), + ] + + let audioMenuConfigs = [ + Config(name: "Next Audio Track", action: #selector(command(_:)), target: self, command: "cycle audio"), + Config(name: "Previous Audio Track", action: #selector(command(_:)), target: self, command: "cycle audio down"), + Config(type: .separator), + Config(name: "Toggle Mute", action: #selector(command(_:)), target: self, command: "cycle mute"), + Config(type: .separator), + Config(name: "Play Audio Later", action: #selector(command(_:)), target: self, command: "add audio-delay 0.1"), + Config(name: "Play Audio Earlier", action: #selector(command(_:)), target: self, command: "add audio-delay -0.1"), + Config(name: "Reset Audio Delay", action: #selector(command(_:)), target: self, command: "set audio-delay 0.0"), + ] + + let subtitleMenuConfigs = [ + Config(name: "Next Subtitle Track", action: #selector(command(_:)), target: self, command: "cycle sub"), + Config(name: "Previous Subtitle Track", action: #selector(command(_:)), target: self, command: "cycle sub down"), + Config(type: .separator), + Config(name: "Toggle Force Style", action: #selector(command(_:)), target: self, command: "cycle-values sub-ass-override \"force\" \"no\""), + Config(type: .separator), + Config(name: "Display Subtitles Later", action: #selector(command(_:)), target: self, command: "add sub-delay 0.1"), + Config(name: "Display Subtitles Earlier", action: #selector(command(_:)), target: self, command: "add sub-delay -0.1"), + Config(name: "Reset Subtitle Delay", action: #selector(command(_:)), target: self, command: "set sub-delay 0.0"), + ] + + let playbackMenuConfigs = [ + Config(name: "Toggle Pause", action: #selector(command(_:)), target: self, command: "cycle pause"), + Config(name: "Increase Speed", action: #selector(command(_:)), target: self, command: "add speed 0.1"), + Config(name: "Decrease Speed", action: #selector(command(_:)), target: self, command: "add speed -0.1"), + Config(name: "Reset Speed", action: #selector(command(_:)), target: self, command: "set speed 1.0"), + Config(type: .separator), + Config(name: "Show Playlist", action: #selector(command(_:)), target: self, command: "script-message osc-playlist"), + Config(name: "Show Chapters", action: #selector(command(_:)), target: self, command: "script-message osc-chapterlist"), + Config(name: "Show Tracks", action: #selector(command(_:)), target: self, command: "script-message osc-tracklist"), + Config(type: .separator), + Config(name: "Next File", action: #selector(command(_:)), target: self, command: "playlist-next"), + Config(name: "Previous File", action: #selector(command(_:)), target: self, command: "playlist-prev"), + Config(name: "Toggle Loop File", action: #selector(command(_:)), target: self, command: "cycle-values loop-file \"inf\" \"no\""), + Config(name: "Toggle Loop Playlist", action: #selector(command(_:)), target: self, command: "cycle-values loop-playlist \"inf\" \"no\""), + Config(name: "Shuffle", action: #selector(command(_:)), target: self, command: "playlist-shuffle"), + Config(type: .separator), + Config(name: "Next Chapter", action: #selector(command(_:)), target: self, command: "add chapter 1"), + Config(name: "Previous Chapter", action: #selector(command(_:)), target: self, command: "add chapter -1"), + Config(type: .separator), + Config(name: "Step Forward", action: #selector(command(_:)), target: self, command: "frame-step"), + Config(name: "Step Backward", action: #selector(command(_:)), target: self, command: "frame-back-step"), + ] + + let windowMenuConfigs = [ + Config(name: "Minimize", key: "m", type: .itemMinimize), + Config(name: "Zoom", type: .itemZoom), + ] + + var helpMenuConfigs = [ + Config(name: "mpv Website…", action: #selector(url(_:)), target: self, url: "https://mpv.io"), + Config(name: "mpv on GitHub…", action: #selector(url(_:)), target: self, url: "https://github.com/mpv-player/mpv"), + Config(type: .separator), + Config(name: "Online Manual…", action: #selector(url(_:)), target: self, url: "https://mpv.io/manual/master/"), + Config(name: "Online Wiki…", action: #selector(url(_:)), target: self, url: "https://github.com/mpv-player/mpv/wiki"), + Config(name: "Release Notes…", action: #selector(url(_:)), target: self, url: "https://github.com/mpv-player/mpv/blob/master/RELEASE_NOTES"), + Config(name: "Keyboard Shortcuts…", action: #selector(url(_:)), target: self, url: "https://github.com/mpv-player/mpv/blob/master/etc/input.conf"), + Config(type: .separator), + Config(name: "Report Issue…", action: #selector(url(_:)), target: self, url: "https://github.com/mpv-player/mpv/issues/new/choose"), + ] + if ProcessInfo.processInfo.environment["MPVBUNDLE"] == "true" { + helpMenuConfigs += [ + Config(name: "Show log File…", action: #selector(showFile(_:)), target: self, url: NSHomeDirectory() + "/Library/Logs/mpv.log") + ] + } + + menuConfigs = [ + Config(name: "Apple", configs: appMenuConfigs), + Config(name: "File", configs: fileMenuConfigs), + Config(name: "Edit", configs: editMenuConfigs), + Config(name: "View", configs: viewMenuConfigs), + Config(name: "Video", configs: videoMenuConfigs), + Config(name: "Audio", configs: audioMenuConfigs), + Config(name: "Subtitle", configs: subtitleMenuConfigs), + Config(name: "Playback", configs: playbackMenuConfigs), + Config(name: "Window", configs: windowMenuConfigs), + Config(name: "Help", configs: helpMenuConfigs), + ] + + createMenu(parentMenu: mainMenu, configs: menuConfigs) + NSApp.mainMenu = mainMenu + NSApp.servicesMenu = servicesMenu + } + + func createMenu(parentMenu: NSMenu, configs: [Config]) { + for config in configs { + let item = createMenuItem(parentMenu: parentMenu, config: config) + + if config.type <= .menuServices { + let menu = config.type == .menuServices ? servicesMenu : NSMenu(title: config.name) + item.submenu = menu + createMenu(parentMenu: menu, configs: config.configs) + } + + if config.type > Type.item { + dynamicMenuItems[config.type] = (dynamicMenuItems[config.type] ?? []) + [item] + } + } + } + + func createMenuItem(parentMenu: NSMenu, config: Config) -> MenuItem { + var item = MenuItem(title: config.name, action: config.action, keyEquivalent: config.key) + item.config = config + item.target = config.target + item.keyEquivalentModifierMask = config.modifiers + + if config.type == .separator { + item = MenuItem.separator() as? MenuItem ?? item + } + parentMenu.addItem(item) + + return item + } + + @objc func about() { + NSApp.orderFrontStandardAboutPanel(options: [ + .applicationName: "mpv", + .applicationIcon: appIcon, + .applicationVersion: String(cString: swift_mpv_version), + .init(rawValue: "Copyright"): String(cString: swift_mpv_copyright), + ]) + } + + @objc func settings(_ menuItem: MenuItem) { + guard let menuConfig = menuItem.config else { return } + let configPaths: [URL] = [ + URL(fileURLWithPath: NSHomeDirectory() + "/.config/mpv/", isDirectory: true), + URL(fileURLWithPath: NSHomeDirectory() + "/.mpv/", isDirectory: true), + ] + + for path in configPaths { + let configFile = path.appendingPathComponent(menuConfig.url, isDirectory: false) + + if FileManager.default.fileExists(atPath: configFile.path) { + if NSWorkspace.shared.open(configFile) { + return + } + NSWorkspace.shared.open(path) + alert(title: "No Application found to open your config file.", text: "Please open the \(menuConfig.url) file with your preferred text editor in the now open folder to edit your config.") + return + } + + if NSWorkspace.shared.open(path) { + alert(title: "No config file found.", text: "Please create a \(menuConfig.url) file with your preferred text editor in the now open folder.") + return + } + } + } + + @objc func openFiles() { + let panel = NSOpenPanel() + panel.allowsMultipleSelection = true + panel.canChooseDirectories = true + + if panel.runModal() == .OK { + appHub.input.open(files: panel.urls.map { $0.path }) + } + } + + @objc func openPlaylist() { + let panel = NSOpenPanel() + + if panel.runModal() == .OK, let url = panel.urls.first { + appHub.input.command("loadlist \"\(url.path)\"") + } + } + + @objc func openUrl() { + let alert = NSAlert() + alert.messageText = "Open URL" + alert.icon = appIcon + alert.addButton(withTitle: "Ok") + alert.addButton(withTitle: "Cancel") + + let input = NSTextField(frame: NSRect(x: 0, y: 0, width: 300, height: 24)) + input.placeholderString = "URL" + alert.accessoryView = input + + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) { + input.becomeFirstResponder() + } + + if alert.runModal() == .alertFirstButtonReturn && input.stringValue.count > 0 { + appHub.input.open(files: [input.stringValue]) + } + } + + @objc func command(_ menuItem: MenuItem) { + guard let menuConfig = menuItem.config else { return } + appHub.input.command(menuConfig.command) + } + + @objc func url(_ menuItem: MenuItem) { + guard let menuConfig = menuItem.config, + let url = URL(string: menuConfig.url) else { return } + NSWorkspace.shared.open(url) + } + + @objc func showFile(_ menuItem: MenuItem) { + guard let menuConfig = menuItem.config else { return } + let url = URL(fileURLWithPath: menuConfig.url) + if FileManager.default.fileExists(atPath: url.path) { + NSWorkspace.shared.activateFileViewerSelecting([url]) + return + } + + alert(title: "No log File found.", text: "You deactivated logging for the Bundle.") + } + + func alert(title: String, text: String) { + let alert = NSAlert() + alert.messageText = title + alert.informativeText = text + alert.icon = appIcon + alert.addButton(withTitle: "Ok") + alert.runModal() + } + + func register(_ selector: Selector, key: Type) { + for menuItem in dynamicMenuItems[key] ?? [] { + menuItem.action = selector + } + } +} diff --git a/osdep/meson.build b/osdep/mac/meson.build index 21baafc..8ddbdba 100644 --- a/osdep/meson.build +++ b/osdep/mac/meson.build @@ -1,8 +1,8 @@ # custom swift targets -bridge = join_paths(source_root, 'osdep/macOS_swift_bridge.h') -header = join_paths(build_root, 'osdep/macOS_swift.h') -module = join_paths(build_root, 'osdep/macOS_swift.swiftmodule') -target = join_paths(build_root, 'osdep/macOS_swift.o') +bridge = join_paths(source_root, 'osdep/mac/app_bridge_objc.h') +header = join_paths(build_root, 'osdep/mac/swift.h') +module = join_paths(build_root, 'osdep/mac/swift.swiftmodule') +target = join_paths(build_root, 'osdep/mac/swift.o') swift_flags = ['-frontend', '-c', '-sdk', macos_sdk_path, '-enable-objc-interop', '-emit-objc-header', '-parse-as-library'] @@ -19,10 +19,22 @@ if get_option('optimization') != '0' swift_flags += '-O' endif +if macos_cocoa_cb.allowed() + swift_flags += ['-D', 'HAVE_MACOS_COCOA_CB'] +endif + +if macos_touchbar.allowed() + swift_flags += ['-D', 'HAVE_MACOS_TOUCHBAR'] +endif + +if macos_media_player.allowed() + swift_flags += ['-D', 'HAVE_MACOS_MEDIA_PLAYER'] +endif + extra_flags = get_option('swift-flags').split() swift_flags += extra_flags -swift_compile = [swift_prog, swift_flags, '-module-name', 'macOS_swift', +swift_compile = [swift_prog, swift_flags, '-module-name', 'swift', '-emit-module-path', '@OUTPUT0@', '-import-objc-header', bridge, '-emit-objc-header-path', '@OUTPUT1@', '-o', '@OUTPUT2@', '@INPUT@', '-I.', '-I' + source_root, @@ -31,7 +43,7 @@ swift_compile = [swift_prog, swift_flags, '-module-name', 'macOS_swift', swift_targets = custom_target('swift_targets', input: swift_sources, - output: ['macOS_swift.swiftmodule', 'macOS_swift.h', 'macOS_swift.o'], + output: ['swift.swiftmodule', 'swift.h', 'swift.o'], command: swift_compile, ) sources += swift_targets diff --git a/osdep/mac/option_helper.swift b/osdep/mac/option_helper.swift new file mode 100644 index 0000000..c44f090 --- /dev/null +++ b/osdep/mac/option_helper.swift @@ -0,0 +1,74 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +import Cocoa + +typealias swift_wakeup_cb_fn = (@convention(c) (UnsafeMutableRawPointer?) -> Void)? + +class OptionHelper { + var voCachePtr: UnsafeMutablePointer<m_config_cache> + var macCachePtr: UnsafeMutablePointer<m_config_cache> + + var voPtr: UnsafeMutablePointer<mp_vo_opts> + { get { return UnsafeMutablePointer<mp_vo_opts>(OpaquePointer(voCachePtr.pointee.opts)) } } + var macPtr: UnsafeMutablePointer<macos_opts> + { get { return UnsafeMutablePointer<macos_opts>(OpaquePointer(macCachePtr.pointee.opts)) } } + + // these computed properties return a local copy of the struct accessed: + // - don't use if you rely on the pointers + // - only for reading + var vo: mp_vo_opts { get { return voPtr.pointee } } + var mac: macos_opts { get { return macPtr.pointee } } + + init(_ taParent: UnsafeMutableRawPointer, _ global: OpaquePointer?) { + voCachePtr = m_config_cache_alloc(taParent, global, AppHub.shared.getVoConf()) + macCachePtr = m_config_cache_alloc(taParent, global, AppHub.shared.getMacConf()) + } + + func nextChangedOption(property: inout UnsafeMutableRawPointer?) -> Bool { + return m_config_cache_get_next_changed(voCachePtr, &property) + } + + func setOption(fullscreen: Bool) { + voPtr.pointee.fullscreen = fullscreen + _ = withUnsafeMutableBytes(of: &voPtr.pointee.fullscreen) { (ptr: UnsafeMutableRawBufferPointer) in + m_config_cache_write_opt(voCachePtr, ptr.baseAddress) + } + } + + func setOption(minimized: Bool) { + voPtr.pointee.window_minimized = minimized + _ = withUnsafeMutableBytes(of: &voPtr.pointee.window_minimized) { (ptr: UnsafeMutableRawBufferPointer) in + m_config_cache_write_opt(voCachePtr, ptr.baseAddress) + } + } + + func setOption(maximized: Bool) { + voPtr.pointee.window_maximized = maximized + _ = withUnsafeMutableBytes(of: &voPtr.pointee.window_maximized) { (ptr: UnsafeMutableRawBufferPointer) in + m_config_cache_write_opt(voCachePtr, ptr.baseAddress) + } + } + + func setMacOptionCallback(_ callback: swift_wakeup_cb_fn, context object: AnyObject) { + m_config_cache_set_wakeup_cb(macCachePtr, callback, TypeHelper.bridge(obj: object)) + } + + func nextChangedMacOption(property: inout UnsafeMutableRawPointer?) -> Bool { + return m_config_cache_get_next_changed(macCachePtr, &property) + } +} diff --git a/osdep/macos/precise_timer.swift b/osdep/mac/precise_timer.swift index f4ad3bb..d4837f9 100644 --- a/osdep/macos/precise_timer.swift +++ b/osdep/mac/precise_timer.swift @@ -24,7 +24,6 @@ struct Timing { class PreciseTimer { unowned var common: Common - var mpv: MPVHelper? { get { return common.mpv } } let nanoPerSecond: Double = 1e+9 let machToNano: Double = { @@ -59,9 +58,9 @@ class PreciseTimer { init?(common com: Common) { common = com - pthread_create(&thread, &threadAttr, entryC, MPVHelper.bridge(obj: self)) + pthread_create(&thread, &threadAttr, entryC, TypeHelper.bridge(obj: self)) if thread == nil { - common.log.sendWarning("Couldn't create pthread for high precision timer") + common.log.warning("Couldn't create pthread for high precision timer") return nil } @@ -85,7 +84,7 @@ class PreciseTimer { isHighPrecision = success == KERN_SUCCESS if !isHighPrecision { - common.log.sendWarning("Couldn't create a high precision timer") + common.log.warning("Couldn't create a high precision timer") } } @@ -119,7 +118,7 @@ class PreciseTimer { let threadSignal: @convention(c) (Int32) -> () = { (sig: Int32) in } let entryC: @convention(c) (UnsafeMutableRawPointer) -> UnsafeMutableRawPointer? = { (ptr: UnsafeMutableRawPointer) in - let ptimer: PreciseTimer = MPVHelper.bridge(ptr: ptr) + let ptimer: PreciseTimer = TypeHelper.bridge(ptr: ptr) ptimer.entry() return nil } diff --git a/osdep/mac/presentation.swift b/osdep/mac/presentation.swift new file mode 100644 index 0000000..c1d521a --- /dev/null +++ b/osdep/mac/presentation.swift @@ -0,0 +1,56 @@ +/* + * This file is part of mpv. + * + * mpv is free software) you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation) either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY) without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +extension Presentation { + struct Time { + let cvTime: CVTimeStamp + var skipped: Int64 = 0 + var time: Int64 { return mp_time_ns_from_raw_time(mp_raw_time_ns_from_mach(cvTime.hostTime)) } + var duration: Int64 { + let durationSeconds = Double(cvTime.videoRefreshPeriod) / Double(cvTime.videoTimeScale) + return Int64(durationSeconds * Presentation.nanoPerSecond * cvTime.rateScalar) + } + + init(_ time: CVTimeStamp) { + cvTime = time + } + } +} + +class Presentation { + unowned var common: Common + var times: [Time] = [] + static let nanoPerSecond: Double = 1e+9 + + init(common com: Common) { + common = com + } + + func add(time: CVTimeStamp) { + times.append(Time(time)) + } + + func next() -> Time? { + let now = mp_time_ns() + let count = times.count + times.removeAll(where: { $0.time <= now }) + var time = times.first + time?.skipped = Int64(max(count - times.count - 1, 0)) + + return time + } +} diff --git a/osdep/mac/remote_command_center.swift b/osdep/mac/remote_command_center.swift new file mode 100644 index 0000000..0e0eaeb --- /dev/null +++ b/osdep/mac/remote_command_center.swift @@ -0,0 +1,202 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +import Cocoa +import MediaPlayer + +extension RemoteCommandCenter { + typealias ConfigHandler = (MPRemoteCommandEvent) -> (MPRemoteCommandHandlerStatus) + + enum KeyType { + case normal + case repeatable + } + + struct Config { + let key: Int32 + let type: KeyType + var state: NSEvent.EventType = .applicationDefined + let handler: ConfigHandler + + init(key: Int32 = 0, type: KeyType = .normal, handler: @escaping ConfigHandler = { event in return .commandFailed }) { + self.key = key + self.type = type + self.handler = handler + } + } +} + +class RemoteCommandCenter: EventSubscriber { + unowned let appHub: AppHub + var event: EventHelper? { get { return appHub.event } } + var input: InputHelper { get { return appHub.input } } + var configs: [MPRemoteCommand:Config] = [:] + var disabledCommands: [MPRemoteCommand] = [] + var isPaused: Bool = false { didSet { updateInfoCenter() } } + var duration: Double = 0 { didSet { updateInfoCenter() } } + var position: Double = 0 { didSet { updateInfoCenter() } } + var rate: Double = 1 { didSet { updateInfoCenter() } } + var title: String = "" { didSet { updateInfoCenter() } } + var chapter: String? { didSet { updateInfoCenter() } } + var album: String? { didSet { updateInfoCenter() } } + var artist: String? { didSet { updateInfoCenter() } } + var cover: NSImage + + var infoCenter: MPNowPlayingInfoCenter { get { return MPNowPlayingInfoCenter.default() } } + var commandCenter: MPRemoteCommandCenter { get { return MPRemoteCommandCenter.shared() } } + + init(_ appHub: AppHub) { + self.appHub = appHub + cover = appHub.getIcon() + + configs = [ + commandCenter.pauseCommand: Config(key: MP_KEY_PAUSEONLY, handler: keyHandler), + commandCenter.playCommand: Config(key: MP_KEY_PLAYONLY, handler: keyHandler), + commandCenter.stopCommand: Config(key: MP_KEY_STOP, handler: keyHandler), + commandCenter.nextTrackCommand: Config(key: MP_KEY_NEXT, handler: keyHandler), + commandCenter.previousTrackCommand: Config(key: MP_KEY_PREV, handler: keyHandler), + commandCenter.togglePlayPauseCommand: Config(key: MP_KEY_PLAY, handler: keyHandler), + commandCenter.seekForwardCommand: Config(key: MP_KEY_FORWARD, type: .repeatable, handler: keyHandler), + commandCenter.seekBackwardCommand: Config(key: MP_KEY_REWIND, type: .repeatable, handler: keyHandler), + commandCenter.changePlaybackPositionCommand: Config(handler: seekHandler), + ] + + disabledCommands = [ + commandCenter.changePlaybackRateCommand, + commandCenter.changeRepeatModeCommand, + commandCenter.changeShuffleModeCommand, + commandCenter.skipForwardCommand, + commandCenter.skipBackwardCommand, + commandCenter.enableLanguageOptionCommand, + commandCenter.disableLanguageOptionCommand, + commandCenter.ratingCommand, + commandCenter.likeCommand, + commandCenter.dislikeCommand, + commandCenter.bookmarkCommand, + ] + + for cmd in disabledCommands { + cmd.isEnabled = false + } + } + + func registerEvents() { + event?.subscribe(self, event: .init(name: "duration", format: MPV_FORMAT_DOUBLE)) + event?.subscribe(self, event: .init(name: "time-pos", format: MPV_FORMAT_DOUBLE)) + event?.subscribe(self, event: .init(name: "speed", format: MPV_FORMAT_DOUBLE)) + event?.subscribe(self, event: .init(name: "pause", format: MPV_FORMAT_FLAG)) + event?.subscribe(self, event: .init(name: "media-title", format: MPV_FORMAT_STRING)) + event?.subscribe(self, event: .init(name: "chapter-metadata/title", format: MPV_FORMAT_STRING)) + event?.subscribe(self, event: .init(name: "metadata/by-key/album", format: MPV_FORMAT_STRING)) + event?.subscribe(self, event: .init(name: "metadata/by-key/artist", format: MPV_FORMAT_STRING)) + } + + func start() { + for (cmd, config) in configs { + cmd.isEnabled = true + cmd.addTarget(handler: config.handler) + } + + updateInfoCenter() + + NotificationCenter.default.addObserver( + self, + selector: #selector(self.makeCurrent), + name: NSApplication.willBecomeActiveNotification, + object: nil + ) + } + + func stop() { + for (cmd, _) in configs { + cmd.isEnabled = false + cmd.removeTarget(nil) + } + + infoCenter.nowPlayingInfo = nil + infoCenter.playbackState = .unknown + + NotificationCenter.default.removeObserver( + self, + name: NSApplication.willBecomeActiveNotification, + object: nil + ) + } + + @objc func makeCurrent(notification: NSNotification) { + infoCenter.playbackState = .paused + infoCenter.playbackState = .playing + updateInfoCenter() + } + + func updateInfoCenter() { + infoCenter.playbackState = isPaused ? .paused : .playing + infoCenter.nowPlayingInfo = (infoCenter.nowPlayingInfo ?? [:]).merging([ + MPNowPlayingInfoPropertyMediaType: NSNumber(value: MPNowPlayingInfoMediaType.video.rawValue), + MPNowPlayingInfoPropertyPlaybackProgress: NSNumber(value: 0.0), + MPNowPlayingInfoPropertyPlaybackRate: NSNumber(value: isPaused ? 0 : rate), + MPNowPlayingInfoPropertyElapsedPlaybackTime: NSNumber(value: position), + MPMediaItemPropertyPlaybackDuration: NSNumber(value: duration), + MPMediaItemPropertyTitle: title, + MPMediaItemPropertyArtist: artist ?? chapter ?? "", + MPMediaItemPropertyAlbumTitle: album ?? "", + MPMediaItemPropertyArtwork: MPMediaItemArtwork(boundsSize: cover.size) { _ in return self.cover } + ]) { (_, new) in new } + } + + lazy var keyHandler: ConfigHandler = { event in + guard let config = self.configs[event.command] else { + return .commandFailed + } + + var state = config.state + if config.type == .repeatable { + state = config.state == .keyDown ? .keyUp : .keyDown + self.configs[event.command]?.state = state + } + self.input.put(key: config.key, type: state) + + return .success + } + + lazy var seekHandler: ConfigHandler = { event in + guard let posEvent = event as? MPChangePlaybackPositionCommandEvent else { + return .commandFailed + } + + let cmd = String(format: "seek %.02f absolute", posEvent.positionTime) + return self.input.command(cmd) ? .success : .commandFailed + } + + func handle(event: EventHelper.Event) { + switch event.name { + case "time-pos": + let newPosition = max(event.double ?? 0, 0) + if Int((floor(newPosition) - floor(position)) / rate) != 0 { + position = newPosition + } + case "pause": isPaused = event.bool ?? false + case "duration": duration = event.double ?? 0 + case "speed": rate = event.double ?? 1 + case "media-title": title = event.string ?? "" + case "chapter-metadata/title": chapter = event.string + case "metadata/by-key/album": album = event.string + case "metadata/by-key/artist": artist = event.string + default: break + } + } +} diff --git a/osdep/macos/swift_compat.swift b/osdep/mac/swift_compat.swift index 83059da..83059da 100644 --- a/osdep/macos/swift_compat.swift +++ b/osdep/mac/swift_compat.swift diff --git a/osdep/mac/swift_extensions.swift b/osdep/mac/swift_extensions.swift new file mode 100644 index 0000000..ed6c86c --- /dev/null +++ b/osdep/mac/swift_extensions.swift @@ -0,0 +1,93 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +import Cocoa +import IOKit.hidsystem + +extension NSDeviceDescriptionKey { + static let screenNumber = NSDeviceDescriptionKey("NSScreenNumber") +} + +extension NSScreen { + public var displayID: CGDirectDisplayID { + get { + return deviceDescription[.screenNumber] as? CGDirectDisplayID ?? 0 + } + } +} + +extension NSColor { + convenience init(hex: String) { + let int = Int(hex.dropFirst(), radix: 16) ?? 0 + let alpha = CGFloat((int >> 24) & 0x000000FF)/255 + let red = CGFloat((int >> 16) & 0x000000FF)/255 + let green = CGFloat((int >> 8) & 0x000000FF)/255 + let blue = CGFloat((int) & 0x000000FF)/255 + + self.init(calibratedRed: red, green: green, blue: blue, alpha: alpha) + } +} + +extension NSEvent.ModifierFlags { + public static var optionLeft: NSEvent.ModifierFlags = .init(rawValue: UInt(NX_DEVICELALTKEYMASK)) + public static var optionRight: NSEvent.ModifierFlags = .init(rawValue: UInt(NX_DEVICERALTKEYMASK)) +} + +extension mp_keymap { + init(_ f: Int, _ t: Int32) { + self.init(from: Int32(f), to: t) + } +} + +extension mpv_event_id: CustomStringConvertible { + public var description: String { + switch self { + case MPV_EVENT_NONE: return "MPV_EVENT_NONE2" + case MPV_EVENT_SHUTDOWN: return "MPV_EVENT_SHUTDOWN" + case MPV_EVENT_LOG_MESSAGE: return "MPV_EVENT_LOG_MESSAGE" + case MPV_EVENT_GET_PROPERTY_REPLY: return "MPV_EVENT_GET_PROPERTY_REPLY" + case MPV_EVENT_SET_PROPERTY_REPLY: return "MPV_EVENT_SET_PROPERTY_REPLY" + case MPV_EVENT_COMMAND_REPLY: return "MPV_EVENT_COMMAND_REPLY" + case MPV_EVENT_START_FILE: return "MPV_EVENT_START_FILE" + case MPV_EVENT_END_FILE: return "MPV_EVENT_END_FILE" + case MPV_EVENT_FILE_LOADED: return "MPV_EVENT_FILE_LOADED" + case MPV_EVENT_IDLE: return "MPV_EVENT_IDLE" + case MPV_EVENT_TICK: return "MPV_EVENT_TICK" + case MPV_EVENT_CLIENT_MESSAGE: return "MPV_EVENT_CLIENT_MESSAGE" + case MPV_EVENT_VIDEO_RECONFIG: return "MPV_EVENT_VIDEO_RECONFIG" + case MPV_EVENT_AUDIO_RECONFIG: return "MPV_EVENT_AUDIO_RECONFIG" + case MPV_EVENT_SEEK: return "MPV_EVENT_SEEK" + case MPV_EVENT_PLAYBACK_RESTART: return "MPV_EVENT_PLAYBACK_RESTART" + case MPV_EVENT_PROPERTY_CHANGE: return "MPV_EVENT_PROPERTY_CHANGE" + case MPV_EVENT_QUEUE_OVERFLOW: return "MPV_EVENT_QUEUE_OVERFLOW" + case MPV_EVENT_HOOK: return "MPV_EVENT_HOOK" + default: return "MPV_EVENT_" + String(self.rawValue) + } + } +} + +extension Bool { + init(_ int32: Int32) { + self.init(int32 != 0) + } +} + +extension Int32 { + init(_ bool: Bool) { + self.init(bool ? 1 : 0) + } +} diff --git a/osdep/mac/touch_bar.swift b/osdep/mac/touch_bar.swift new file mode 100644 index 0000000..8e64c51 --- /dev/null +++ b/osdep/mac/touch_bar.swift @@ -0,0 +1,297 @@ +/* + * This file is part of mpv. + * + * mpv is free software) you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation) either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY) without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +import Cocoa + +extension NSTouchBar.CustomizationIdentifier { + public static let customId: NSTouchBar.CustomizationIdentifier = "io.mpv.touchbar" +} + +extension NSTouchBarItem.Identifier { + public static let seekBar = NSTouchBarItem.Identifier(custom: ".seekbar") + public static let play = NSTouchBarItem.Identifier(custom: ".play") + public static let nextItem = NSTouchBarItem.Identifier(custom: ".nextItem") + public static let previousItem = NSTouchBarItem.Identifier(custom: ".previousItem") + public static let nextChapter = NSTouchBarItem.Identifier(custom: ".nextChapter") + public static let previousChapter = NSTouchBarItem.Identifier(custom: ".previousChapter") + public static let cycleAudio = NSTouchBarItem.Identifier(custom: ".cycleAudio") + public static let cycleSubtitle = NSTouchBarItem.Identifier(custom: ".cycleSubtitle") + public static let currentPosition = NSTouchBarItem.Identifier(custom: ".currentPosition") + public static let timeLeft = NSTouchBarItem.Identifier(custom: ".timeLeft") + + init(custom: String) { + self.init(NSTouchBar.CustomizationIdentifier.customId + custom) + } +} + +extension TouchBar { + typealias ViewHandler = (Config) -> (NSView) + + struct Config { + let name: String + let command: String + var item: NSCustomTouchBarItem? + var constraint: NSLayoutConstraint? + let image: NSImage + let imageAlt: NSImage + let handler: ViewHandler + + init( + name: String = "", + command: String = "", + item: NSCustomTouchBarItem? = nil, + constraint: NSLayoutConstraint? = nil, + image: NSImage? = nil, + imageAlt: NSImage? = nil, + handler: @escaping ViewHandler = { _ in return NSButton(title: "", target: nil, action: nil) } + ) { + self.name = name + self.command = command + self.item = item + self.constraint = constraint + self.image = image ?? NSImage(size: NSSize(width: 1, height: 1)) + self.imageAlt = imageAlt ?? NSImage(size: NSSize(width: 1, height: 1)) + self.handler = handler + } + } +} + +class TouchBar: NSTouchBar, NSTouchBarDelegate, EventSubscriber { + unowned let appHub: AppHub + var event: EventHelper? { get { return appHub.event } } + var input: InputHelper { get { return appHub.input } } + var configs: [NSTouchBarItem.Identifier:Config] = [:] + var isPaused: Bool = false { didSet { updatePlayButton() } } + var position: Double = 0 { didSet { updateTouchBarTimeItems() } } + var duration: Double = 0 { didSet { updateTouchBarTimeItems() } } + var rate: Double = 1 + + init(_ appHub: AppHub) { + self.appHub = appHub + super.init() + + configs = [ + .seekBar: Config(name: "Seek Bar", command: "seek %f absolute-percent", handler: createSlider), + .currentPosition: Config(name: "Current Position", handler: createText), + .timeLeft: Config(name: "Time Left", handler: createText), + .play: Config( + name: "Play Button", + command: "cycle pause", + image: .init(named: NSImage.touchBarPauseTemplateName), + imageAlt: .init(named: NSImage.touchBarPlayTemplateName), + handler: createButton + ), + .previousItem: Config( + name: "Previous Playlist Item", + command: "playlist-prev", + image: .init(named: NSImage.touchBarGoBackTemplateName), + handler: createButton + ), + .nextItem: Config( + name: "Next Playlist Item", + command: "playlist-next", + image: .init(named: NSImage.touchBarGoForwardTemplateName), + handler: createButton + ), + .previousChapter: Config( + name: "Previous Chapter", + command: "add chapter -1", + image: .init(named: NSImage.touchBarSkipBackTemplateName), + handler: createButton + ), + .nextChapter: Config( + name: "Next Chapter", + command: "add chapter 1", + image: .init(named: NSImage.touchBarSkipAheadTemplateName), + handler: createButton + ), + .cycleAudio: Config( + name: "Cycle Audio", + command: "cycle audio", + image: .init(named: NSImage.touchBarAudioInputTemplateName), + handler: createButton + ), + .cycleSubtitle: Config( + name: "Cycle Subtitle", + command: "cycle sub", + image: .init(named: NSImage.touchBarComposeTemplateName), + handler: createButton + ) + ] + + delegate = self + customizationIdentifier = .customId; + defaultItemIdentifiers = [.play, .previousItem, .nextItem, .seekBar] + customizationAllowedItemIdentifiers = [.play, .seekBar, .previousItem, .nextItem, + .previousChapter, .nextChapter, .cycleAudio, .cycleSubtitle, .currentPosition, .timeLeft] + addObserver(self, forKeyPath: "visible", options: [.new], context: nil) + + event?.subscribe(self, event: .init(name: "duration", format: MPV_FORMAT_DOUBLE)) + event?.subscribe(self, event: .init(name: "time-pos", format: MPV_FORMAT_DOUBLE)) + event?.subscribe(self, event: .init(name: "speed", format: MPV_FORMAT_DOUBLE)) + event?.subscribe(self, event: .init(name: "pause", format: MPV_FORMAT_FLAG)) + event?.subscribe(self, event: .init(name: "MPV_EVENT_END_FILE")) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func touchBar(_ touchBar: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItem.Identifier) -> NSTouchBarItem? { + guard let config = configs[identifier] else { return nil } + + let item = NSCustomTouchBarItem(identifier: identifier) + item.view = config.handler(config) + item.customizationLabel = config.name + configs[identifier]?.item = item + item.addObserver(self, forKeyPath: "visible", options: [.new], context: nil) + return item + } + + lazy var createButton: ViewHandler = { config in + return NSButton(image: config.image, target: self, action: #selector(Self.buttonAction(_:))) + } + + lazy var createText: ViewHandler = { config in + let text = NSTextField(labelWithString: "0:00") + text.alignment = .center + return text + } + + lazy var createSlider: ViewHandler = { config in + let slider = NSSlider(target: self, action: #selector(Self.seekbarChanged(_:))) + slider.minValue = 0 + slider.maxValue = 100 + return slider + } + + override func observeValue( + forKeyPath keyPath: String?, + of object: Any?, + change: [NSKeyValueChangeKey:Any]?, + context: UnsafeMutableRawPointer? + ) { + guard let visible = change?[.newKey] as? Bool else { return } + if keyPath == "isVisible" && visible { + updateTouchBarTimeItems() + updatePlayButton() + } + } + + func updateTouchBarTimeItems() { + if !isVisible { return } + updateSlider() + updateTimeLeft() + updateCurrentPosition() + } + + func updateSlider() { + guard let config = configs[.seekBar], let slider = config.item?.view as? NSSlider else { return } + if !(config.item?.isVisible ?? false) { return } + + slider.isEnabled = duration > 0 + if !slider.isHighlighted { + slider.doubleValue = slider.isEnabled ? (position / duration) * 100 : 0 + } + } + + func updateTimeLeft() { + guard let config = configs[.timeLeft], let text = config.item?.view as? NSTextField else { return } + if !(config.item?.isVisible ?? false) { return } + + removeConstraintFor(identifier: .timeLeft) + text.stringValue = duration > 0 ? "-" + format(time: Int(floor(duration) - floor(position))) : "" + if !text.stringValue.isEmpty { + applyConstraintFrom(string: "-" + format(time: Int(duration)), identifier: .timeLeft) + } + } + + func updateCurrentPosition() { + guard let config = configs[.currentPosition], let text = config.item?.view as? NSTextField else { return } + if !(config.item?.isVisible ?? false) { return } + + text.stringValue = format(time: Int(floor(position))) + removeConstraintFor(identifier: .currentPosition) + applyConstraintFrom(string: format(time: Int(duration > 0 ? duration : position)), identifier: .currentPosition) + } + + func updatePlayButton() { + guard let config = configs[.play], let button = config.item?.view as? NSButton else { return } + if !isVisible || !(config.item?.isVisible ?? false) { return } + button.image = isPaused ? configs[.play]?.imageAlt : configs[.play]?.image + } + + @objc func buttonAction(_ button: NSButton) { + guard let identifier = getIdentifierFrom(view: button), let command = configs[identifier]?.command else { return } + input.command(command) + } + + @objc func seekbarChanged(_ slider: NSSlider) { + guard let identifier = getIdentifierFrom(view: slider), let command = configs[identifier]?.command else { return } + input.command(String(format: command, slider.doubleValue)) + } + + func format(time: Int) -> String { + let formatter = DateComponentsFormatter() + formatter.unitsStyle = .positional + formatter.zeroFormattingBehavior = time >= (60 * 60) ? [.dropLeading] : [] + formatter.allowedUnits = time >= (60 * 60) ? [.hour, .minute, .second] : [.minute, .second] + return formatter.string(from: TimeInterval(time)) ?? "0:00" + } + + func removeConstraintFor(identifier: NSTouchBarItem.Identifier) { + guard let text = configs[identifier]?.item?.view as? NSTextField, + let constraint = configs[identifier]?.constraint as? NSLayoutConstraint else { return } + text.removeConstraint(constraint) + } + + func applyConstraintFrom(string: String, identifier: NSTouchBarItem.Identifier) { + guard let text = configs[identifier]?.item?.view as? NSTextField else { return } + let fullString = string.components(separatedBy: .decimalDigits).joined(separator: "0") + let textField = NSTextField(labelWithString: fullString) + let con = NSLayoutConstraint(item: text, attribute: .width, relatedBy: .equal, toItem: nil, + attribute: .notAnAttribute, multiplier: 1.1, constant: ceil(textField.frame.size.width)) + text.addConstraint(con) + configs[identifier]?.constraint = con + } + + func getIdentifierFrom(view: NSView) -> NSTouchBarItem.Identifier? { + for (identifier, config) in configs { + if config.item?.view == view { + return identifier + } + } + return nil + } + + func handle(event: EventHelper.Event) { + switch event.name { + case "MPV_EVENT_END_FILE": + position = 0 + duration = 0 + case "time-pos": + let newPosition = max(event.double ?? 0, 0) + if Int((floor(newPosition) - floor(position)) / rate) != 0 { + position = newPosition + } + case "pause": isPaused = event.bool ?? false + case "duration": duration = event.double ?? 0 + case "speed": rate = event.double ?? 1 + default: break + } + } +} diff --git a/osdep/mac/type_helper.swift b/osdep/mac/type_helper.swift new file mode 100644 index 0000000..bd90d0a --- /dev/null +++ b/osdep/mac/type_helper.swift @@ -0,0 +1,69 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +class TypeHelper { + // (__bridge void*) + class func bridge<T: AnyObject>(obj: T) -> UnsafeMutableRawPointer { + return UnsafeMutableRawPointer(Unmanaged.passUnretained(obj).toOpaque()) + } + + // (__bridge T*) + class func bridge<T: AnyObject>(ptr: UnsafeRawPointer) -> T { + return Unmanaged<T>.fromOpaque(ptr).takeUnretainedValue() + } + + class func withUnsafeMutableRawPointers(_ arguments: [Any], + pointers: [UnsafeMutableRawPointer?] = [], + closure: (_ pointers: [UnsafeMutableRawPointer?]) -> Void) { + if arguments.count > 0 { + let args = Array(arguments.dropFirst(1)) + var newPtrs = pointers + var firstArg = arguments.first + withUnsafeMutableBytes(of: &firstArg) { (ptr: UnsafeMutableRawBufferPointer) in + newPtrs.append(ptr.baseAddress) + withUnsafeMutableRawPointers(args, pointers: newPtrs, closure: closure) + } + + return + } + + closure(pointers) + } + + class func toPointer<T>(_ value: inout T) -> UnsafeMutableRawPointer? { + return withUnsafeMutableBytes(of: &value) { (ptr: UnsafeMutableRawBufferPointer) in + ptr.baseAddress + } + } + + // *(char **) MPV_FORMAT_STRING + class func toString(_ obj: UnsafeMutableRawPointer?) -> String? { + guard let str = obj else { return nil } + let cstr = UnsafeMutablePointer<UnsafeMutablePointer<Int8>>(OpaquePointer(str)) + return String(cString: cstr[0]) + } + + // MPV_FORMAT_FLAG + class func toBool(_ obj: UnsafeMutableRawPointer) -> Bool? { + return UnsafePointer<Bool>(OpaquePointer(obj))?.pointee + } + + // MPV_FORMAT_DOUBLE + class func toDouble(_ obj: UnsafeMutableRawPointer) -> Double? { + return UnsafePointer<Double>(OpaquePointer(obj))?.pointee + } +} diff --git a/osdep/macos/log_helper.swift b/osdep/macos/log_helper.swift deleted file mode 100644 index 9464075..0000000 --- a/osdep/macos/log_helper.swift +++ /dev/null @@ -1,47 +0,0 @@ -/* - * This file is part of mpv. - * - * mpv is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * mpv is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with mpv. If not, see <http://www.gnu.org/licenses/>. - */ - -import Cocoa - -class LogHelper: NSObject { - var log: OpaquePointer? - - init(_ log: OpaquePointer?) { - self.log = log - } - - func sendVerbose(_ msg: String) { - send(message: msg, type: MSGL_V) - } - - func sendInfo(_ msg: String) { - send(message: msg, type: MSGL_INFO) - } - - func sendWarning(_ msg: String) { - send(message: msg, type: MSGL_WARN) - } - - func sendError(_ msg: String) { - send(message: msg, type: MSGL_ERR) - } - - func send(message msg: String, type t: Int) { - let args: [CVarArg] = [ (msg as NSString).utf8String ?? "NO MESSAGE"] - mp_msg_va(log, Int32(t), "%s\n", getVaList(args)) - } -} diff --git a/osdep/macos/mpv_helper.swift b/osdep/macos/mpv_helper.swift deleted file mode 100644 index 3b2a716..0000000 --- a/osdep/macos/mpv_helper.swift +++ /dev/null @@ -1,156 +0,0 @@ -/* - * This file is part of mpv. - * - * mpv is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * mpv is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with mpv. If not, see <http://www.gnu.org/licenses/>. - */ - -import Cocoa - -typealias swift_wakeup_cb_fn = (@convention(c) (UnsafeMutableRawPointer?) -> Void)? - -class MPVHelper { - var log: LogHelper - var vo: UnsafeMutablePointer<vo> - var optsCachePtr: UnsafeMutablePointer<m_config_cache> - var optsPtr: UnsafeMutablePointer<mp_vo_opts> - var macOptsCachePtr: UnsafeMutablePointer<m_config_cache> - var macOptsPtr: UnsafeMutablePointer<macos_opts> - - // these computed properties return a local copy of the struct accessed: - // - don't use if you rely on the pointers - // - only for reading - var vout: vo { get { return vo.pointee } } - var optsCache: m_config_cache { get { return optsCachePtr.pointee } } - var opts: mp_vo_opts { get { return optsPtr.pointee } } - var macOptsCache: m_config_cache { get { return macOptsCachePtr.pointee } } - var macOpts: macos_opts { get { return macOptsPtr.pointee } } - - var input: OpaquePointer { get { return vout.input_ctx } } - - init(_ vo: UnsafeMutablePointer<vo>, _ log: LogHelper) { - self.vo = vo - self.log = log - - guard let app = NSApp as? Application, - let cache = m_config_cache_alloc(vo, vo.pointee.global, app.getVoSubConf()) else - { - log.sendError("NSApp couldn't be retrieved") - exit(1) - } - - optsCachePtr = cache - optsPtr = UnsafeMutablePointer<mp_vo_opts>(OpaquePointer(cache.pointee.opts)) - - guard let macCache = m_config_cache_alloc(vo, - vo.pointee.global, - app.getMacOSConf()) else - { - // will never be hit, mp_get_config_group asserts for invalid groups - exit(1) - } - macOptsCachePtr = macCache - macOptsPtr = UnsafeMutablePointer<macos_opts>(OpaquePointer(macCache.pointee.opts)) - } - - func canBeDraggedAt(_ pos: NSPoint) -> Bool { - let canDrag = !mp_input_test_dragging(input, Int32(pos.x), Int32(pos.y)) - return canDrag - } - - func mouseEnabled() -> Bool { - return mp_input_mouse_enabled(input) - } - - func setMousePosition(_ pos: NSPoint) { - mp_input_set_mouse_pos(input, Int32(pos.x), Int32(pos.y)) - } - - func putAxis(_ mpkey: Int32, delta: Double) { - mp_input_put_wheel(input, mpkey, delta) - } - - func nextChangedOption(property: inout UnsafeMutableRawPointer?) -> Bool { - return m_config_cache_get_next_changed(optsCachePtr, &property) - } - - func setOption(fullscreen: Bool) { - optsPtr.pointee.fullscreen = fullscreen - _ = withUnsafeMutableBytes(of: &optsPtr.pointee.fullscreen) { (ptr: UnsafeMutableRawBufferPointer) in - m_config_cache_write_opt(optsCachePtr, ptr.baseAddress) - } - } - - func setOption(minimized: Bool) { - optsPtr.pointee.window_minimized = minimized - _ = withUnsafeMutableBytes(of: &optsPtr.pointee.window_minimized) { (ptr: UnsafeMutableRawBufferPointer) in - m_config_cache_write_opt(optsCachePtr, ptr.baseAddress) - } - } - - func setOption(maximized: Bool) { - optsPtr.pointee.window_maximized = maximized - _ = withUnsafeMutableBytes(of: &optsPtr.pointee.window_maximized) { (ptr: UnsafeMutableRawBufferPointer) in - m_config_cache_write_opt(optsCachePtr, ptr.baseAddress) - } - } - - func setMacOptionCallback(_ callback: swift_wakeup_cb_fn, context object: AnyObject) { - m_config_cache_set_wakeup_cb(macOptsCachePtr, callback, MPVHelper.bridge(obj: object)) - } - - func nextChangedMacOption(property: inout UnsafeMutableRawPointer?) -> Bool { - return m_config_cache_get_next_changed(macOptsCachePtr, &property) - } - - func command(_ cmd: String) { - let cCmd = UnsafePointer<Int8>(strdup(cmd)) - let mpvCmd = mp_input_parse_cmd(input, bstr0(cCmd), "") - mp_input_queue_cmd(input, mpvCmd) - free(UnsafeMutablePointer(mutating: cCmd)) - } - - // (__bridge void*) - class func bridge<T: AnyObject>(obj: T) -> UnsafeMutableRawPointer { - return UnsafeMutableRawPointer(Unmanaged.passUnretained(obj).toOpaque()) - } - - // (__bridge T*) - class func bridge<T: AnyObject>(ptr: UnsafeRawPointer) -> T { - return Unmanaged<T>.fromOpaque(ptr).takeUnretainedValue() - } - - class func withUnsafeMutableRawPointers(_ arguments: [Any], - pointers: [UnsafeMutableRawPointer?] = [], - closure: (_ pointers: [UnsafeMutableRawPointer?]) -> Void) { - if arguments.count > 0 { - let args = Array(arguments.dropFirst(1)) - var newPtrs = pointers - var firstArg = arguments.first - withUnsafeMutableBytes(of: &firstArg) { (ptr: UnsafeMutableRawBufferPointer) in - newPtrs.append(ptr.baseAddress) - withUnsafeMutableRawPointers(args, pointers: newPtrs, closure: closure) - } - - return - } - - closure(pointers) - } - - class func getPointer<T>(_ value: inout T) -> UnsafeMutableRawPointer? { - return withUnsafeMutableBytes(of: &value) { (ptr: UnsafeMutableRawBufferPointer) in - ptr.baseAddress - } - } -} diff --git a/osdep/macos/remote_command_center.swift b/osdep/macos/remote_command_center.swift deleted file mode 100644 index 6fb2229..0000000 --- a/osdep/macos/remote_command_center.swift +++ /dev/null @@ -1,191 +0,0 @@ -/* - * This file is part of mpv. - * - * mpv is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * mpv is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with mpv. If not, see <http://www.gnu.org/licenses/>. - */ - -import MediaPlayer - -class RemoteCommandCenter: NSObject { - enum KeyType { - case normal - case repeatable - } - - var config: [MPRemoteCommand:[String:Any]] = [ - MPRemoteCommandCenter.shared().pauseCommand: [ - "mpKey": MP_KEY_PAUSEONLY, - "keyType": KeyType.normal - ], - MPRemoteCommandCenter.shared().playCommand: [ - "mpKey": MP_KEY_PLAYONLY, - "keyType": KeyType.normal - ], - MPRemoteCommandCenter.shared().stopCommand: [ - "mpKey": MP_KEY_STOP, - "keyType": KeyType.normal - ], - MPRemoteCommandCenter.shared().nextTrackCommand: [ - "mpKey": MP_KEY_NEXT, - "keyType": KeyType.normal - ], - MPRemoteCommandCenter.shared().previousTrackCommand: [ - "mpKey": MP_KEY_PREV, - "keyType": KeyType.normal - ], - MPRemoteCommandCenter.shared().togglePlayPauseCommand: [ - "mpKey": MP_KEY_PLAY, - "keyType": KeyType.normal - ], - MPRemoteCommandCenter.shared().seekForwardCommand: [ - "mpKey": MP_KEY_FORWARD, - "keyType": KeyType.repeatable, - "state": MP_KEY_STATE_UP - ], - MPRemoteCommandCenter.shared().seekBackwardCommand: [ - "mpKey": MP_KEY_REWIND, - "keyType": KeyType.repeatable, - "state": MP_KEY_STATE_UP - ], - ] - - var nowPlayingInfo: [String: Any] = [ - MPNowPlayingInfoPropertyMediaType: NSNumber(value: MPNowPlayingInfoMediaType.video.rawValue), - MPNowPlayingInfoPropertyDefaultPlaybackRate: NSNumber(value: 1), - MPNowPlayingInfoPropertyPlaybackProgress: NSNumber(value: 0.0), - MPMediaItemPropertyPlaybackDuration: NSNumber(value: 0), - MPMediaItemPropertyTitle: "mpv", - MPMediaItemPropertyAlbumTitle: "mpv", - MPMediaItemPropertyArtist: "mpv", - ] - - let disabledCommands: [MPRemoteCommand] = [ - MPRemoteCommandCenter.shared().changePlaybackRateCommand, - MPRemoteCommandCenter.shared().changeRepeatModeCommand, - MPRemoteCommandCenter.shared().changeShuffleModeCommand, - MPRemoteCommandCenter.shared().skipForwardCommand, - MPRemoteCommandCenter.shared().skipBackwardCommand, - MPRemoteCommandCenter.shared().changePlaybackPositionCommand, - MPRemoteCommandCenter.shared().enableLanguageOptionCommand, - MPRemoteCommandCenter.shared().disableLanguageOptionCommand, - MPRemoteCommandCenter.shared().ratingCommand, - MPRemoteCommandCenter.shared().likeCommand, - MPRemoteCommandCenter.shared().dislikeCommand, - MPRemoteCommandCenter.shared().bookmarkCommand, - ] - - var mpInfoCenter: MPNowPlayingInfoCenter { get { return MPNowPlayingInfoCenter.default() } } - var isPaused: Bool = false { didSet { updatePlaybackState() } } - - @objc override init() { - super.init() - - for cmd in disabledCommands { - cmd.isEnabled = false - } - } - - @objc func start() { - for (cmd, _) in config { - cmd.isEnabled = true - cmd.addTarget { [unowned self] event in - return self.cmdHandler(event) - } - } - - if let app = NSApp as? Application, let icon = app.getMPVIcon() { - let albumArt = MPMediaItemArtwork(boundsSize: icon.size) { _ in - return icon - } - nowPlayingInfo[MPMediaItemPropertyArtwork] = albumArt - } - - mpInfoCenter.nowPlayingInfo = nowPlayingInfo - mpInfoCenter.playbackState = .playing - - NotificationCenter.default.addObserver( - self, - selector: #selector(self.makeCurrent), - name: NSApplication.willBecomeActiveNotification, - object: nil - ) - } - - @objc func stop() { - for (cmd, _) in config { - cmd.isEnabled = false - cmd.removeTarget(nil) - } - - mpInfoCenter.nowPlayingInfo = nil - mpInfoCenter.playbackState = .unknown - } - - @objc func makeCurrent(notification: NSNotification) { - mpInfoCenter.playbackState = .paused - mpInfoCenter.playbackState = .playing - updatePlaybackState() - } - - func updatePlaybackState() { - mpInfoCenter.playbackState = isPaused ? .paused : .playing - } - - func cmdHandler(_ event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus { - guard let cmdConfig = config[event.command], - let mpKey = cmdConfig["mpKey"] as? Int32, - let keyType = cmdConfig["keyType"] as? KeyType else - { - return .commandFailed - } - - var state = cmdConfig["state"] as? UInt32 ?? 0 - - if let currentState = cmdConfig["state"] as? UInt32, keyType == .repeatable { - state = MP_KEY_STATE_DOWN - config[event.command]?["state"] = MP_KEY_STATE_DOWN - if currentState == MP_KEY_STATE_DOWN { - state = MP_KEY_STATE_UP - config[event.command]?["state"] = MP_KEY_STATE_UP - } - } - - EventsResponder.sharedInstance().handleMPKey(mpKey, withMask: Int32(state)) - - return .success - } - - @objc func processEvent(_ event: UnsafeMutablePointer<mpv_event>) { - switch event.pointee.event_id { - case MPV_EVENT_PROPERTY_CHANGE: - handlePropertyChange(event) - default: - break - } - } - - func handlePropertyChange(_ event: UnsafeMutablePointer<mpv_event>) { - let pData = OpaquePointer(event.pointee.data) - guard let property = UnsafePointer<mpv_event_property>(pData)?.pointee else { - return - } - - switch String(cString: property.name) { - case "pause" where property.format == MPV_FORMAT_FLAG: - isPaused = LibmpvHelper.mpvFlagToBool(property.data) ?? false - default: - break - } - } -} diff --git a/osdep/macos/swift_extensions.swift b/osdep/macos/swift_extensions.swift deleted file mode 100644 index 127c568..0000000 --- a/osdep/macos/swift_extensions.swift +++ /dev/null @@ -1,58 +0,0 @@ -/* - * This file is part of mpv. - * - * mpv is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * mpv is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with mpv. If not, see <http://www.gnu.org/licenses/>. - */ - -import Cocoa - -extension NSDeviceDescriptionKey { - static let screenNumber = NSDeviceDescriptionKey("NSScreenNumber") -} - -extension NSScreen { - - public var displayID: CGDirectDisplayID { - get { - return deviceDescription[.screenNumber] as? CGDirectDisplayID ?? 0 - } - } -} - -extension NSColor { - - convenience init(hex: String) { - let int = Int(hex.dropFirst(), radix: 16) ?? 0 - let alpha = CGFloat((int >> 24) & 0x000000FF)/255 - let red = CGFloat((int >> 16) & 0x000000FF)/255 - let green = CGFloat((int >> 8) & 0x000000FF)/255 - let blue = CGFloat((int) & 0x000000FF)/255 - - self.init(calibratedRed: red, green: green, blue: blue, alpha: alpha) - } -} - -extension Bool { - - init(_ int32: Int32) { - self.init(int32 != 0) - } -} - -extension Int32 { - - init(_ bool: Bool) { - self.init(bool ? 1 : 0) - } -} diff --git a/osdep/macosx_application.m b/osdep/macosx_application.m deleted file mode 100644 index 73503ad..0000000 --- a/osdep/macosx_application.m +++ /dev/null @@ -1,375 +0,0 @@ -/* - * This file is part of mpv. - * - * mpv is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * mpv is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with mpv. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <stdio.h> -#include "config.h" -#include "mpv_talloc.h" - -#include "common/msg.h" -#include "input/input.h" -#include "player/client.h" -#include "options/m_config.h" -#include "options/options.h" - -#import "osdep/macosx_application_objc.h" -#import "osdep/macosx_events_objc.h" -#include "osdep/threads.h" -#include "osdep/main-fn.h" - -#if HAVE_MACOS_TOUCHBAR -#import "osdep/macosx_touchbar.h" -#endif -#if HAVE_MACOS_COCOA_CB -#include "osdep/macOS_swift.h" -#endif - -#define MPV_PROTOCOL @"mpv://" - -#define OPT_BASE_STRUCT struct macos_opts -const struct m_sub_options macos_conf = { - .opts = (const struct m_option[]) { - {"macos-title-bar-appearance", OPT_CHOICE(macos_title_bar_appearance, - {"auto", 0}, {"aqua", 1}, {"darkAqua", 2}, - {"vibrantLight", 3}, {"vibrantDark", 4}, - {"aquaHighContrast", 5}, {"darkAquaHighContrast", 6}, - {"vibrantLightHighContrast", 7}, - {"vibrantDarkHighContrast", 8})}, - {"macos-title-bar-material", OPT_CHOICE(macos_title_bar_material, - {"titlebar", 0}, {"selection", 1}, {"menu", 2}, - {"popover", 3}, {"sidebar", 4}, {"headerView", 5}, - {"sheet", 6}, {"windowBackground", 7}, {"hudWindow", 8}, - {"fullScreen", 9}, {"toolTip", 10}, {"contentBackground", 11}, - {"underWindowBackground", 12}, {"underPageBackground", 13}, - {"dark", 14}, {"light", 15}, {"mediumLight", 16}, - {"ultraDark", 17})}, - {"macos-title-bar-color", OPT_COLOR(macos_title_bar_color)}, - {"macos-fs-animation-duration", - OPT_CHOICE(macos_fs_animation_duration, {"default", -1}), - M_RANGE(0, 1000)}, - {"macos-force-dedicated-gpu", OPT_BOOL(macos_force_dedicated_gpu)}, - {"macos-app-activation-policy", OPT_CHOICE(macos_app_activation_policy, - {"regular", 0}, {"accessory", 1}, {"prohibited", 2})}, - {"macos-geometry-calculation", OPT_CHOICE(macos_geometry_calculation, - {"visible", FRAME_VISIBLE}, {"whole", FRAME_WHOLE})}, - {"macos-render-timer", OPT_CHOICE(macos_render_timer, - {"callback", RENDER_TIMER_CALLBACK}, {"precise", RENDER_TIMER_PRECISE}, - {"system", RENDER_TIMER_SYSTEM})}, - {"cocoa-cb-sw-renderer", OPT_CHOICE(cocoa_cb_sw_renderer, - {"auto", -1}, {"no", 0}, {"yes", 1})}, - {"cocoa-cb-10bit-context", OPT_BOOL(cocoa_cb_10bit_context)}, - {0} - }, - .size = sizeof(struct macos_opts), - .defaults = &(const struct macos_opts){ - .macos_title_bar_color = {0, 0, 0, 0}, - .macos_fs_animation_duration = -1, - .cocoa_cb_sw_renderer = -1, - .cocoa_cb_10bit_context = true - }, -}; - -// Whether the NSApplication singleton was created. If this is false, we are -// running in libmpv mode, and cocoa_main() was never called. -static bool application_instantiated; - -static mp_thread playback_thread_id; - -@interface Application () -{ - EventsResponder *_eventsResponder; -} - -@end - -static Application *mpv_shared_app(void) -{ - return (Application *)[Application sharedApplication]; -} - -static void terminate_cocoa_application(void) -{ - dispatch_async(dispatch_get_main_queue(), ^{ - [NSApp hide:NSApp]; - [NSApp terminate:NSApp]; - }); -} - -@implementation Application -@synthesize menuBar = _menu_bar; -@synthesize openCount = _open_count; -@synthesize cocoaCB = _cocoa_cb; - -- (void)sendEvent:(NSEvent *)event -{ - if ([self modalWindow] || ![_eventsResponder processKeyEvent:event]) - [super sendEvent:event]; - [_eventsResponder wakeup]; -} - -- (id)init -{ - if (self = [super init]) { - _eventsResponder = [EventsResponder sharedInstance]; - - NSAppleEventManager *em = [NSAppleEventManager sharedAppleEventManager]; - [em setEventHandler:self - andSelector:@selector(getUrl:withReplyEvent:) - forEventClass:kInternetEventClass - andEventID:kAEGetURL]; - } - - return self; -} - -- (void)dealloc -{ - NSAppleEventManager *em = [NSAppleEventManager sharedAppleEventManager]; - [em removeEventHandlerForEventClass:kInternetEventClass - andEventID:kAEGetURL]; - [em removeEventHandlerForEventClass:kCoreEventClass - andEventID:kAEQuitApplication]; - [super dealloc]; -} - -static const char macosx_icon[] = -#include "TOOLS/osxbundle/icon.icns.inc" -; - -- (NSImage *)getMPVIcon -{ - // The C string contains a trailing null, so we strip it away - NSData *icon_data = [NSData dataWithBytesNoCopy:(void *)macosx_icon - length:sizeof(macosx_icon) - 1 - freeWhenDone:NO]; - return [[NSImage alloc] initWithData:icon_data]; -} - -#if HAVE_MACOS_TOUCHBAR -- (NSTouchBar *)makeTouchBar -{ - TouchBar *tBar = [[TouchBar alloc] init]; - [tBar setApp:self]; - tBar.delegate = tBar; - tBar.customizationIdentifier = customID; - tBar.defaultItemIdentifiers = @[play, previousItem, nextItem, seekBar]; - tBar.customizationAllowedItemIdentifiers = @[play, seekBar, previousItem, - nextItem, previousChapter, nextChapter, cycleAudio, cycleSubtitle, - currentPosition, timeLeft]; - return tBar; -} -#endif - -- (void)processEvent:(struct mpv_event *)event -{ -#if HAVE_MACOS_TOUCHBAR - [(TouchBar *)self.touchBar processEvent:event]; -#endif - if (_cocoa_cb) { - [_cocoa_cb processEvent:event]; - } -} - -- (void)setMpvHandle:(struct mpv_handle *)ctx -{ -#if HAVE_MACOS_COCOA_CB - [NSApp setCocoaCB:[[CocoaCB alloc] init:ctx]]; -#endif -} - -- (const struct m_sub_options *)getMacOSConf -{ - return &macos_conf; -} - -- (const struct m_sub_options *)getVoSubConf -{ - return &vo_sub_opts; -} - -- (void)queueCommand:(char *)cmd -{ - [_eventsResponder queueCommand:cmd]; -} - -- (void)stopMPV:(char *)cmd -{ - if (![_eventsResponder queueCommand:cmd]) - terminate_cocoa_application(); -} - -- (void)applicationWillFinishLaunching:(NSNotification *)notification -{ - NSAppleEventManager *em = [NSAppleEventManager sharedAppleEventManager]; - [em setEventHandler:self - andSelector:@selector(handleQuitEvent:withReplyEvent:) - forEventClass:kCoreEventClass - andEventID:kAEQuitApplication]; -} - -- (void)handleQuitEvent:(NSAppleEventDescriptor *)event - withReplyEvent:(NSAppleEventDescriptor *)replyEvent -{ - [self stopMPV:"quit"]; -} - -- (void)getUrl:(NSAppleEventDescriptor *)event - withReplyEvent:(NSAppleEventDescriptor *)replyEvent -{ - NSString *url = - [[event paramDescriptorForKeyword:keyDirectObject] stringValue]; - - url = [url stringByReplacingOccurrencesOfString:MPV_PROTOCOL - withString:@"" - options:NSAnchoredSearch - range:NSMakeRange(0, [MPV_PROTOCOL length])]; - - url = [url stringByRemovingPercentEncoding]; - [_eventsResponder handleFilesArray:@[url]]; -} - -- (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames -{ - if (mpv_shared_app().openCount > 0) { - mpv_shared_app().openCount--; - return; - } - [self openFiles:filenames]; -} - -- (void)openFiles:(NSArray *)filenames -{ - SEL cmpsel = @selector(localizedStandardCompare:); - NSArray *files = [filenames sortedArrayUsingSelector:cmpsel]; - [_eventsResponder handleFilesArray:files]; -} -@end - -struct playback_thread_ctx { - int *argc; - char ***argv; -}; - -static void cocoa_run_runloop(void) -{ - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - [NSApp run]; - [pool drain]; -} - -static MP_THREAD_VOID playback_thread(void *ctx_obj) -{ - mp_thread_set_name("core/playback"); - @autoreleasepool { - struct playback_thread_ctx *ctx = (struct playback_thread_ctx*) ctx_obj; - int r = mpv_main(*ctx->argc, *ctx->argv); - terminate_cocoa_application(); - // normally never reached - unless the cocoa mainloop hasn't started yet - exit(r); - } -} - -void cocoa_register_menu_item_action(MPMenuKey key, void* action) -{ - if (application_instantiated) - [[NSApp menuBar] registerSelector:(SEL)action forKey:key]; -} - -static void init_cocoa_application(bool regular) -{ - NSApp = mpv_shared_app(); - [NSApp setDelegate:NSApp]; - [NSApp setMenuBar:[[MenuBar alloc] init]]; - - // Will be set to Regular from cocoa_common during UI creation so that we - // don't create an icon when playing audio only files. - [NSApp setActivationPolicy: regular ? - NSApplicationActivationPolicyRegular : - NSApplicationActivationPolicyAccessory]; - - atexit_b(^{ - // Because activation policy has just been set to behave like a real - // application, that policy must be reset on exit to prevent, among - // other things, the menubar created here from remaining on screen. - dispatch_async(dispatch_get_main_queue(), ^{ - [NSApp setActivationPolicy:NSApplicationActivationPolicyProhibited]; - }); - }); -} - -static bool bundle_started_from_finder() -{ - NSString* bundle = [[[NSProcessInfo processInfo] environment] objectForKey:@"MPVBUNDLE"]; - return [bundle isEqual:@"true"]; -} - -static bool is_psn_argument(char *arg_to_check) -{ - NSString *arg = [NSString stringWithUTF8String:arg_to_check]; - return [arg hasPrefix:@"-psn_"]; -} - -static void setup_bundle(int *argc, char *argv[]) -{ - if (*argc > 1 && is_psn_argument(argv[1])) { - *argc = 1; - argv[1] = NULL; - } - - NSDictionary *env = [[NSProcessInfo processInfo] environment]; - NSString *path_bundle = [env objectForKey:@"PATH"]; - NSString *path_new = [NSString stringWithFormat:@"%@:%@:%@:%@:%@", - path_bundle, - @"/usr/local/bin", - @"/usr/local/sbin", - @"/opt/local/bin", - @"/opt/local/sbin"]; - setenv("PATH", [path_new UTF8String], 1); -} - -int cocoa_main(int argc, char *argv[]) -{ - @autoreleasepool { - application_instantiated = true; - [[EventsResponder sharedInstance] setIsApplication:YES]; - - struct playback_thread_ctx ctx = {0}; - ctx.argc = &argc; - ctx.argv = &argv; - - if (bundle_started_from_finder()) { - setup_bundle(&argc, argv); - init_cocoa_application(true); - } else { - for (int i = 1; i < argc; i++) - if (argv[i][0] != '-') - mpv_shared_app().openCount++; - init_cocoa_application(false); - } - - mp_thread_create(&playback_thread_id, playback_thread, &ctx); - [[EventsResponder sharedInstance] waitForInputContext]; - cocoa_run_runloop(); - - // This should never be reached: cocoa_run_runloop blocks until the - // process is quit - fprintf(stderr, "There was either a problem " - "initializing Cocoa or the Runloop was stopped unexpectedly. " - "Please report this issues to a developer.\n"); - mp_thread_join(playback_thread_id); - return 1; - } -} diff --git a/osdep/macosx_application_objc.h b/osdep/macosx_application_objc.h deleted file mode 100644 index 11959a8..0000000 --- a/osdep/macosx_application_objc.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * This file is part of mpv. - * - * mpv is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * mpv is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with mpv. If not, see <http://www.gnu.org/licenses/>. - */ - -#import <Cocoa/Cocoa.h> -#include "osdep/macosx_application.h" -#import "osdep/macosx_menubar_objc.h" - -@class CocoaCB; -struct mpv_event; -struct mpv_handle; - -@interface Application : NSApplication - -- (NSImage *)getMPVIcon; -- (void)processEvent:(struct mpv_event *)event; -- (void)queueCommand:(char *)cmd; -- (void)stopMPV:(char *)cmd; -- (void)openFiles:(NSArray *)filenames; -- (void)setMpvHandle:(struct mpv_handle *)ctx; -- (const struct m_sub_options *)getMacOSConf; -- (const struct m_sub_options *)getVoSubConf; - -@property(nonatomic, retain) MenuBar *menuBar; -@property(nonatomic, assign) size_t openCount; -@property(nonatomic, retain) CocoaCB *cocoaCB; -@end diff --git a/osdep/macosx_events.h b/osdep/macosx_events.h deleted file mode 100644 index 9188c8b..0000000 --- a/osdep/macosx_events.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Cocoa Application Event Handling - * - * This file is part of mpv. - * - * mpv is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * mpv is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with mpv. If not, see <http://www.gnu.org/licenses/>. - */ - -#ifndef MACOSX_EVENTS_H -#define MACOSX_EVENTS_H -#include "input/keycodes.h" - -struct input_ctx; -struct mpv_handle; - -void cocoa_put_key(int keycode); -void cocoa_put_key_with_modifiers(int keycode, int modifiers); - -void cocoa_init_media_keys(void); -void cocoa_uninit_media_keys(void); - -void cocoa_set_input_context(struct input_ctx *input_context); -void cocoa_set_mpv_handle(struct mpv_handle *ctx); - -#endif diff --git a/osdep/macosx_events.m b/osdep/macosx_events.m deleted file mode 100644 index 627077a..0000000 --- a/osdep/macosx_events.m +++ /dev/null @@ -1,408 +0,0 @@ -/* - * Cocoa Application Event Handling - * - * This file is part of mpv. - * - * mpv is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * mpv is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with mpv. If not, see <http://www.gnu.org/licenses/>. - */ - -// Carbon header is included but Carbon is NOT linked to mpv's binary. This -// file only needs this include to use the keycode definitions in keymap. -#import <Carbon/Carbon.h> - -// Media keys definitions -#import <IOKit/hidsystem/ev_keymap.h> -#import <Cocoa/Cocoa.h> - -#include "mpv_talloc.h" -#include "input/event.h" -#include "input/input.h" -#include "player/client.h" -#include "input/keycodes.h" -// doesn't make much sense, but needed to access keymap functionality -#include "video/out/vo.h" - -#import "osdep/macosx_events_objc.h" -#import "osdep/macosx_application_objc.h" - -#include "config.h" - -#if HAVE_MACOS_COCOA_CB -#include "osdep/macOS_swift.h" -#endif - -@interface EventsResponder () -{ - struct input_ctx *_inputContext; - struct mpv_handle *_ctx; - BOOL _is_application; - NSCondition *_input_lock; -} - -- (NSEvent *)handleKey:(NSEvent *)event; -- (BOOL)setMpvHandle:(struct mpv_handle *)ctx; -- (void)readEvents; -- (void)startMediaKeys; -- (void)stopMediaKeys; -- (int)mapKeyModifiers:(int)cocoaModifiers; -- (int)keyModifierMask:(NSEvent *)event; -@end - - -#define NSLeftAlternateKeyMask (0x000020 | NSEventModifierFlagOption) -#define NSRightAlternateKeyMask (0x000040 | NSEventModifierFlagOption) - -static bool LeftAltPressed(int mask) -{ - return (mask & NSLeftAlternateKeyMask) == NSLeftAlternateKeyMask; -} - -static bool RightAltPressed(int mask) -{ - return (mask & NSRightAlternateKeyMask) == NSRightAlternateKeyMask; -} - -static const struct mp_keymap keymap[] = { - // special keys - {kVK_Return, MP_KEY_ENTER}, {kVK_Escape, MP_KEY_ESC}, - {kVK_Delete, MP_KEY_BACKSPACE}, {kVK_Option, MP_KEY_BACKSPACE}, - {kVK_Control, MP_KEY_BACKSPACE}, {kVK_Shift, MP_KEY_BACKSPACE}, - {kVK_Tab, MP_KEY_TAB}, - - // cursor keys - {kVK_UpArrow, MP_KEY_UP}, {kVK_DownArrow, MP_KEY_DOWN}, - {kVK_LeftArrow, MP_KEY_LEFT}, {kVK_RightArrow, MP_KEY_RIGHT}, - - // navigation block - {kVK_Help, MP_KEY_INSERT}, {kVK_ForwardDelete, MP_KEY_DELETE}, - {kVK_Home, MP_KEY_HOME}, {kVK_End, MP_KEY_END}, - {kVK_PageUp, MP_KEY_PAGE_UP}, {kVK_PageDown, MP_KEY_PAGE_DOWN}, - - // F-keys - {kVK_F1, MP_KEY_F + 1}, {kVK_F2, MP_KEY_F + 2}, {kVK_F3, MP_KEY_F + 3}, - {kVK_F4, MP_KEY_F + 4}, {kVK_F5, MP_KEY_F + 5}, {kVK_F6, MP_KEY_F + 6}, - {kVK_F7, MP_KEY_F + 7}, {kVK_F8, MP_KEY_F + 8}, {kVK_F9, MP_KEY_F + 9}, - {kVK_F10, MP_KEY_F + 10}, {kVK_F11, MP_KEY_F + 11}, {kVK_F12, MP_KEY_F + 12}, - {kVK_F13, MP_KEY_F + 13}, {kVK_F14, MP_KEY_F + 14}, {kVK_F15, MP_KEY_F + 15}, - {kVK_F16, MP_KEY_F + 16}, {kVK_F17, MP_KEY_F + 17}, {kVK_F18, MP_KEY_F + 18}, - {kVK_F19, MP_KEY_F + 19}, {kVK_F20, MP_KEY_F + 20}, - - // numpad - {kVK_ANSI_KeypadPlus, '+'}, {kVK_ANSI_KeypadMinus, '-'}, - {kVK_ANSI_KeypadMultiply, '*'}, {kVK_ANSI_KeypadDivide, '/'}, - {kVK_ANSI_KeypadEnter, MP_KEY_KPENTER}, - {kVK_ANSI_KeypadDecimal, MP_KEY_KPDEC}, - {kVK_ANSI_Keypad0, MP_KEY_KP0}, {kVK_ANSI_Keypad1, MP_KEY_KP1}, - {kVK_ANSI_Keypad2, MP_KEY_KP2}, {kVK_ANSI_Keypad3, MP_KEY_KP3}, - {kVK_ANSI_Keypad4, MP_KEY_KP4}, {kVK_ANSI_Keypad5, MP_KEY_KP5}, - {kVK_ANSI_Keypad6, MP_KEY_KP6}, {kVK_ANSI_Keypad7, MP_KEY_KP7}, - {kVK_ANSI_Keypad8, MP_KEY_KP8}, {kVK_ANSI_Keypad9, MP_KEY_KP9}, - - {0, 0} -}; - -static int convert_key(unsigned key, unsigned charcode) -{ - int mpkey = lookup_keymap_table(keymap, key); - if (mpkey) - return mpkey; - return charcode; -} - -void cocoa_init_media_keys(void) -{ - [[EventsResponder sharedInstance] startMediaKeys]; -} - -void cocoa_uninit_media_keys(void) -{ - [[EventsResponder sharedInstance] stopMediaKeys]; -} - -void cocoa_put_key(int keycode) -{ - [[EventsResponder sharedInstance] putKey:keycode]; -} - -void cocoa_put_key_with_modifiers(int keycode, int modifiers) -{ - keycode |= [[EventsResponder sharedInstance] mapKeyModifiers:modifiers]; - cocoa_put_key(keycode); -} - -void cocoa_set_input_context(struct input_ctx *input_context) -{ - [[EventsResponder sharedInstance] setInputContext:input_context]; -} - -static void wakeup(void *context) -{ - [[EventsResponder sharedInstance] readEvents]; -} - -void cocoa_set_mpv_handle(struct mpv_handle *ctx) -{ - if ([[EventsResponder sharedInstance] setMpvHandle:ctx]) { - mpv_observe_property(ctx, 0, "duration", MPV_FORMAT_DOUBLE); - mpv_observe_property(ctx, 0, "time-pos", MPV_FORMAT_DOUBLE); - mpv_observe_property(ctx, 0, "pause", MPV_FORMAT_FLAG); - mpv_set_wakeup_callback(ctx, wakeup, NULL); - } -} - -@implementation EventsResponder - -@synthesize remoteCommandCenter = _remoteCommandCenter; - -+ (EventsResponder *)sharedInstance -{ - static EventsResponder *responder = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - responder = [EventsResponder new]; - }); - return responder; -} - -- (id)init -{ - self = [super init]; - if (self) { - _input_lock = [NSCondition new]; - } - return self; -} - -- (void)waitForInputContext -{ - [_input_lock lock]; - while (!_inputContext) - [_input_lock wait]; - [_input_lock unlock]; -} - -- (void)setInputContext:(struct input_ctx *)ctx -{ - [_input_lock lock]; - _inputContext = ctx; - [_input_lock signal]; - [_input_lock unlock]; -} - -- (void)wakeup -{ - [_input_lock lock]; - if (_inputContext) - mp_input_wakeup(_inputContext); - [_input_lock unlock]; -} - -- (bool)queueCommand:(char *)cmd -{ - bool r = false; - [_input_lock lock]; - if (_inputContext) { - mp_cmd_t *cmdt = mp_input_parse_cmd(_inputContext, bstr0(cmd), ""); - mp_input_queue_cmd(_inputContext, cmdt); - r = true; - } - [_input_lock unlock]; - return r; -} - -- (void)putKey:(int)keycode -{ - [_input_lock lock]; - if (_inputContext) - mp_input_put_key(_inputContext, keycode); - [_input_lock unlock]; -} - -- (BOOL)useAltGr -{ - BOOL r = YES; - [_input_lock lock]; - if (_inputContext) - r = mp_input_use_alt_gr(_inputContext); - [_input_lock unlock]; - return r; -} - -- (void)setIsApplication:(BOOL)isApplication -{ - _is_application = isApplication; -} - -- (BOOL)setMpvHandle:(struct mpv_handle *)ctx -{ - if (_is_application) { - dispatch_sync(dispatch_get_main_queue(), ^{ - _ctx = ctx; - [NSApp setMpvHandle:ctx]; - }); - return YES; - } else { - mpv_destroy(ctx); - return NO; - } -} - -- (void)readEvents -{ - dispatch_async(dispatch_get_main_queue(), ^{ - while (_ctx) { - mpv_event *event = mpv_wait_event(_ctx, 0); - if (event->event_id == MPV_EVENT_NONE) - break; - [self processEvent:event]; - } - }); -} - --(void)processEvent:(struct mpv_event *)event -{ - if(_is_application) { - [NSApp processEvent:event]; - } - - if (_remoteCommandCenter) { - [_remoteCommandCenter processEvent:event]; - } - - switch (event->event_id) { - case MPV_EVENT_SHUTDOWN: { -#if HAVE_MACOS_COCOA_CB - if ([(Application *)NSApp cocoaCB].isShuttingDown) { - _ctx = nil; - return; - } -#endif - mpv_destroy(_ctx); - _ctx = nil; - break; - } - } -} - -- (void)startMediaKeys -{ -#if HAVE_MACOS_MEDIA_PLAYER - if (_remoteCommandCenter == nil) { - _remoteCommandCenter = [[RemoteCommandCenter alloc] init]; - } -#endif - - [_remoteCommandCenter start]; -} - -- (void)stopMediaKeys -{ - [_remoteCommandCenter stop]; -} - -- (int)mapKeyModifiers:(int)cocoaModifiers -{ - int mask = 0; - if (cocoaModifiers & NSEventModifierFlagShift) - mask |= MP_KEY_MODIFIER_SHIFT; - if (cocoaModifiers & NSEventModifierFlagControl) - mask |= MP_KEY_MODIFIER_CTRL; - if (LeftAltPressed(cocoaModifiers) || - (RightAltPressed(cocoaModifiers) && ![self useAltGr])) - mask |= MP_KEY_MODIFIER_ALT; - if (cocoaModifiers & NSEventModifierFlagCommand) - mask |= MP_KEY_MODIFIER_META; - return mask; -} - -- (int)mapTypeModifiers:(NSEventType)type -{ - NSDictionary *map = @{ - @(NSEventTypeKeyDown) : @(MP_KEY_STATE_DOWN), - @(NSEventTypeKeyUp) : @(MP_KEY_STATE_UP), - }; - return [map[@(type)] intValue]; -} - -- (int)keyModifierMask:(NSEvent *)event -{ - return [self mapKeyModifiers:[event modifierFlags]] | - [self mapTypeModifiers:[event type]]; -} - --(BOOL)handleMPKey:(int)key withMask:(int)mask -{ - if (key > 0) { - cocoa_put_key(key | mask); - if (mask & MP_KEY_STATE_UP) - cocoa_put_key(MP_INPUT_RELEASE_ALL); - return YES; - } else { - return NO; - } -} - -- (NSEvent*)handleKey:(NSEvent *)event -{ - if ([event isARepeat]) return nil; - - NSString *chars; - - if ([self useAltGr] && RightAltPressed([event modifierFlags])) { - chars = [event characters]; - } else { - chars = [event charactersIgnoringModifiers]; - } - - struct bstr t = bstr0([chars UTF8String]); - int key = convert_key([event keyCode], bstr_decode_utf8(t, &t)); - - if (key > -1) - [self handleMPKey:key withMask:[self keyModifierMask:event]]; - - return nil; -} - -- (bool)processKeyEvent:(NSEvent *)event -{ - if (event.type == NSEventTypeKeyDown || event.type == NSEventTypeKeyUp){ - if (![[NSApp mainMenu] performKeyEquivalent:event]) - [self handleKey:event]; - return true; - } - return false; -} - -- (void)handleFilesArray:(NSArray *)files -{ - enum mp_dnd_action action = [NSEvent modifierFlags] & - NSEventModifierFlagShift ? DND_APPEND : DND_REPLACE; - - size_t num_files = [files count]; - char **files_utf8 = talloc_array(NULL, char*, num_files); - [files enumerateObjectsUsingBlock:^(NSString *p, NSUInteger i, BOOL *_){ - if ([p hasPrefix:@"file:///.file/id="]) - p = [[NSURL URLWithString:p] path]; - char *filename = (char *)[p UTF8String]; - size_t bytes = [p lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; - files_utf8[i] = talloc_memdup(files_utf8, filename, bytes + 1); - }]; - [_input_lock lock]; - if (_inputContext) - mp_event_drop_files(_inputContext, num_files, files_utf8, action); - [_input_lock unlock]; - talloc_free(files_utf8); -} - -@end diff --git a/osdep/macosx_events_objc.h b/osdep/macosx_events_objc.h deleted file mode 100644 index 9394fe7..0000000 --- a/osdep/macosx_events_objc.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Cocoa Application Event Handling - * - * This file is part of mpv. - * - * mpv is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * mpv is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with mpv. If not, see <http://www.gnu.org/licenses/>. - */ - -#import <Cocoa/Cocoa.h> -#include "osdep/macosx_events.h" - -@class RemoteCommandCenter; -struct input_ctx; - -@interface EventsResponder : NSObject - -+ (EventsResponder *)sharedInstance; -- (void)setInputContext:(struct input_ctx *)ctx; -- (void)setIsApplication:(BOOL)isApplication; - -/// Blocks until inputContext is present. -- (void)waitForInputContext; -- (void)wakeup; -- (void)putKey:(int)keycode; -- (void)handleFilesArray:(NSArray *)files; - -- (bool)queueCommand:(char *)cmd; -- (bool)processKeyEvent:(NSEvent *)event; - -- (BOOL)handleMPKey:(int)key withMask:(int)mask; - -@property(nonatomic, retain) RemoteCommandCenter *remoteCommandCenter; - -@end diff --git a/osdep/macosx_menubar.m b/osdep/macosx_menubar.m deleted file mode 100644 index 5c6cd47..0000000 --- a/osdep/macosx_menubar.m +++ /dev/null @@ -1,853 +0,0 @@ -/* - * This file is part of mpv. - * - * mpv is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * mpv is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with mpv. If not, see <http://www.gnu.org/licenses/>. - */ - -#include "config.h" -#include "common/common.h" - -#import "macosx_menubar_objc.h" -#import "osdep/macosx_application_objc.h" - -@implementation MenuBar -{ - NSArray *menuTree; -} - -- (id)init -{ - if (self = [super init]) { - NSUserDefaults *userDefaults =[NSUserDefaults standardUserDefaults]; - [userDefaults setBool:NO forKey:@"NSFullScreenMenuItemEverywhere"]; - [userDefaults setBool:YES forKey:@"NSDisabledDictationMenuItem"]; - [userDefaults setBool:YES forKey:@"NSDisabledCharacterPaletteMenuItem"]; - [NSWindow setAllowsAutomaticWindowTabbing: NO]; - - menuTree = @[ - @{ - @"name": @"Apple", - @"menu": @[ - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"About mpv", - @"action" : @"about", - @"key" : @"", - @"target" : self - }], - @{ @"name": @"separator" }, - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Preferences…", - @"action" : @"preferences:", - @"key" : @",", - @"target" : self, - @"file" : @"mpv.conf", - @"alertTitle1": @"No Application found to open your config file.", - @"alertText1" : @"Please open the mpv.conf file with " - "your preferred text editor in the now " - "open folder to edit your config.", - @"alertTitle2": @"No config file found.", - @"alertText2" : @"Please create a mpv.conf file with your " - "preferred text editor in the now open folder.", - @"alertTitle3": @"No config path or file found.", - @"alertText3" : @"Please create the following path ~/.config/mpv/ " - "and a mpv.conf file within with your preferred " - "text editor." - }], - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Keyboard Shortcuts Config…", - @"action" : @"preferences:", - @"key" : @"", - @"target" : self, - @"file" : @"input.conf", - @"alertTitle1": @"No Application found to open your config file.", - @"alertText1" : @"Please open the input.conf file with " - "your preferred text editor in the now " - "open folder to edit your config.", - @"alertTitle2": @"No config file found.", - @"alertText2" : @"Please create a input.conf file with your " - "preferred text editor in the now open folder.", - @"alertTitle3": @"No config path or file found.", - @"alertText3" : @"Please create the following path ~/.config/mpv/ " - "and a input.conf file within with your preferred " - "text editor." - }], - @{ @"name": @"separator" }, - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Services", - @"key" : @"", - }], - @{ @"name": @"separator" }, - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Hide mpv", - @"action" : @"hide:", - @"key" : @"h", - @"target" : NSApp - }], - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Hide Others", - @"action" : @"hideOtherApplications:", - @"key" : @"h", - @"modifiers" : [NSNumber numberWithUnsignedInteger: - NSEventModifierFlagCommand | - NSEventModifierFlagOption], - @"target" : NSApp - }], - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Show All", - @"action" : @"unhideAllApplications:", - @"key" : @"", - @"target" : NSApp - }], - @{ @"name": @"separator" }, - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Quit and Remember Position", - @"action" : @"quit:", - @"key" : @"", - @"target" : self, - @"cmd" : @"quit-watch-later" - }], - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Quit mpv", - @"action" : @"quit:", - @"key" : @"q", - @"target" : self, - @"cmd" : @"quit" - }] - ] - }, - @{ - @"name": @"File", - @"menu": @[ - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Open File…", - @"action" : @"openFile", - @"key" : @"o", - @"target" : self - }], - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Open URL…", - @"action" : @"openURL", - @"key" : @"O", - @"target" : self - }], - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Open Playlist…", - @"action" : @"openPlaylist", - @"key" : @"", - @"target" : self - }], - @{ @"name": @"separator" }, - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Close", - @"action" : @"performClose:", - @"key" : @"w" - }], - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Save Screenshot", - @"action" : @"cmd:", - @"key" : @"", - @"target" : self, - @"cmd" : @"async screenshot" - }] - ] - }, - @{ - @"name": @"Edit", - @"menu": @[ - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Undo", - @"action" : @"undo:", - @"key" : @"z" - }], - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Redo", - @"action" : @"redo:", - @"key" : @"Z" - }], - @{ @"name": @"separator" }, - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Cut", - @"action" : @"cut:", - @"key" : @"x" - }], - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Copy", - @"action" : @"copy:", - @"key" : @"c" - }], - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Paste", - @"action" : @"paste:", - @"key" : @"v" - }], - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Select All", - @"action" : @"selectAll:", - @"key" : @"a" - }] - ] - }, - @{ - @"name": @"View", - @"menu": @[ - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Toggle Fullscreen", - @"action" : @"cmd:", - @"key" : @"", - @"target" : self, - @"cmd" : @"cycle fullscreen" - }], - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Toggle Float on Top", - @"action" : @"cmd:", - @"key" : @"", - @"target" : self, - @"cmd" : @"cycle ontop" - }], - [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:@{ - @"name" : @"Customize Touch Bar…", - @"action" : @"toggleTouchBarCustomizationPalette:", - @"key" : @"", - @"target" : NSApp - }] -#endif - ] - }, - @{ - @"name": @"Video", - @"menu": @[ - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Zoom Out", - @"action" : @"cmd:", - @"key" : @"", - @"target" : self, - @"cmd" : @"add panscan -0.1" - }], - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Zoom In", - @"action" : @"cmd:", - @"key" : @"", - @"target" : self, - @"cmd" : @"add panscan 0.1" - }], - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Reset Zoom", - @"action" : @"cmd:", - @"key" : @"", - @"target" : self, - @"cmd" : @"set panscan 0" - }], - @{ @"name": @"separator" }, - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Aspect Ratio 4:3", - @"action" : @"cmd:", - @"key" : @"", - @"target" : self, - @"cmd" : @"set video-aspect-override \"4:3\"" - }], - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Aspect Ratio 16:9", - @"action" : @"cmd:", - @"key" : @"", - @"target" : self, - @"cmd" : @"set video-aspect-override \"16:9\"" - }], - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Aspect Ratio 1.85:1", - @"action" : @"cmd:", - @"key" : @"", - @"target" : self, - @"cmd" : @"set video-aspect-override \"1.85:1\"" - }], - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Aspect Ratio 2.35:1", - @"action" : @"cmd:", - @"key" : @"", - @"target" : self, - @"cmd" : @"set video-aspect-override \"2.35:1\"" - }], - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Reset Aspect Ratio", - @"action" : @"cmd:", - @"key" : @"", - @"target" : self, - @"cmd" : @"set video-aspect-override \"-1\"" - }], - @{ @"name": @"separator" }, - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Rotate Left", - @"action" : @"cmd:", - @"key" : @"", - @"target" : self, - @"cmd" : @"cycle-values video-rotate 0 270 180 90" - }], - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Rotate Right", - @"action" : @"cmd:", - @"key" : @"", - @"target" : self, - @"cmd" : @"cycle-values video-rotate 90 180 270 0" - }], - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Reset Rotation", - @"action" : @"cmd:", - @"key" : @"", - @"target" : self, - @"cmd" : @"set video-rotate 0" - }], - @{ @"name": @"separator" }, - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Half Size", - @"key" : @"0", - @"cmdSpecial" : [NSNumber numberWithInt:MPM_H_SIZE] - }], - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Normal Size", - @"key" : @"1", - @"cmdSpecial" : [NSNumber numberWithInt:MPM_N_SIZE] - }], - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Double Size", - @"key" : @"2", - @"cmdSpecial" : [NSNumber numberWithInt:MPM_D_SIZE] - }] - ] - }, - @{ - @"name": @"Audio", - @"menu": @[ - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Next Audio Track", - @"action" : @"cmd:", - @"key" : @"", - @"target" : self, - @"cmd" : @"cycle audio" - }], - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Previous Audio Track", - @"action" : @"cmd:", - @"key" : @"", - @"target" : self, - @"cmd" : @"cycle audio down" - }], - @{ @"name": @"separator" }, - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Toggle Mute", - @"action" : @"cmd:", - @"key" : @"", - @"target" : self, - @"cmd" : @"cycle mute" - }], - @{ @"name": @"separator" }, - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Play Audio Later", - @"action" : @"cmd:", - @"key" : @"", - @"target" : self, - @"cmd" : @"add audio-delay 0.1" - }], - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Play Audio Earlier", - @"action" : @"cmd:", - @"key" : @"", - @"target" : self, - @"cmd" : @"add audio-delay -0.1" - }], - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Reset Audio Delay", - @"action" : @"cmd:", - @"key" : @"", - @"target" : self, - @"cmd" : @"set audio-delay 0.0 " - }] - ] - }, - @{ - @"name": @"Subtitle", - @"menu": @[ - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Next Subtitle Track", - @"action" : @"cmd:", - @"key" : @"", - @"target" : self, - @"cmd" : @"cycle sub" - }], - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Previous Subtitle Track", - @"action" : @"cmd:", - @"key" : @"", - @"target" : self, - @"cmd" : @"cycle sub down" - }], - @{ @"name": @"separator" }, - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Toggle Force Style", - @"action" : @"cmd:", - @"key" : @"", - @"target" : self, - @"cmd" : @"cycle-values sub-ass-override \"force\" \"no\"" - }], - @{ @"name": @"separator" }, - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Display Subtitles Later", - @"action" : @"cmd:", - @"key" : @"", - @"target" : self, - @"cmd" : @"add sub-delay 0.1" - }], - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Display Subtitles Earlier", - @"action" : @"cmd:", - @"key" : @"", - @"target" : self, - @"cmd" : @"add sub-delay -0.1" - }], - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Reset Subtitle Delay", - @"action" : @"cmd:", - @"key" : @"", - @"target" : self, - @"cmd" : @"set sub-delay 0.0" - }] - ] - }, - @{ - @"name": @"Playback", - @"menu": @[ - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Toggle Pause", - @"action" : @"cmd:", - @"key" : @"", - @"target" : self, - @"cmd" : @"cycle pause" - }], - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Increase Speed", - @"action" : @"cmd:", - @"key" : @"", - @"target" : self, - @"cmd" : @"add speed 0.1" - }], - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Decrease Speed", - @"action" : @"cmd:", - @"key" : @"", - @"target" : self, - @"cmd" : @"add speed -0.1" - }], - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Reset Speed", - @"action" : @"cmd:", - @"key" : @"", - @"target" : self, - @"cmd" : @"set speed 1.0" - }], - @{ @"name": @"separator" }, - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Show Playlist", - @"action" : @"cmd:", - @"key" : @"", - @"target" : self, - @"cmd" : @"script-message osc-playlist" - }], - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Show Chapters", - @"action" : @"cmd:", - @"key" : @"", - @"target" : self, - @"cmd" : @"script-message osc-chapterlist" - }], - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Show Tracks", - @"action" : @"cmd:", - @"key" : @"", - @"target" : self, - @"cmd" : @"script-message osc-tracklist" - }], - @{ @"name": @"separator" }, - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Next File", - @"action" : @"cmd:", - @"key" : @"", - @"target" : self, - @"cmd" : @"playlist-next" - }], - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Previous File", - @"action" : @"cmd:", - @"key" : @"", - @"target" : self, - @"cmd" : @"playlist-prev" - }], - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Toggle Loop File", - @"action" : @"cmd:", - @"key" : @"", - @"target" : self, - @"cmd" : @"cycle-values loop-file \"inf\" \"no\"" - }], - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Toggle Loop Playlist", - @"action" : @"cmd:", - @"key" : @"", - @"target" : self, - @"cmd" : @"cycle-values loop-playlist \"inf\" \"no\"" - }], - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Shuffle", - @"action" : @"cmd:", - @"key" : @"", - @"target" : self, - @"cmd" : @"playlist-shuffle" - }], - @{ @"name": @"separator" }, - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Next Chapter", - @"action" : @"cmd:", - @"key" : @"", - @"target" : self, - @"cmd" : @"add chapter 1" - }], - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Previous Chapter", - @"action" : @"cmd:", - @"key" : @"", - @"target" : self, - @"cmd" : @"add chapter -1" - }], - @{ @"name": @"separator" }, - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Step Forward", - @"action" : @"cmd:", - @"key" : @"", - @"target" : self, - @"cmd" : @"frame-step" - }], - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Step Backward", - @"action" : @"cmd:", - @"key" : @"", - @"target" : self, - @"cmd" : @"frame-back-step" - }] - ] - }, - @{ - @"name": @"Window", - @"menu": @[ - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Minimize", - @"key" : @"m", - @"cmdSpecial" : [NSNumber numberWithInt:MPM_MINIMIZE] - }], - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Zoom", - @"key" : @"z", - @"cmdSpecial" : [NSNumber numberWithInt:MPM_ZOOM] - }] - ] - }, - @{ - @"name": @"Help", - @"menu": @[ - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"mpv Website…", - @"action" : @"url:", - @"key" : @"", - @"target" : self, - @"url" : @"https://mpv.io" - }], - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"mpv on github…", - @"action" : @"url:", - @"key" : @"", - @"target" : self, - @"url" : @"https://github.com/mpv-player/mpv" - }], - @{ @"name": @"separator" }, - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Online Manual…", - @"action" : @"url:", - @"key" : @"", - @"target" : self, - @"url" : @"https://mpv.io/manual/master/" - }], - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Online Wiki…", - @"action" : @"url:", - @"key" : @"", - @"target" : self, - @"url" : @"https://github.com/mpv-player/mpv/wiki" - }], - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Release Notes…", - @"action" : @"url:", - @"key" : @"", - @"target" : self, - @"url" : @"https://github.com/mpv-player/mpv/blob/master/RELEASE_NOTES" - }], - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Keyboard Shortcuts…", - @"action" : @"url:", - @"key" : @"", - @"target" : self, - @"url" : @"https://github.com/mpv-player/mpv/blob/master/etc/input.conf" - }], - @{ @"name": @"separator" }, - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Report Issue…", - @"action" : @"url:", - @"key" : @"", - @"target" : self, - @"url" : @"https://github.com/mpv-player/mpv/issues/new/choose" - }], - [NSMutableDictionary dictionaryWithDictionary:@{ - @"name" : @"Show log File…", - @"action" : @"showFile:", - @"key" : @"", - @"target" : self, - @"file" : @"~/Library/Logs/mpv.log", - @"alertTitle" : @"No log File found.", - @"alertText" : @"You deactivated logging for the Bundle." - }] - ] - } - ]; - - [NSApp setMainMenu:[self mainMenu]]; - } - - return self; -} - -- (NSMenu *)mainMenu -{ - NSMenu *mainMenu = [[NSMenu alloc] initWithTitle:@"MainMenu"]; - [NSApp setServicesMenu:[[NSMenu alloc] init]]; - NSString* bundle = [[[NSProcessInfo processInfo] environment] objectForKey:@"MPVBUNDLE"]; - - for(id mMenu in menuTree) { - NSMenu *menu = [[NSMenu alloc] initWithTitle:mMenu[@"name"]]; - NSMenuItem *mItem = [mainMenu addItemWithTitle:mMenu[@"name"] - action:nil - keyEquivalent:@""]; - [mainMenu setSubmenu:menu forItem:mItem]; - - for(id subMenu in mMenu[@"menu"]) { - NSString *name = subMenu[@"name"]; - NSString *action = subMenu[@"action"]; - -#if HAVE_MACOS_TOUCHBAR - if ([action isEqual:@"toggleTouchBarCustomizationPalette:"]) { - continue; - } -#endif - - if ([name isEqual:@"Show log File…"] && ![bundle isEqual:@"true"]) { - continue; - } - - if ([name isEqual:@"separator"]) { - [menu addItem:[NSMenuItem separatorItem]]; - } else { - NSMenuItem *iItem = [menu addItemWithTitle:name - action:NSSelectorFromString(action) - keyEquivalent:subMenu[@"key"]]; - [iItem setTarget:subMenu[@"target"]]; - [subMenu setObject:iItem forKey:@"menuItem"]; - - NSNumber *m = subMenu[@"modifiers"]; - if (m) { - [iItem setKeyEquivalentModifierMask:m.unsignedIntegerValue]; - } - - if ([subMenu[@"name"] isEqual:@"Services"]) { - iItem.submenu = [NSApp servicesMenu]; - } - } - } - } - - return mainMenu; -} - -- (void)about -{ - NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys: - @"mpv", @"ApplicationName", - [(Application *)NSApp getMPVIcon], @"ApplicationIcon", - [NSString stringWithUTF8String:mpv_copyright], @"Copyright", - [NSString stringWithUTF8String:mpv_version], @"ApplicationVersion", - nil]; - [NSApp orderFrontStandardAboutPanelWithOptions:options]; -} - -- (void)preferences:(NSMenuItem *)menuItem -{ - NSWorkspace *workspace = [NSWorkspace sharedWorkspace]; - NSFileManager *fileManager = [NSFileManager defaultManager]; - NSMutableDictionary *mItemDict = [self getDictFromMenuItem:menuItem]; - NSArray *configPaths = @[ - [NSString stringWithFormat:@"%@/.mpv/", NSHomeDirectory()], - [NSString stringWithFormat:@"%@/.config/mpv/", NSHomeDirectory()]]; - - for (id path in configPaths) { - NSString *fileP = [path stringByAppendingString:mItemDict[@"file"]]; - if ([fileManager fileExistsAtPath:fileP]){ - if ([workspace openFile:fileP]) - return; - [workspace openFile:path]; - [self alertWithTitle:mItemDict[@"alertTitle1"] - andText:mItemDict[@"alertText1"]]; - return; - } - if ([workspace openFile:path]) { - [self alertWithTitle:mItemDict[@"alertTitle2"] - andText:mItemDict[@"alertText2"]]; - return; - } - } - - [self alertWithTitle:mItemDict[@"alertTitle3"] - andText:mItemDict[@"alertText3"]]; -} - -- (void)quit:(NSMenuItem *)menuItem -{ - NSString *cmd = [self getDictFromMenuItem:menuItem][@"cmd"]; - [(Application *)NSApp stopMPV:(char *)[cmd UTF8String]]; -} - -- (void)openFile -{ - NSOpenPanel *panel = [[NSOpenPanel alloc] init]; - [panel setCanChooseDirectories:YES]; - [panel setAllowsMultipleSelection:YES]; - - if ([panel runModal] == NSModalResponseOK){ - NSMutableArray *fileArray = [[NSMutableArray alloc] init]; - for (id url in [panel URLs]) - [fileArray addObject:[url path]]; - [(Application *)NSApp openFiles:fileArray]; - } -} - -- (void)openPlaylist -{ - NSOpenPanel *panel = [[NSOpenPanel alloc] init]; - - if ([panel runModal] == NSModalResponseOK){ - NSString *pl = [NSString stringWithFormat:@"loadlist \"%@\"", - [panel URLs][0].path]; - [(Application *)NSApp queueCommand:(char *)[pl UTF8String]]; - } -} - -- (void)openURL -{ - NSAlert *alert = [[NSAlert alloc] init]; - [alert setMessageText:@"Open URL"]; - [alert addButtonWithTitle:@"Ok"]; - [alert addButtonWithTitle:@"Cancel"]; - [alert setIcon:[(Application *)NSApp getMPVIcon]]; - - NSTextField *input = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 300, 24)]; - [input setPlaceholderString:@"URL"]; - [alert setAccessoryView:input]; - - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ - [input becomeFirstResponder]; - }); - - if ([alert runModal] == NSAlertFirstButtonReturn && [input stringValue].length > 0) { - NSArray *url = [NSArray arrayWithObjects:[input stringValue], nil]; - [(Application *)NSApp openFiles:url]; - } -} - -- (void)cmd:(NSMenuItem *)menuItem -{ - NSString *cmd = [self getDictFromMenuItem:menuItem][@"cmd"]; - [(Application *)NSApp queueCommand:(char *)[cmd UTF8String]]; -} - -- (void)url:(NSMenuItem *)menuItem -{ - NSString *url = [self getDictFromMenuItem:menuItem][@"url"]; - [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:url]]; -} - -- (void)showFile:(NSMenuItem *)menuItem -{ - NSWorkspace *workspace = [NSWorkspace sharedWorkspace]; - NSFileManager *fileManager = [NSFileManager defaultManager]; - NSMutableDictionary *mItemDict = [self getDictFromMenuItem:menuItem]; - NSString *file = [mItemDict[@"file"] stringByExpandingTildeInPath]; - - if ([fileManager fileExistsAtPath:file]){ - NSURL *url = [NSURL fileURLWithPath:file]; - NSArray *urlArray = [NSArray arrayWithObjects:url, nil]; - - [workspace activateFileViewerSelectingURLs:urlArray]; - return; - } - - [self alertWithTitle:mItemDict[@"alertTitle"] - andText:mItemDict[@"alertText"]]; -} - -- (void)alertWithTitle:(NSString *)title andText:(NSString *)text -{ - NSAlert *alert = [[NSAlert alloc] init]; - [alert setMessageText:title]; - [alert setInformativeText:text]; - [alert addButtonWithTitle:@"Ok"]; - [alert setIcon:[(Application *)NSApp getMPVIcon]]; - [alert runModal]; -} - -- (NSMutableDictionary *)getDictFromMenuItem:(NSMenuItem *)menuItem -{ - for(id mMenu in menuTree) { - for(id subMenu in mMenu[@"menu"]) { - if([subMenu[@"menuItem"] isEqual:menuItem]) - return subMenu; - } - } - - return nil; -} - -- (void)registerSelector:(SEL)action forKey:(MPMenuKey)key -{ - for(id mMenu in menuTree) { - for(id subMenu in mMenu[@"menu"]) { - if([subMenu[@"cmdSpecial"] isEqual:[NSNumber numberWithInt:key]]) { - [subMenu[@"menuItem"] setAction:action]; - return; - } - } - } -} - -@end diff --git a/osdep/macosx_touchbar.h b/osdep/macosx_touchbar.h deleted file mode 100644 index a03b68c..0000000 --- a/osdep/macosx_touchbar.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * This file is part of mpv. - * - * mpv is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * mpv is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with mpv. If not, see <http://www.gnu.org/licenses/>. - */ - -#import <Cocoa/Cocoa.h> -#import "osdep/macosx_application_objc.h" - -#define BASE_ID @"io.mpv.touchbar" -static NSTouchBarCustomizationIdentifier customID = BASE_ID; -static NSTouchBarItemIdentifier seekBar = BASE_ID ".seekbar"; -static NSTouchBarItemIdentifier play = BASE_ID ".play"; -static NSTouchBarItemIdentifier nextItem = BASE_ID ".nextItem"; -static NSTouchBarItemIdentifier previousItem = BASE_ID ".previousItem"; -static NSTouchBarItemIdentifier nextChapter = BASE_ID ".nextChapter"; -static NSTouchBarItemIdentifier previousChapter = BASE_ID ".previousChapter"; -static NSTouchBarItemIdentifier cycleAudio = BASE_ID ".cycleAudio"; -static NSTouchBarItemIdentifier cycleSubtitle = BASE_ID ".cycleSubtitle"; -static NSTouchBarItemIdentifier currentPosition = BASE_ID ".currentPosition"; -static NSTouchBarItemIdentifier timeLeft = BASE_ID ".timeLeft"; - -struct mpv_event; - -@interface TouchBar : NSTouchBar <NSTouchBarDelegate> - --(void)processEvent:(struct mpv_event *)event; - -@property(nonatomic, retain) Application *app; -@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 deleted file mode 100644 index ccce8f7..0000000 --- a/osdep/macosx_touchbar.m +++ /dev/null @@ -1,334 +0,0 @@ -/* - * This file is part of mpv. - * - * mpv is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * mpv is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with mpv. If not, see <http://www.gnu.org/licenses/>. - */ - -#include "player/client.h" -#import "macosx_touchbar.h" - -@implementation TouchBar - -@synthesize app = _app; -@synthesize touchbarItems = _touchbar_items; -@synthesize duration = _duration; -@synthesize position = _position; -@synthesize pause = _pause; - -- (id)init -{ - if (self = [super init]) { - self.touchbarItems = @{ - seekBar: [NSMutableDictionary dictionaryWithDictionary:@{ - @"type": @"slider", - @"name": @"Seek Bar", - @"cmd": @"seek %f absolute-percent" - }], - play: [NSMutableDictionary dictionaryWithDictionary:@{ - @"type": @"button", - @"name": @"Play Button", - @"cmd": @"cycle pause", - @"image": [NSImage imageNamed:NSImageNameTouchBarPauseTemplate], - @"imageAlt": [NSImage imageNamed:NSImageNameTouchBarPlayTemplate] - }], - previousItem: [NSMutableDictionary dictionaryWithDictionary:@{ - @"type": @"button", - @"name": @"Previous Playlist Item", - @"cmd": @"playlist-prev", - @"image": [NSImage imageNamed:NSImageNameTouchBarGoBackTemplate] - }], - nextItem: [NSMutableDictionary dictionaryWithDictionary:@{ - @"type": @"button", - @"name": @"Next Playlist Item", - @"cmd": @"playlist-next", - @"image": [NSImage imageNamed:NSImageNameTouchBarGoForwardTemplate] - }], - previousChapter: [NSMutableDictionary dictionaryWithDictionary:@{ - @"type": @"button", - @"name": @"Previous Chapter", - @"cmd": @"add chapter -1", - @"image": [NSImage imageNamed:NSImageNameTouchBarSkipBackTemplate] - }], - nextChapter: [NSMutableDictionary dictionaryWithDictionary:@{ - @"type": @"button", - @"name": @"Next Chapter", - @"cmd": @"add chapter 1", - @"image": [NSImage imageNamed:NSImageNameTouchBarSkipAheadTemplate] - }], - cycleAudio: [NSMutableDictionary dictionaryWithDictionary:@{ - @"type": @"button", - @"name": @"Cycle Audio", - @"cmd": @"cycle audio", - @"image": [NSImage imageNamed:NSImageNameTouchBarAudioInputTemplate] - }], - cycleSubtitle: [NSMutableDictionary dictionaryWithDictionary:@{ - @"type": @"button", - @"name": @"Cycle Subtitle", - @"cmd": @"cycle sub", - @"image": [NSImage imageNamed:NSImageNameTouchBarComposeTemplate] - }], - currentPosition: [NSMutableDictionary dictionaryWithDictionary:@{ - @"type": @"text", - @"name": @"Current Position" - }], - timeLeft: [NSMutableDictionary dictionaryWithDictionary:@{ - @"type": @"text", - @"name": @"Time Left" - }] - }; - - [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"]) { - 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: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]; - NSImage *tbImage = self.touchbarItems[identifier][@"image"]; - NSButton *tbButton = [NSButton buttonWithImage:tbImage target:self action:@selector(buttonAction:)]; - 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]; - NSTextField *tbText = [NSTextField labelWithString:@"0:00"]; - tbText.alignment = NSTextAlignmentCenter; - 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; - int minutes = (time / 60) % 60; - int hours = time / (60 * 60); - - NSString *stime = hours > 0 ? [NSString stringWithFormat:@"%d:", hours] : @""; - stime = (stime.length > 0 || minutes > 9) ? - [NSString stringWithFormat:@"%@%02d:", stime, minutes] : - [NSString stringWithFormat:@"%d:", minutes]; - stime = [NSString stringWithFormat:@"%@%02d", stime, seconds]; - - return stime; -} - -- (void)removeConstraintForIdentifier:(NSTouchBarItemIdentifier)identifier -{ - NSTextField *field = self.touchbarItems[identifier][@"view"]; - [field removeConstraint:self.touchbarItems[identifier][@"constrain"]]; -} - -- (void)applyConstraintFromString:(NSString *)string - forIdentifier:(NSTouchBarItemIdentifier)identifier -{ - NSTextField *field = self.touchbarItems[identifier][@"view"]; - if (field) { - NSString *fString = [[string componentsSeparatedByCharactersInSet: - [NSCharacterSet decimalDigitCharacterSet]] componentsJoinedByString:@"0"]; - NSTextField *textField = [NSTextField labelWithString:fString]; - NSSize size = [textField frame].size; - - NSLayoutConstraint *con = - [NSLayoutConstraint constraintWithItem:field - attribute:NSLayoutAttributeWidth - relatedBy:NSLayoutRelationEqual - toItem:nil - attribute:NSLayoutAttributeNotAnAttribute - multiplier:1.0 - constant:(int)ceil(size.width*1.1)]; - [field addConstraint:con]; - [self.touchbarItems[identifier] setObject:con forKey:@"constrain"]; - } -} - -- (NSString *)getIdentifierFromView:(id)view -{ - NSString *identifier; - for (identifier in self.touchbarItems) - if([self.touchbarItems[identifier][@"view"] isEqual:view]) - break; - return identifier; -} - -- (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) { - 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/osdep/main-fn-cocoa.c b/osdep/main-fn-cocoa.c deleted file mode 100644 index eeed127..0000000 --- a/osdep/main-fn-cocoa.c +++ /dev/null @@ -1,10 +0,0 @@ -#include "osdep/macosx_application.h" - -// This is needed because Cocoa absolutely requires creating the NSApplication -// singleton and running it in the "main" thread. It is apparently not -// possible to do this on a separate thread at all. It is not known how -// Apple managed this colossal fuckup. -int main(int argc, char *argv[]) -{ - return cocoa_main(argc, argv); -} diff --git a/osdep/main-fn-mac.c b/osdep/main-fn-mac.c new file mode 100644 index 0000000..1406100 --- /dev/null +++ b/osdep/main-fn-mac.c @@ -0,0 +1,7 @@ +#include "osdep/mac/app_bridge.h" + +// Cocoa absolutely requires creating the NSApplication singleton and running it on the main thread. +int main(int argc, char *argv[]) +{ + return cocoa_main(argc, argv); +} diff --git a/osdep/main-fn-win.c b/osdep/main-fn-win.c index 16ea80b..cf2df57 100644 --- a/osdep/main-fn-win.c +++ b/osdep/main-fn-win.c @@ -1,4 +1,5 @@ #include <windows.h> +#include <shellapi.h> #ifndef BASE_SEARCH_PATH_ENABLE_SAFE_SEARCHMODE #define BASE_SEARCH_PATH_ENABLE_SAFE_SEARCHMODE (0x0001) @@ -12,7 +13,7 @@ #ifndef HEAP_OPTIMIZE_RESOURCES_CURRENT_VERSION #define HEAP_OPTIMIZE_RESOURCES_CURRENT_VERSION 1 -enum { HeapOptimizeResources = 3 }; +#define HeapOptimizeResources ((HEAP_INFORMATION_CLASS)3) struct HEAP_OPTIMIZE_RESOURCES_INFORMATION { DWORD Version; diff --git a/osdep/mpv.exe.manifest b/osdep/mpv.exe.manifest index 32dd80b..3cade40 100644 --- a/osdep/mpv.exe.manifest +++ b/osdep/mpv.exe.manifest @@ -12,6 +12,8 @@ <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True/PM</dpiAware> <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2,PerMonitor</dpiAwareness> <activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage> + <longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware> + <heapType xmlns="http://schemas.microsoft.com/SMI/2020/WindowsSettings">SegmentHeap</heapType> </windowsSettings> </application> <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2"> diff --git a/osdep/path-macosx.m b/osdep/path-mac.m index 8a5a704..2054269 100644 --- a/osdep/path-macosx.m +++ b/osdep/path-mac.m @@ -19,7 +19,7 @@ #include "options/path.h" #include "osdep/path.h" -const char *mp_get_platform_path_osx(void *talloc_ctx, const char *type) +const char *mp_get_platform_path_mac(void *talloc_ctx, const char *type) { if (strcmp(type, "osxbundle") == 0 && getenv("MPVBUNDLE")) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; diff --git a/osdep/path-uwp.c b/osdep/path-uwp.c index 7eafb03..e21c9d1 100644 --- a/osdep/path-uwp.c +++ b/osdep/path-uwp.c @@ -27,9 +27,13 @@ WINBASEAPI DWORD WINAPI GetCurrentDirectoryW(DWORD nBufferLength, LPWSTR lpBuffe const char *mp_get_platform_path_uwp(void *talloc_ctx, const char *type) { if (strcmp(type, "home") == 0) { - wchar_t homeDir[_MAX_PATH]; - if (GetCurrentDirectoryW(_MAX_PATH, homeDir) != 0) - return mp_to_utf8(talloc_ctx, homeDir); + DWORD count = GetCurrentDirectoryW(0, NULL); + wchar_t *home_dir = talloc_array(NULL, wchar_t, count); + if (GetCurrentDirectoryW(count, home_dir) != 0) { + char *ret = mp_to_utf8(talloc_ctx, home_dir); + talloc_free(home_dir); + return ret; + } } return NULL; } diff --git a/osdep/path-win.c b/osdep/path-win.c index bddf5a5..3b104ca 100644 --- a/osdep/path-win.c +++ b/osdep/path-win.c @@ -24,17 +24,15 @@ #include "osdep/path.h" #include "osdep/threads.h" -// Warning: do not use PATH_MAX. Cygwin messed it up. - static mp_once path_init_once = MP_STATIC_ONCE_INITIALIZER; static char *portable_path; static char *mp_get_win_exe_dir(void *talloc_ctx) { - wchar_t w_exedir[MAX_PATH + 1] = {0}; + wchar_t *w_exedir = talloc_array(NULL, wchar_t, MP_PATH_MAX); - int len = (int)GetModuleFileNameW(NULL, w_exedir, MAX_PATH); + int len = (int)GetModuleFileNameW(NULL, w_exedir, MP_PATH_MAX); int imax = 0; for (int i = 0; i < len; i++) { if (w_exedir[i] == '\\') { @@ -42,10 +40,11 @@ static char *mp_get_win_exe_dir(void *talloc_ctx) imax = i; } } - w_exedir[imax] = '\0'; - return mp_to_utf8(talloc_ctx, w_exedir); + char *ret = mp_to_utf8(talloc_ctx, w_exedir); + talloc_free(w_exedir); + return ret; } static char *mp_get_win_exe_subdir(void *ta_ctx, const char *name) diff --git a/osdep/path.h b/osdep/path.h index 2c00ea5..c5540e4 100644 --- a/osdep/path.h +++ b/osdep/path.h @@ -7,7 +7,7 @@ // The following type values are defined: // "home" the native mpv-specific user config dir // "old_home" same as "home", but lesser priority (compatibility) -// "osxbundle" OSX bundle resource path +// "osxbundle" macOS bundle resource path // "global" the least priority, global config file location // "desktop" path to desktop contents // @@ -26,7 +26,7 @@ typedef const char *(*mp_get_platform_path_cb)(void *talloc_ctx, const char *typ const char *mp_get_platform_path_darwin(void *talloc_ctx, const char *type); const char *mp_get_platform_path_uwp(void *talloc_ctx, const char *type); const char *mp_get_platform_path_win(void *talloc_ctx, const char *type); -const char *mp_get_platform_path_osx(void *talloc_ctx, const char *type); +const char *mp_get_platform_path_mac(void *talloc_ctx, const char *type); const char *mp_get_platform_path_unix(void *talloc_ctx, const char *type); #endif diff --git a/osdep/semaphore_osx.c b/osdep/semaphore-mac.c index bfb4d57..bfb4d57 100644 --- a/osdep/semaphore_osx.c +++ b/osdep/semaphore-mac.c diff --git a/osdep/semaphore.h b/osdep/semaphore.h index 40cf383..81da12d 100644 --- a/osdep/semaphore.h +++ b/osdep/semaphore.h @@ -4,7 +4,7 @@ #include <sys/types.h> #include <semaphore.h> -// OSX provides non-working empty stubs, so we emulate them. +// macOS provides non-working empty stubs, so we emulate them. // This should be AS-safe, but cancellation issues were ignored. // sem_getvalue() is not provided. // sem_post() won't always correctly return an error on overflow. diff --git a/osdep/subprocess-posix.c b/osdep/subprocess-posix.c index 0656ec5..c75d267 100644 --- a/osdep/subprocess-posix.c +++ b/osdep/subprocess-posix.c @@ -281,7 +281,7 @@ void mp_subprocess2(struct mp_subprocess_opts *opts, if (pid) kill(pid, SIGKILL); killed_by_us = true; - break; + goto break_poll; } struct mp_subprocess_fd *fd = &opts->fds[n]; if (fd->on_read) { @@ -316,6 +316,8 @@ void mp_subprocess2(struct mp_subprocess_opts *opts, } } +break_poll: + // Note: it can happen that a child process closes the pipe, but does not // terminate yet. In this case, we would have to run waitpid() in // a separate thread and use pthread_cancel(), or use other weird diff --git a/osdep/subprocess-win.c b/osdep/subprocess-win.c index 5413b24..efeb650 100644 --- a/osdep/subprocess-win.c +++ b/osdep/subprocess-win.c @@ -377,7 +377,8 @@ void mp_subprocess2(struct mp_subprocess_opts *opts, // Get pointers to the arrays in lpReserved2. This is an undocumented data // structure used by MSVCRT (and other frameworks and runtimes) to emulate // FD inheritance. The format is unofficially documented here: - // https://www.catch22.net/tuts/undocumented-createprocess + // <https://web.archive.org/web/20221014190010/ + // https://www.catch22.net/tuts/undocumented-createprocess> si.StartupInfo.cbReserved2 = sizeof(int) + crt_fd_count * (1 + sizeof(intptr_t)); si.StartupInfo.lpReserved2 = talloc_size(tmp, si.StartupInfo.cbReserved2); char *crt_buf_flags = si.StartupInfo.lpReserved2 + sizeof(int); @@ -402,7 +403,8 @@ void mp_subprocess2(struct mp_subprocess_opts *opts, // Specify which handles are inherited by the subprocess. If this isn't // specified, the subprocess inherits all inheritable handles, which could // include handles created by other threads. See: - // http://blogs.msdn.com/b/oldnewthing/archive/2011/12/16/10248328.aspx + // <https://web.archive.org/web/20121127222200/ + // http://blogs.msdn.com/b/oldnewthing/archive/2011/12/16/10248328.aspx> si.lpAttributeList = create_handle_list(tmp, share_hndls, share_hndl_count); // If we have a console, the subprocess will automatically attach to it so diff --git a/osdep/terminal-dummy.c b/osdep/terminal-dummy.c index 4b33d78..8ae6c64 100644 --- a/osdep/terminal-dummy.c +++ b/osdep/terminal-dummy.c @@ -1,5 +1,7 @@ #include "terminal.h" +#include "misc/bstr.h" + void terminal_init(void) { } @@ -25,8 +27,14 @@ void terminal_get_size2(int *rows, int *cols, int *px_width, int *px_height) { } -void mp_write_console_ansi(void *wstream, char *buf) +int mp_console_vfprintf(void *wstream, const char *format, va_list args) +{ + return 0; +} + +int mp_console_write(void *wstream, bstr str) { + return 0; } bool terminal_try_attach(void) diff --git a/osdep/terminal-unix.c b/osdep/terminal-unix.c index d5b8fe3..be0463b 100644 --- a/osdep/terminal-unix.c +++ b/osdep/terminal-unix.c @@ -324,7 +324,7 @@ static int setsigaction(int signo, void (*handler) (int), struct sigaction sa; sa.sa_handler = handler; - if(do_mask) + if (do_mask) sigfillset(&sa.sa_mask); else sigemptyset(&sa.sa_mask); @@ -354,24 +354,11 @@ static int death_pipe[2] = {-1, -1}; enum { PIPE_STOP, PIPE_CONT }; static int stop_cont_pipe[2] = {-1, -1}; -static void stop_sighandler(int signum) +static void stop_cont_sighandler(int signum) { int saved_errno = errno; - (void)write(stop_cont_pipe[1], &(char){PIPE_STOP}, 1); - errno = saved_errno; - - // note: for this signal, we use SA_RESETHAND but do NOT mask signals - // so this will invoke the default handler - raise(SIGTSTP); -} - -static void continue_sighandler(int signum) -{ - int saved_errno = errno; - // SA_RESETHAND has reset SIGTSTP, so we need to restore it here - setsigaction(SIGTSTP, stop_sighandler, SA_RESETHAND, false); - - (void)write(stop_cont_pipe[1], &(char){PIPE_CONT}, 1); + char sig = signum == SIGCONT ? PIPE_CONT : PIPE_STOP; + (void)write(stop_cont_pipe[1], &sig, 1); errno = saved_errno; } @@ -435,10 +422,20 @@ static MP_THREAD_VOID terminal_thread(void *ptr) if (fds[1].revents & POLLIN) { int8_t c = -1; (void)read(stop_cont_pipe[0], &c, 1); - if (c == PIPE_STOP) + if (c == PIPE_STOP) { do_deactivate_getch2(); - else if (c == PIPE_CONT) + if (isatty(STDERR_FILENO)) { + (void)write(STDERR_FILENO, TERM_ESC_RESTORE_CURSOR, + sizeof(TERM_ESC_RESTORE_CURSOR) - 1); + } + // trying to reset SIGTSTP handler to default and raise it will + // result in a race and there's no other way to invoke the + // default handler. so just invoke SIGSTOP since it's + // effectively the same thing. + raise(SIGSTOP); + } else if (c == PIPE_CONT) { getch2_poll(); + } } if (fds[2].revents) { int retval = read(tty_in, &buf.b[buf.len], BUF_LEN - buf.len); @@ -470,10 +467,6 @@ void terminal_setup_getch(struct input_ctx *ictx) if (mp_make_wakeup_pipe(death_pipe) < 0) return; - if (mp_make_wakeup_pipe(stop_cont_pipe) < 0) { - close_sig_pipes(); - return; - } // Disable reading from the terminal even if stdout is not a tty, to make // mpv ... | less @@ -490,8 +483,8 @@ void terminal_setup_getch(struct input_ctx *ictx) } setsigaction(SIGINT, quit_request_sighandler, SA_RESETHAND, false); - setsigaction(SIGQUIT, quit_request_sighandler, SA_RESETHAND, false); - setsigaction(SIGTERM, quit_request_sighandler, SA_RESETHAND, false); + setsigaction(SIGQUIT, quit_request_sighandler, 0, true); + setsigaction(SIGTERM, quit_request_sighandler, 0, true); } void terminal_uninit(void) @@ -555,6 +548,11 @@ void terminal_init(void) assert(!getch2_enabled); getch2_enabled = 1; + if (mp_make_wakeup_pipe(stop_cont_pipe) < 0) { + getch2_enabled = 0; + return; + } + tty_in = tty_out = open("/dev/tty", O_RDWR | O_CLOEXEC); if (tty_in < 0) { tty_in = STDIN_FILENO; @@ -564,8 +562,8 @@ void terminal_init(void) tcgetattr(tty_in, &tio_orig); // handlers to fix terminal settings - setsigaction(SIGCONT, continue_sighandler, 0, true); - setsigaction(SIGTSTP, stop_sighandler, SA_RESETHAND, false); + setsigaction(SIGCONT, stop_cont_sighandler, 0, true); + setsigaction(SIGTSTP, stop_cont_sighandler, 0, true); setsigaction(SIGTTIN, SIG_IGN, 0, true); setsigaction(SIGTTOU, SIG_IGN, 0, true); diff --git a/osdep/terminal-win.c b/osdep/terminal-win.c index 8f3410c..dc75180 100644 --- a/osdep/terminal-win.c +++ b/osdep/terminal-win.c @@ -53,20 +53,17 @@ static void attempt_native_out_vt(HANDLE hOut, DWORD basemode) SetConsoleMode(hOut, basemode); } -static bool is_native_out_vt(HANDLE hOut) -{ - DWORD cmode; - return GetConsoleMode(hOut, &cmode) && - (cmode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) && - !(cmode & DISABLE_NEWLINE_AUTO_RETURN); -} +#define hSTDIN GetStdHandle(STD_INPUT_HANDLE) #define hSTDOUT GetStdHandle(STD_OUTPUT_HANDLE) #define hSTDERR GetStdHandle(STD_ERROR_HANDLE) #define FOREGROUND_ALL (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE) #define BACKGROUND_ALL (BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE) +static bool is_console[STDERR_FILENO + 1]; +static bool is_vt[STDERR_FILENO + 1]; +static bool utf8_output; static short stdoutAttrs = 0; // copied from the screen buffer on init static const unsigned char ansi2win32[8] = { 0, @@ -94,6 +91,23 @@ static HANDLE death; static mp_thread input_thread; static struct input_ctx *input_ctx; +static bool is_native_out_vt_internal(HANDLE hOut) +{ + DWORD cmode; + return GetConsoleMode(hOut, &cmode) && + (cmode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) && + !(cmode & DISABLE_NEWLINE_AUTO_RETURN); +} + +static bool is_native_out_vt(HANDLE hOut) +{ + if (hOut == hSTDOUT) + return is_vt[STDOUT_FILENO]; + if (hOut == hSTDERR) + return is_vt[STDERR_FILENO]; + return is_native_out_vt_internal(hOut); +} + void terminal_get_size(int *w, int *h) { CONSOLE_SCREEN_BUFFER_INFO cinfo; @@ -191,6 +205,12 @@ void terminal_setup_getch(struct input_ctx *ictx) } } +DWORD tmp_buffers_key = FLS_OUT_OF_INDEXES; +struct tmp_buffers { + bstr write_console_buf; + wchar_t *write_console_wbuf; +}; + void terminal_uninit(void) { if (running) { @@ -199,6 +219,7 @@ void terminal_uninit(void) input_ctx = NULL; running = false; } + FlsFree(tmp_buffers_key); } bool terminal_in_background(void) @@ -206,16 +227,53 @@ bool terminal_in_background(void) return false; } -void mp_write_console_ansi(HANDLE wstream, char *buf) +int mp_console_vfprintf(HANDLE wstream, const char *format, va_list args) { - wchar_t *wbuf = mp_from_utf8(NULL, buf); - wchar_t *pos = wbuf; + struct tmp_buffers *buffers = FlsGetValue(tmp_buffers_key); + bool free_buf = false; + if (!buffers) { + buffers = talloc_zero(NULL, struct tmp_buffers); + free_buf = !FlsSetValue(tmp_buffers_key, buffers); + } - while (*pos) { - if (is_native_out_vt(wstream)) { - WriteConsoleW(wstream, pos, wcslen(pos), NULL, NULL); - break; + buffers->write_console_buf.len = 0; + bstr_xappend_vasprintf(buffers, &buffers->write_console_buf, format, args); + + int ret = mp_console_write(wstream, buffers->write_console_buf); + + if (free_buf) + talloc_free(buffers); + + return ret; +} + +int mp_console_write(HANDLE wstream, bstr str) +{ + struct tmp_buffers *buffers = FlsGetValue(tmp_buffers_key); + bool free_buf = false; + if (!buffers) { + buffers = talloc_zero(NULL, struct tmp_buffers); + free_buf = !FlsSetValue(tmp_buffers_key, buffers); + } + + bool vt = is_native_out_vt(wstream); + int wlen = 0; + wchar_t *pos = NULL; + if (!utf8_output || !vt) { + wlen = bstr_to_wchar(buffers, str, &buffers->write_console_wbuf); + pos = buffers->write_console_wbuf; + } + + if (vt) { + if (utf8_output) { + WriteConsoleA(wstream, str.start, str.len, NULL, NULL); + } else { + WriteConsoleW(wstream, pos, wlen, NULL, NULL); } + goto done; + } + + while (*pos) { wchar_t *next = wcschr(pos, '\033'); if (!next) { WriteConsoleW(wstream, pos, wcslen(pos), NULL, NULL); @@ -227,6 +285,10 @@ void mp_write_console_ansi(HANDLE wstream, char *buf) // CSI - Control Sequence Introducer next += 2; + // private sequences + bool priv = next[0] == '?'; + next += priv; + // CSI codes generally follow this syntax: // "\033[" [ <i> (';' <i> )* ] <c> // where <i> are integers, and <c> a single char command code. @@ -250,18 +312,84 @@ void mp_write_console_ansi(HANDLE wstream, char *buf) CONSOLE_SCREEN_BUFFER_INFO info; GetConsoleScreenBufferInfo(wstream, &info); switch (code) { - case 'K': { // erase to end of line - COORD at = info.dwCursorPosition; - int len = info.dwSize.X - at.X; - FillConsoleOutputCharacterW(wstream, ' ', len, at, &(DWORD){0}); - SetConsoleCursorPosition(wstream, at); + case 'K': { // erase line + COORD cursor_pos = info.dwCursorPosition; + COORD at = cursor_pos; + int len; + switch (num_params ? params[0] : 0) { + case 1: + len = at.X; + at.X = 0; + break; + case 2: + len = info.dwSize.X; + at.X = 0; + break; + case 0: + default: + len = info.dwSize.X - at.X; + } + FillConsoleOutputCharacterW(wstream, L' ', len, at, &(DWORD){0}); + SetConsoleCursorPosition(wstream, cursor_pos); + break; + } + case 'B': { // cursor down + info.dwCursorPosition.Y += !num_params ? 1 : params[0]; + SetConsoleCursorPosition(wstream, info.dwCursorPosition); break; } case 'A': { // cursor up - info.dwCursorPosition.Y -= 1; + info.dwCursorPosition.Y -= !num_params ? 1 : params[0]; SetConsoleCursorPosition(wstream, info.dwCursorPosition); break; } + case 'J': { + // Only full screen clear is supported + if (!num_params || params[0] != 2) + break; + + COORD top_left = {0, 0}; + FillConsoleOutputCharacterW(wstream, L' ', info.dwSize.X * info.dwSize.Y, + top_left, &(DWORD){0}); + SetConsoleCursorPosition(wstream, top_left); + break; + } + case 'f': { + if (num_params != 2) + break; + SetConsoleCursorPosition(wstream, (COORD){params[0], params[1]}); + break; + } + case 'l': { + if (!priv || !num_params) + break; + + switch (params[0]) { + case 25:; // hide the cursor + CONSOLE_CURSOR_INFO cursor_info; + if (!GetConsoleCursorInfo(wstream, &cursor_info)) + break; + cursor_info.bVisible = FALSE; + SetConsoleCursorInfo(wstream, &cursor_info); + break; + } + break; + } + case 'h': { + if (!priv || !num_params) + break; + + switch (params[0]) { + case 25:; // show the cursor + CONSOLE_CURSOR_INFO cursor_info; + if (!GetConsoleCursorInfo(wstream, &cursor_info)) + break; + cursor_info.bVisible = TRUE; + SetConsoleCursorInfo(wstream, &cursor_info); + break; + } + break; + } case 'm': { // "SGR" short attr = info.wAttributes; if (num_params == 0) // reset @@ -352,7 +480,13 @@ void mp_write_console_ansi(HANDLE wstream, char *buf) pos = next; } - talloc_free(wbuf); +done:; + int ret = buffers->write_console_buf.len; + + if (free_buf) + talloc_free(buffers); + + return ret; } static bool is_a_console(HANDLE h) @@ -360,6 +494,17 @@ static bool is_a_console(HANDLE h) return GetConsoleMode(h, &(DWORD){0}); } +bool mp_check_console(void *handle) +{ + if (handle == hSTDIN) + return is_console[STDIN_FILENO]; + if (handle == hSTDOUT) + return is_console[STDOUT_FILENO]; + if (handle == hSTDERR) + return is_console[STDERR_FILENO]; + return is_a_console(handle); +} + static void reopen_console_handle(DWORD std, int fd, FILE *stream) { HANDLE handle = GetStdHandle(std); @@ -369,7 +514,6 @@ static void reopen_console_handle(DWORD std, int fd, FILE *stream) } else { freopen("CONOUT$", "wt", stream); } - setvbuf(stream, NULL, _IONBF, 0); // Set the low-level FD to the new handle value, since mp_subprocess2 // callers might rely on low-level FDs being set. Note, with this @@ -420,6 +564,19 @@ void terminal_init(void) cmode |= (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT); attempt_native_out_vt(hSTDOUT, cmode); attempt_native_out_vt(hSTDERR, cmode); + + // Init for mp_check_console(), this never changes during runtime + is_console[STDIN_FILENO] = is_a_console(hSTDIN); + is_console[STDOUT_FILENO] = is_a_console(hSTDOUT); + is_console[STDERR_FILENO] = is_a_console(hSTDERR); + + // Init for is_native_out_vt(), this is never disabled/changed during runtime + is_vt[STDOUT_FILENO] = is_native_out_vt_internal(hSTDOUT); + is_vt[STDERR_FILENO] = is_native_out_vt_internal(hSTDERR); + GetConsoleScreenBufferInfo(hSTDOUT, &cinfo); stdoutAttrs = cinfo.wAttributes; + + tmp_buffers_key = FlsAlloc((PFLS_CALLBACK_FUNCTION)talloc_free); + utf8_output = SetConsoleOutputCP(CP_UTF8); } diff --git a/osdep/terminal.h b/osdep/terminal.h index 5383a17..c83b0a2 100644 --- a/osdep/terminal.h +++ b/osdep/terminal.h @@ -20,12 +20,16 @@ #ifndef MPLAYER_GETCH2_H #define MPLAYER_GETCH2_H +#include <stdarg.h> #include <stdbool.h> -#include <stdio.h> + +#include "misc/bstr.h" #define TERM_ESC_GOTO_YX "\033[%d;%df" #define TERM_ESC_HIDE_CURSOR "\033[?25l" #define TERM_ESC_RESTORE_CURSOR "\033[?25h" +#define TERM_ESC_SYNC_UPDATE_BEGIN "\033[?2026h" +#define TERM_ESC_SYNC_UPDATE_END "\033[?2026l" #define TERM_ESC_CLEAR_SCREEN "\033[2J" #define TERM_ESC_ALT_SCREEN "\033[?1049h" @@ -52,7 +56,9 @@ void terminal_get_size(int *w, int *h); void terminal_get_size2(int *rows, int *cols, int *px_width, int *px_height); // Windows only. -void mp_write_console_ansi(void *wstream, char *buf); +int mp_console_vfprintf(void *wstream, const char *format, va_list args); +int mp_console_write(void *wstream, bstr str); +bool mp_check_console(void *handle); /* Windows-only function to attach to the parent process's console */ bool terminal_try_attach(void); diff --git a/osdep/threads-posix.h b/osdep/threads-posix.h index 482e4a8..4b3bb90 100644 --- a/osdep/threads-posix.h +++ b/osdep/threads-posix.h @@ -23,6 +23,11 @@ #include "common/common.h" #include "config.h" +// We make use of NON-POSIX pthreads functions and certain systems +// require this header to build without issues. (ex: OpenBSD) +#if HAVE_BSD_THREAD_NAME +#include <pthread_np.h> +#endif #include "osdep/compiler.h" #include "timer.h" @@ -97,7 +102,7 @@ typedef pthread_once_t mp_once; typedef pthread_t mp_thread_id; typedef pthread_t mp_thread; -#define MP_STATIC_COND_INITIALIZER (mp_cond){ .cond = PTHREAD_COND_INITIALIZER, .clk_id = CLOCK_REALTIME } +#define MP_STATIC_COND_INITIALIZER { .cond = PTHREAD_COND_INITIALIZER, .clk_id = CLOCK_REALTIME } #define MP_STATIC_MUTEX_INITIALIZER PTHREAD_MUTEX_INITIALIZER #define MP_STATIC_ONCE_INITIALIZER PTHREAD_ONCE_INIT @@ -230,7 +235,7 @@ static inline void mp_thread_set_name(const char *name) } #elif HAVE_BSD_THREAD_NAME pthread_set_name_np(pthread_self(), name); -#elif HAVE_OSX_THREAD_NAME +#elif HAVE_MAC_THREAD_NAME pthread_setname_np(name); #endif } diff --git a/osdep/threads-win32.h b/osdep/threads-win32.h index dbce353..ec90fe9 100644 --- a/osdep/threads-win32.h +++ b/osdep/threads-win32.h @@ -39,7 +39,7 @@ typedef HANDLE mp_thread; typedef DWORD mp_thread_id; #define MP_STATIC_COND_INITIALIZER CONDITION_VARIABLE_INIT -#define MP_STATIC_MUTEX_INITIALIZER (mp_mutex){ .srw = SRWLOCK_INIT } +#define MP_STATIC_MUTEX_INITIALIZER { .srw = SRWLOCK_INIT } #define MP_STATIC_ONCE_INITIALIZER INIT_ONCE_STATIC_INIT static inline int mp_mutex_init_type_internal(mp_mutex *mutex, enum mp_mutex_type mtype) @@ -115,7 +115,7 @@ static inline int mp_cond_timedwait(mp_cond *cond, mp_mutex *mutex, int64_t time timeout = MPCLAMP(timeout, 0, MP_TIME_MS_TO_NS(INFINITE)) / MP_TIME_MS_TO_NS(1); int ret = 0; - int hrt = mp_start_hires_timers(timeout); + int64_t hrt = mp_start_hires_timers(MP_TIME_MS_TO_NS(timeout)); BOOL bRet; if (mutex->use_cs) { diff --git a/osdep/timer-darwin.c b/osdep/timer-darwin.c index bb8a9b4..36e7194 100644 --- a/osdep/timer-darwin.c +++ b/osdep/timer-darwin.c @@ -36,7 +36,12 @@ void mp_sleep_ns(int64_t ns) uint64_t mp_raw_time_ns(void) { - return mach_absolute_time() * timebase_ratio_ns; + return mp_raw_time_ns_from_mach(mach_absolute_time()); +} + +uint64_t mp_raw_time_ns_from_mach(uint64_t mach_time) +{ + return mach_time * timebase_ratio_ns; } void mp_raw_time_init(void) diff --git a/osdep/timer-win32.c b/osdep/timer-win32.c index 7867b5a..a2815ca 100644 --- a/osdep/timer-win32.c +++ b/osdep/timer-win32.c @@ -16,6 +16,8 @@ */ #include <windows.h> +#include <winternl.h> +#include <ntstatus.h> #include <sys/time.h> #include <mmsystem.h> #include <stdlib.h> @@ -27,16 +29,23 @@ static LARGE_INTEGER perf_freq; -// ms values -static int hires_max = 50; -static int hires_res = 1; +static int64_t hires_max = MP_TIME_MS_TO_NS(50); +static int64_t hires_res = MP_TIME_MS_TO_NS(1); -int mp_start_hires_timers(int wait_ms) +// NtSetTimerResolution allows setting the timer resolution to less than 1 ms. +// Resolutions are specified in 100-ns units. +// If Set is TRUE, set the RequestedResolution. Otherwise, return to the previous resolution. +NTSTATUS NTAPI NtSetTimerResolution(ULONG RequestedResolution, BOOLEAN Set, PULONG ActualResolution); +// Acquire the valid timer resolution range. +NTSTATUS NTAPI NtQueryTimerResolution(PULONG MinimumResolution, PULONG MaximumResolution, PULONG ActualResolution); + +int64_t mp_start_hires_timers(int64_t wait_ns) { #if !HAVE_UWP - // policy: request hires_res ms resolution if wait < hires_max ms - if (wait_ms > 0 && wait_ms <= hires_max && - timeBeginPeriod(hires_res) == TIMERR_NOERROR) + ULONG actual_res = 0; + // policy: request hires_res resolution if wait < hires_max ns + if (wait_ns > 0 && wait_ns <= hires_max && + NtSetTimerResolution(hires_res / 100, TRUE, &actual_res) == STATUS_SUCCESS) { return hires_res; } @@ -44,11 +53,12 @@ int mp_start_hires_timers(int wait_ms) return 0; } -void mp_end_hires_timers(int res_ms) +void mp_end_hires_timers(int64_t res_ns) { #if !HAVE_UWP - if (res_ms > 0) - timeEndPeriod(res_ms); + ULONG actual_res = 0; + if (res_ns > 0) + NtSetTimerResolution(res_ns / 100, FALSE, &actual_res); #endif } @@ -57,7 +67,7 @@ void mp_sleep_ns(int64_t ns) if (ns < 0) return; - int hrt = mp_start_hires_timers(ns < 1e6 ? 1 : ns / 1e6); + int64_t hrt = mp_start_hires_timers(ns); #ifndef CREATE_WAITABLE_TIMER_HIGH_RESOLUTION #define CREATE_WAITABLE_TIMER_HIGH_RESOLUTION 0x2 @@ -107,21 +117,28 @@ void mp_raw_time_init(void) QueryPerformanceFrequency(&perf_freq); #if !HAVE_UWP + ULONG min_res, max_res, actual_res; + if (NtQueryTimerResolution(&min_res, &max_res, &actual_res) != STATUS_SUCCESS) { + min_res = 156250; + max_res = 10000; + } + // allow (undocumented) control of all the High Res Timers parameters, // for easier experimentation and diagnostic of bug reports. const char *v; + char *end; // 1..1000 ms max timetout for hires (used in "perwait" mode) if ((v = getenv("MPV_HRT_MAX"))) { - int hmax = atoi(v); - if (hmax >= 1 && hmax <= 1000) + int64_t hmax = strtoll(v, &end, 10); + if (*end == '\0' && hmax >= MP_TIME_MS_TO_NS(1) && hmax <= MP_TIME_MS_TO_NS(1000)) hires_max = hmax; } - // 1..15 ms hires resolution (not used in "never" mode) + // hires resolution clamped by the available resolution range (not used in "never" mode) if ((v = getenv("MPV_HRT_RES"))) { - int res = atoi(v); - if (res >= 1 && res <= 15) + int64_t res = strtoll(v, &end, 10); + if (*end == '\0' && res >= max_res * INT64_C(100) && res <= min_res * INT64_C(100)) hires_res = res; } @@ -134,8 +151,8 @@ void mp_raw_time_init(void) } else if (!strcmp(v, "never")) { hires_max = 0; } else { // "always" or unknown value + mp_start_hires_timers(hires_res); hires_max = 0; - timeBeginPeriod(hires_res); } #endif } diff --git a/osdep/timer.c b/osdep/timer.c index d0a8a92..907ba50 100644 --- a/osdep/timer.c +++ b/osdep/timer.c @@ -46,7 +46,12 @@ void mp_time_init(void) int64_t mp_time_ns(void) { - return mp_raw_time_ns() - raw_time_offset; + return mp_time_ns_from_raw_time(mp_raw_time_ns()); +} + +int64_t mp_time_ns_from_raw_time(uint64_t raw_time) +{ + return raw_time - raw_time_offset; } double mp_time_sec(void) diff --git a/osdep/timer.h b/osdep/timer.h index 3a925ca..5fedbb6 100644 --- a/osdep/timer.h +++ b/osdep/timer.h @@ -19,13 +19,17 @@ #define MPLAYER_TIMER_H #include <inttypes.h> +#include "config.h" // Initialize timer, must be called at least once at start. void mp_time_init(void); -// Return time in nanoseconds. Never wraps. Never returns 0 or negative values. +// Return time in nanoseconds. Never wraps. Never returns negative values. int64_t mp_time_ns(void); +// Return time in nanoseconds. Coverts raw time in nanoseconds to mp time, subtracts init offset. +int64_t mp_time_ns_from_raw_time(uint64_t raw_time); + // Return time in seconds. Can have down to 1 nanosecond resolution, but will // be much worse when casted to float. double mp_time_sec(void); @@ -38,12 +42,17 @@ uint64_t mp_raw_time_ns(void); // Sleep in nanoseconds. void mp_sleep_ns(int64_t ns); +#if HAVE_DARWIN +// Coverts mach time to raw time in nanoseconds and returns it. +uint64_t mp_raw_time_ns_from_mach(uint64_t mach_time); +#endif + #ifdef _WIN32 -// returns: timer resolution in ms if needed and started successfully, else 0 -int mp_start_hires_timers(int wait_ms); +// returns: timer resolution in ns if needed and started successfully, else 0 +int64_t mp_start_hires_timers(int64_t wait_ns); // call unconditionally with the return value of mp_start_hires_timers -void mp_end_hires_timers(int resolution_ms); +void mp_end_hires_timers(int64_t resolution_ns); #endif /* _WIN32 */ // Converts time units to nanoseconds (int64_t) diff --git a/osdep/apple_utils.c b/osdep/utils-mac.c index 02cdfaa..6acddfb 100644 --- a/osdep/apple_utils.c +++ b/osdep/utils-mac.c @@ -17,7 +17,7 @@ * License along with mpv. If not, see <http://www.gnu.org/licenses/>. */ -#include "apple_utils.h" +#include "utils-mac.h" #include "mpv_talloc.h" diff --git a/osdep/apple_utils.h b/osdep/utils-mac.h index 166937e..b324825 100644 --- a/osdep/apple_utils.h +++ b/osdep/utils-mac.h @@ -17,12 +17,12 @@ * License along with mpv. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef MPV_APPLE_UTILS -#define MPV_APPLE_UTILS +#ifndef UTILS_MAC +#define UTILS_MAC #include <CoreFoundation/CoreFoundation.h> CFStringRef cfstr_from_cstr(const char *str); char *cfstr_get_cstr(const CFStringRef cfstr); -#endif /* MPV_APPLE_UTILS */ +#endif /* UTILS_MAC */ diff --git a/osdep/w32_keyboard.c b/osdep/w32_keyboard.c index 52221e6..57988ec 100644 --- a/osdep/w32_keyboard.c +++ b/osdep/w32_keyboard.c @@ -93,6 +93,8 @@ static const struct keymap appcmd_map[] = { {APPCOMMAND_LAUNCH_MAIL, MP_KEY_MAIL}, {APPCOMMAND_BROWSER_FAVORITES, MP_KEY_FAVORITES}, {APPCOMMAND_BROWSER_SEARCH, MP_KEY_SEARCH}, + {APPCOMMAND_BROWSER_BACKWARD, MP_KEY_GO_BACK}, + {APPCOMMAND_BROWSER_FORWARD, MP_KEY_GO_FORWARD}, {0, 0} }; diff --git a/osdep/win32-console-wrapper.c b/osdep/win32-console-wrapper.c index 4e74dac..a579163 100644 --- a/osdep/win32-console-wrapper.c +++ b/osdep/win32-console-wrapper.c @@ -19,6 +19,9 @@ #include <stdio.h> #include <windows.h> +// copied from osdep/io.h since this file is standalone +#define MP_PATH_MAX (32000) + int wmain(int argc, wchar_t **argv, wchar_t **envp); static void cr_perror(const wchar_t *prefix) @@ -32,32 +35,37 @@ static void cr_perror(const wchar_t *prefix) MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&error, 0, NULL); - fwprintf(stderr, L"%s: %s", prefix, error); + fwprintf(stderr, L"%ls: %ls", prefix, error); LocalFree(error); } static int cr_runproc(wchar_t *name, wchar_t *cmdline) { - STARTUPINFOW si; - STARTUPINFOW our_si; - PROCESS_INFORMATION pi; DWORD retval = 1; - ZeroMemory(&si, sizeof(si)); - si.cb = sizeof(si); - si.hStdInput = GetStdHandle(STD_INPUT_HANDLE); - si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); - si.hStdError = GetStdHandle(STD_ERROR_HANDLE); - si.dwFlags |= STARTF_USESTDHANDLES; - // Copy the list of inherited CRT file descriptors to the new process - our_si.cb = sizeof(our_si); + STARTUPINFOW our_si = {sizeof(our_si)}; GetStartupInfoW(&our_si); - si.lpReserved2 = our_si.lpReserved2; - si.cbReserved2 = our_si.cbReserved2; - - ZeroMemory(&pi, sizeof(pi)); + // Don't redirect std streams if they are attached to a console. Let mpv + // attach to the console directly in this case. In theory, it should work + // out of the box because "console-like" handles should be managed by Windows + // internally, which works for INPUT and OUTPUT, but in certain cases, + // not for ERROR. + DWORD mode; + HANDLE hStdInput = GetStdHandle(STD_INPUT_HANDLE); + HANDLE hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); + HANDLE hStdError = GetStdHandle(STD_ERROR_HANDLE); + STARTUPINFOW si = { + .cb = sizeof(si), + .lpReserved2 = our_si.lpReserved2, + .cbReserved2 = our_si.cbReserved2, + .hStdInput = GetConsoleMode(hStdInput, &mode) ? NULL : hStdInput, + .hStdOutput = GetConsoleMode(hStdOutput, &mode) ? NULL : hStdOutput, + .hStdError = GetConsoleMode(hStdError, &mode) ? NULL : hStdError, + }; + si.dwFlags = (si.hStdInput || si.hStdOutput || si.hStdError) ? STARTF_USESTDHANDLES : 0; + PROCESS_INFORMATION pi = {0}; if (!CreateProcessW(name, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) { @@ -75,10 +83,11 @@ static int cr_runproc(wchar_t *name, wchar_t *cmdline) int wmain(int argc, wchar_t **argv, wchar_t **envp) { wchar_t *cmd; - wchar_t exe[MAX_PATH]; + wchar_t *exe; cmd = GetCommandLineW(); - GetModuleFileNameW(NULL, exe, MAX_PATH); + exe = LocalAlloc(LPTR, MP_PATH_MAX * sizeof(wchar_t)); + GetModuleFileNameW(NULL, exe, MP_PATH_MAX); wcscpy(wcsrchr(exe, '.') + 1, L"exe"); // Set an environment variable so the child process can tell whether it diff --git a/osdep/windows_utils.c b/osdep/windows_utils.c index 8cedf93..91eef62 100644 --- a/osdep/windows_utils.c +++ b/osdep/windows_utils.c @@ -24,9 +24,12 @@ #include <audioclient.h> #include <d3d9.h> #include <dxgi1_2.h> +#include <ole2.h> +#include <shobjidl.h> #include "common/common.h" #include "windows_utils.h" +#include "mpv_talloc.h" char *mp_GUID_to_str_buf(char *buf, size_t buf_size, const GUID *guid) { @@ -227,3 +230,22 @@ error: *server = *client = INVALID_HANDLE_VALUE; return false; } + +wchar_t *mp_w32_get_shell_link_target(wchar_t *path) +{ + IShellLink *psl = NULL; + IPersistFile *ppf = NULL; + wchar_t *buf = talloc_array(NULL, wchar_t, MAX_PATH + 1); + + if (FAILED(CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, &IID_IShellLinkW, (void**)&psl)) || + FAILED(IShellLinkW_QueryInterface(psl, &IID_IPersistFile, (void**)&ppf)) || + FAILED(IPersistFile_Load(ppf, path, STGM_READ)) || + FAILED(IShellLinkW_GetPath(psl, buf, MAX_PATH, NULL, 0))) + { + TA_FREEP(&buf); + } + + SAFE_RELEASE(psl); + SAFE_RELEASE(ppf); + return buf; +} diff --git a/osdep/windows_utils.h b/osdep/windows_utils.h index a8a5e94..c3fc72a 100644 --- a/osdep/windows_utils.h +++ b/osdep/windows_utils.h @@ -46,4 +46,8 @@ struct w32_create_anon_pipe_opts { bool mp_w32_create_anon_pipe(HANDLE *server, HANDLE *client, struct w32_create_anon_pipe_opts *opts); +// Returns the target of the shell link in talloc memory if the path is a +// resolvable shell link, otherwise returns NULL. +wchar_t *mp_w32_get_shell_link_target(wchar_t *path); + #endif diff --git a/player/audio.c b/player/audio.c index ca17d33..da91dd4 100644 --- a/player/audio.c +++ b/player/audio.c @@ -175,6 +175,7 @@ void audio_update_volume(struct MPContext *mpctx) float gain = MPMAX(opts->softvol_volume / 100.0, 0); gain = pow(gain, 3); gain *= compute_replaygain(mpctx); + gain *= db_gain(opts->softvol_gain); if (opts->softvol_mute == 1) gain = 0.0; @@ -617,7 +618,7 @@ double playing_audio_pts(struct MPContext *mpctx) double pts = written_audio_pts(mpctx); if (pts == MP_NOPTS_VALUE || !mpctx->ao) return pts; - return pts - mpctx->audio_speed * ao_get_delay(mpctx->ao); + return pts - ao_get_delay(mpctx->ao); } // This garbage is needed for untimed AOs. These consume audio infinitely fast, @@ -828,7 +829,8 @@ void audio_start_ao(struct MPContext *mpctx) double pts = MP_NOPTS_VALUE; if (!get_sync_pts(mpctx, &pts)) return; - double apts = playing_audio_pts(mpctx); // (basically including mpctx->delay) + double apts = written_audio_pts(mpctx); + apts -= apts != MP_NOPTS_VALUE ? mpctx->audio_speed * ao_get_delay(mpctx->ao) : 0; if (pts != MP_NOPTS_VALUE && apts != MP_NOPTS_VALUE && pts < apts && mpctx->video_status != STATUS_EOF) { @@ -844,6 +846,7 @@ void audio_start_ao(struct MPContext *mpctx) } MP_VERBOSE(mpctx, "starting audio playback\n"); + ao_c->audio_started = true; ao_start(ao_c->ao); mpctx->audio_status = STATUS_PLAYING; if (ao_c->out_eof) { diff --git a/player/client.c b/player/client.c index b35f20a..5087f89 100644 --- a/player/client.c +++ b/player/client.c @@ -844,7 +844,7 @@ int mp_client_send_event_dup(struct MPContext *mpctx, const char *client_name, return mp_client_send_event(mpctx, client_name, 0, event, event_data.data); } -const static bool deprecated_events[] = { +static const bool deprecated_events[] = { [MPV_EVENT_IDLE] = true, [MPV_EVENT_TICK] = true, }; diff --git a/player/command.c b/player/command.c index 8bff0cd..ff5ca35 100644 --- a/player/command.c +++ b/player/command.c @@ -55,7 +55,9 @@ #include "options/m_option.h" #include "options/m_property.h" #include "options/m_config_frontend.h" +#include "options/parse_configfile.h" #include "osdep/getpid.h" +#include "video/out/gpu/context.h" #include "video/out/vo.h" #include "video/csputils.h" #include "video/hwdec.h" @@ -72,6 +74,7 @@ #include "osdep/io.h" #include "osdep/subprocess.h" +#include "osdep/terminal.h" #include "core.h" @@ -91,6 +94,8 @@ struct command_ctx { char **warned_deprecated; int num_warned_deprecated; + bool command_opts_processed; + struct overlay *overlays; int num_overlays; // One of these is in use by the OSD; the other one exists so that the @@ -109,9 +114,9 @@ struct command_ctx { char **script_props; mpv_node udata; + mpv_node mdata; double cached_window_scale; - bool shared_script_warning; }; static const struct m_option script_props_type = { @@ -122,9 +127,14 @@ static const struct m_option udata_type = { .type = CONF_TYPE_NODE }; +static const struct m_option mdata_type = { + .type = CONF_TYPE_NODE +}; + struct overlay { struct mp_image *source; int x, y; + int dw, dh; }; struct hook_handler { @@ -137,12 +147,27 @@ struct hook_handler { bool active; // hook is currently in progress (only 1 at a time for now) }; -// U+279C HEAVY ROUND-TIPPED RIGHTWARDS ARROW +enum load_action_type { + LOAD_TYPE_REPLACE, + LOAD_TYPE_INSERT_AT, + LOAD_TYPE_INSERT_NEXT, + LOAD_TYPE_APPEND, +}; + +struct load_action { + enum load_action_type type; + bool play; +}; + +// U+25CB WHITE CIRCLE +// U+25CF BLACK CIRCLE // U+00A0 NO-BREAK SPACE -#define ARROW_SP "\342\236\234\302\240" +#define WHITECIRCLE "\xe2\x97\x8b" +#define BLACKCIRCLE "\xe2\x97\x8f" +#define NBSP "\xc2\xa0" -const char list_current[] = OSD_ASS_0 ARROW_SP OSD_ASS_1; -const char list_normal[] = OSD_ASS_0 "{\\alpha&HFF}" ARROW_SP "{\\r}" OSD_ASS_1; +const char list_current[] = BLACKCIRCLE NBSP; +const char list_normal[] = WHITECIRCLE NBSP; static int edit_filters(struct MPContext *mpctx, struct mp_log *log, enum stream_type mediatype, @@ -399,9 +424,9 @@ static int mp_property_playback_speed(void *ctx, struct m_property *prop, int action, void *arg) { MPContext *mpctx = ctx; - if (action == M_PROPERTY_PRINT) { - double speed = mpctx->opts->playback_speed; - *(char **)arg = talloc_asprintf(NULL, "%.2f", speed); + if (action == M_PROPERTY_PRINT || action == M_PROPERTY_FIXED_LEN_PRINT) { + *(char **)arg = mp_format_double(NULL, mpctx->opts->playback_speed, 2, + false, false, action != M_PROPERTY_FIXED_LEN_PRINT); return M_PROPERTY_OK; } return mp_property_generic_option(mpctx, prop, action, arg); @@ -419,8 +444,9 @@ static int mp_property_av_speed_correction(void *ctx, struct m_property *prop, default: MP_ASSERT_UNREACHABLE(); } - if (action == M_PROPERTY_PRINT) { - *(char **)arg = talloc_asprintf(NULL, "%+.3g%%", (val - 1) * 100); + if (action == M_PROPERTY_PRINT || action == M_PROPERTY_FIXED_LEN_PRINT) { + *(char **)arg = mp_format_double(NULL, (val - 1) * 100, 2, true, + true, action != M_PROPERTY_FIXED_LEN_PRINT); return M_PROPERTY_OK; } @@ -652,13 +678,9 @@ static int mp_property_avsync(void *ctx, struct m_property *prop, MPContext *mpctx = ctx; if (!mpctx->ao_chain || !mpctx->vo_chain) return M_PROPERTY_UNAVAILABLE; - if (action == M_PROPERTY_PRINT) { - // Truncate anything < 1e-4 to avoid switching to scientific notation - if (fabs(mpctx->last_av_difference) < 1e-4) { - *(char **)arg = talloc_strdup(NULL, "0"); - } else { - *(char **)arg = talloc_asprintf(NULL, "%+.2g", mpctx->last_av_difference); - } + if (action == M_PROPERTY_PRINT || action == M_PROPERTY_FIXED_LEN_PRINT) { + *(char **)arg = mp_format_double(NULL, mpctx->last_av_difference, 4, + true, false, action != M_PROPERTY_FIXED_LEN_PRINT); return M_PROPERTY_OK; } return m_property_double_ro(action, arg, mpctx->last_av_difference); @@ -1355,6 +1377,18 @@ static int mp_property_core_idle(void *ctx, struct m_property *prop, return m_property_bool_ro(action, arg, !mpctx->playback_active); } +static int mp_property_deinterlace(void *ctx, struct m_property *prop, + int action, void *arg) +{ + MPContext *mpctx = ctx; + struct vo_chain *vo_c = mpctx->vo_chain; + if (!vo_c) + return M_PROPERTY_UNAVAILABLE; + + bool deinterlace_active = mp_output_chain_deinterlace_active(vo_c->filter); + return m_property_bool_ro(action, arg, deinterlace_active); +} + static int mp_property_idle(void *ctx, struct m_property *prop, int action, void *arg) { @@ -1624,6 +1658,28 @@ static int mp_property_volume(void *ctx, struct m_property *prop, return mp_property_generic_option(mpctx, prop, action, arg); } +static int mp_property_volume_gain(void *ctx, struct m_property *prop, + int action, void *arg) +{ + MPContext *mpctx = ctx; + struct MPOpts *opts = mpctx->opts; + + switch (action) { + case M_PROPERTY_GET_CONSTRICTED_TYPE: + *(struct m_option *)arg = (struct m_option){ + .type = CONF_TYPE_FLOAT, + .min = opts->softvol_gain_min, + .max = opts->softvol_gain_max, + }; + return M_PROPERTY_OK; + case M_PROPERTY_PRINT: + *(char **)arg = talloc_asprintf(NULL, "%.1f", opts->softvol_gain); + return M_PROPERTY_OK; + } + + return mp_property_generic_option(mpctx, prop, action, arg); +} + static int mp_property_ao_volume(void *ctx, struct m_property *prop, int action, void *arg) { @@ -1758,8 +1814,7 @@ static int mp_property_audio_devices(void *ctx, struct m_property *prop, static int mp_property_ao(void *ctx, struct m_property *p, int action, void *arg) { MPContext *mpctx = ctx; - return m_property_strdup_ro(action, arg, - mpctx->ao ? ao_get_name(mpctx->ao) : NULL); + return m_property_strdup_ro(action, arg, mpctx->ao ? ao_get_name(mpctx->ao) : NULL); } /// Audio delay (RW) @@ -1774,28 +1829,6 @@ static int mp_property_audio_delay(void *ctx, struct m_property *prop, return mp_property_generic_option(mpctx, prop, action, arg); } -/// Audio codec tag (RO) -static int mp_property_audio_codec_name(void *ctx, struct m_property *prop, - int action, void *arg) -{ - MPContext *mpctx = ctx; - struct track *track = mpctx->current_track[0][STREAM_AUDIO]; - const char *c = track && track->stream ? track->stream->codec->codec : NULL; - return m_property_strdup_ro(action, arg, c); -} - -/// Audio codec name (RO) -static int mp_property_audio_codec(void *ctx, struct m_property *prop, - int action, void *arg) -{ - MPContext *mpctx = ctx; - struct track *track = mpctx->current_track[0][STREAM_AUDIO]; - char desc[256] = ""; - if (track && track->dec) - mp_decoder_wrapper_get_desc(track->dec, desc, sizeof(desc)); - return m_property_strdup_ro(action, arg, desc[0] ? desc : NULL); -} - static int property_audiofmt(struct mp_aframe *fmt, int action, void *arg) { if (!fmt || !mp_aframe_config_is_valid(fmt)) @@ -2000,6 +2033,10 @@ static int get_track_entry(int item, int action, void *arg, void *ctx) .unavailable = !decoder_desc[0]}, {"codec", SUB_PROP_STR(p.codec), .unavailable = !p.codec}, + {"codec-desc", SUB_PROP_STR(p.codec_desc), + .unavailable = !p.codec_desc}, + {"codec-profile", SUB_PROP_STR(p.codec_profile), + .unavailable = !p.codec_profile}, {"demux-w", SUB_PROP_INT(p.disp_w), .unavailable = !p.disp_w}, {"demux-h", SUB_PROP_INT(p.disp_h), .unavailable = !p.disp_h}, {"demux-crop-x",SUB_PROP_INT(p.crop.x0), .unavailable = !has_crop}, @@ -2016,6 +2053,7 @@ static int get_track_entry(int item, int action, void *arg, void *ctx) {"demux-bitrate", SUB_PROP_INT(p.bitrate), .unavailable = p.bitrate <= 0}, {"demux-rotation", SUB_PROP_INT(p.rotate), .unavailable = p.rotate <= 0}, {"demux-par", SUB_PROP_DOUBLE(par), .unavailable = par <= 0}, + {"format-name", SUB_PROP_STR(p.format_name), .unavailable = !p.format_name}, {"replaygain-track-peak", SUB_PROP_FLOAT(rg.track_peak), .unavailable = !has_rg}, {"replaygain-track-gain", SUB_PROP_FLOAT(rg.track_gain), @@ -2208,28 +2246,6 @@ static int mp_property_frame_count(void *ctx, struct m_property *prop, return m_property_int_ro(action, arg, frames); } -/// Video codec tag (RO) -static int mp_property_video_format(void *ctx, struct m_property *prop, - int action, void *arg) -{ - MPContext *mpctx = ctx; - struct track *track = mpctx->current_track[0][STREAM_VIDEO]; - const char *c = track && track->stream ? track->stream->codec->codec : NULL; - return m_property_strdup_ro(action, arg, c); -} - -/// Video codec name (RO) -static int mp_property_video_codec(void *ctx, struct m_property *prop, - int action, void *arg) -{ - MPContext *mpctx = ctx; - struct track *track = mpctx->current_track[0][STREAM_VIDEO]; - char desc[256] = ""; - if (track && track->dec) - mp_decoder_wrapper_get_desc(track->dec, desc, sizeof(desc)); - return m_property_strdup_ro(action, arg, desc[0] ? desc : NULL); -} - static const char *get_aspect_ratio_name(double ratio) { // Depending on cropping/mastering exact ratio may differ. @@ -2272,73 +2288,77 @@ static const char *get_aspect_ratio_name(double ratio) #undef RATIO_CASE } -static int property_imgparams(struct mp_image_params p, int action, void *arg) +static int property_imgparams(const struct mp_image_params *p, int action, void *arg) { - if (!p.imgfmt) + if (!p->imgfmt && !p->imgfmt_name) return M_PROPERTY_UNAVAILABLE; int d_w, d_h; - mp_image_params_get_dsize(&p, &d_w, &d_h); + mp_image_params_get_dsize(p, &d_w, &d_h); - struct mp_imgfmt_desc desc = mp_imgfmt_get_desc(p.imgfmt); int bpp = 0; - for (int i = 0; i < desc.num_planes; i++) - bpp += desc.bpp[i] >> (desc.xs[i] + desc.ys[i]); + enum pl_alpha_mode alpha = p->repr.alpha; + int fmt = p->hw_subfmt ? p->hw_subfmt : p->imgfmt; + if (fmt) { + struct mp_imgfmt_desc desc = mp_imgfmt_get_desc(fmt); + for (int i = 0; i < desc.num_planes; i++) + bpp += desc.bpp[i] >> (desc.xs[i] + desc.ys[i]); - // Alpha type is not supported by FFmpeg, so MP_ALPHA_AUTO may mean alpha - // is of an unknown type, or simply not present. Normalize to AUTO=no alpha. - if (!!(desc.flags & MP_IMGFLAG_ALPHA) != (p.alpha != MP_ALPHA_AUTO)) { - p.alpha = - (desc.flags & MP_IMGFLAG_ALPHA) ? MP_ALPHA_STRAIGHT : MP_ALPHA_AUTO; + // Alpha type is not supported by FFmpeg, so PL_ALPHA_UNKNOWN may mean alpha + // is of an unknown type, or simply not present. Normalize to AUTO=no alpha. + if (!!(desc.flags & MP_IMGFLAG_ALPHA) != (alpha != PL_ALPHA_UNKNOWN)) + alpha = (desc.flags & MP_IMGFLAG_ALPHA) ? PL_ALPHA_INDEPENDENT : PL_ALPHA_UNKNOWN; } - const struct pl_hdr_metadata *hdr = &p.color.hdr; + const struct pl_hdr_metadata *hdr = &p->color.hdr; bool has_cie_y = pl_hdr_metadata_contains(hdr, PL_HDR_METADATA_CIE_Y); bool has_hdr10 = pl_hdr_metadata_contains(hdr, PL_HDR_METADATA_HDR10); bool has_hdr10plus = pl_hdr_metadata_contains(hdr, PL_HDR_METADATA_HDR10PLUS); - bool has_crop = mp_rect_w(p.crop) > 0 && mp_rect_h(p.crop) > 0; + bool has_crop = mp_rect_w(p->crop) > 0 && mp_rect_h(p->crop) > 0; const char *aspect_name = get_aspect_ratio_name(d_w / (double)d_h); - const char *sar_name = get_aspect_ratio_name(p.w / (double)p.h); + const char *sar_name = get_aspect_ratio_name(p->w / (double)p->h); + const char *pixelformat_name = p->imgfmt_name ? p->imgfmt_name : + mp_imgfmt_to_name(p->imgfmt); struct m_sub_property props[] = { - {"pixelformat", SUB_PROP_STR(mp_imgfmt_to_name(p.imgfmt))}, - {"hw-pixelformat", SUB_PROP_STR(mp_imgfmt_to_name(p.hw_subfmt)), - .unavailable = !p.hw_subfmt}, + {"pixelformat", SUB_PROP_STR(pixelformat_name)}, + {"hw-pixelformat", SUB_PROP_STR(mp_imgfmt_to_name(p->hw_subfmt)), + .unavailable = !p->hw_subfmt}, {"average-bpp", SUB_PROP_INT(bpp), .unavailable = !bpp}, - {"w", SUB_PROP_INT(p.w)}, - {"h", SUB_PROP_INT(p.h)}, + {"w", SUB_PROP_INT(p->w)}, + {"h", SUB_PROP_INT(p->h)}, {"dw", SUB_PROP_INT(d_w)}, {"dh", SUB_PROP_INT(d_h)}, - {"crop-x", SUB_PROP_INT(p.crop.x0), .unavailable = !has_crop}, - {"crop-y", SUB_PROP_INT(p.crop.y0), .unavailable = !has_crop}, - {"crop-w", SUB_PROP_INT(mp_rect_w(p.crop)), .unavailable = !has_crop}, - {"crop-h", SUB_PROP_INT(mp_rect_h(p.crop)), .unavailable = !has_crop}, + {"crop-x", SUB_PROP_INT(p->crop.x0), .unavailable = !has_crop}, + {"crop-y", SUB_PROP_INT(p->crop.y0), .unavailable = !has_crop}, + {"crop-w", SUB_PROP_INT(mp_rect_w(p->crop)), .unavailable = !has_crop}, + {"crop-h", SUB_PROP_INT(mp_rect_h(p->crop)), .unavailable = !has_crop}, {"aspect", SUB_PROP_FLOAT(d_w / (double)d_h)}, {"aspect-name", SUB_PROP_STR(aspect_name), .unavailable = !aspect_name}, - {"par", SUB_PROP_FLOAT(p.p_w / (double)p.p_h)}, - {"sar", SUB_PROP_FLOAT(p.w / (double)p.h)}, + {"par", SUB_PROP_FLOAT(p->p_w / (double)p->p_h)}, + {"sar", SUB_PROP_FLOAT(p->w / (double)p->h)}, {"sar-name", SUB_PROP_STR(sar_name), .unavailable = !sar_name}, {"colormatrix", - SUB_PROP_STR(m_opt_choice_str(mp_csp_names, p.color.space))}, + SUB_PROP_STR(m_opt_choice_str(pl_csp_names, p->repr.sys))}, {"colorlevels", - SUB_PROP_STR(m_opt_choice_str(mp_csp_levels_names, p.color.levels))}, + SUB_PROP_STR(m_opt_choice_str(pl_csp_levels_names, p->repr.levels))}, {"primaries", - SUB_PROP_STR(m_opt_choice_str(mp_csp_prim_names, p.color.primaries))}, + SUB_PROP_STR(m_opt_choice_str(pl_csp_prim_names, p->color.primaries))}, {"gamma", - SUB_PROP_STR(m_opt_choice_str(mp_csp_trc_names, p.color.gamma))}, - {"sig-peak", SUB_PROP_FLOAT(p.color.hdr.max_luma / MP_REF_WHITE)}, + SUB_PROP_STR(m_opt_choice_str(pl_csp_trc_names, p->color.transfer))}, + {"sig-peak", SUB_PROP_FLOAT(p->color.hdr.max_luma / MP_REF_WHITE)}, {"light", - SUB_PROP_STR(m_opt_choice_str(mp_csp_light_names, p.color.light))}, + SUB_PROP_STR(m_opt_choice_str(mp_csp_light_names, p->light))}, {"chroma-location", - SUB_PROP_STR(m_opt_choice_str(mp_chroma_names, p.chroma_location))}, + SUB_PROP_STR(m_opt_choice_str(pl_chroma_names, p->chroma_location))}, {"stereo-in", - SUB_PROP_STR(m_opt_choice_str(mp_stereo3d_names, p.stereo3d))}, - {"rotate", SUB_PROP_INT(p.rotate)}, + SUB_PROP_STR(m_opt_choice_str(mp_stereo3d_names, p->stereo3d))}, + {"rotate", SUB_PROP_INT(p->rotate)}, {"alpha", - SUB_PROP_STR(m_opt_choice_str(mp_alpha_names, p.alpha)), + SUB_PROP_STR(m_opt_choice_str(pl_alpha_names, alpha)), // avoid using "auto" for "no", so just make it unavailable - .unavailable = p.alpha == MP_ALPHA_AUTO}, + .unavailable = alpha == PL_ALPHA_UNKNOWN}, {"min-luma", SUB_PROP_FLOAT(hdr->min_luma), .unavailable = !has_hdr10}, {"max-luma", SUB_PROP_FLOAT(hdr->max_luma), .unavailable = !has_hdr10}, {"max-cll", SUB_PROP_FLOAT(hdr->max_cll), .unavailable = !has_hdr10}, @@ -2384,7 +2404,24 @@ static int mp_property_vo_imgparams(void *ctx, struct m_property *prop, if (valid != M_PROPERTY_VALID) return valid; - return property_imgparams(vo_get_current_params(vo), action, arg); + struct mp_image_params p = vo_get_current_params(vo); + return property_imgparams(&p, action, arg); +} + +static int mp_property_tgt_imgparams(void *ctx, struct m_property *prop, + int action, void *arg) +{ + MPContext *mpctx = ctx; + struct vo *vo = mpctx->video_out; + if (!mpctx->video_out) + return M_PROPERTY_UNAVAILABLE; + + int valid = m_property_read_sub_validate(ctx, prop, action, arg); + if (valid != M_PROPERTY_VALID) + return valid; + + struct mp_image_params p = vo_get_target_params(vo); + return property_imgparams(&p, action, arg); } static int mp_property_dec_imgparams(void *ctx, struct m_property *prop, @@ -2403,7 +2440,7 @@ static int mp_property_dec_imgparams(void *ctx, struct m_property *prop, mp_decoder_wrapper_get_video_dec_params(vo_c->track->dec, &p); if (!p.imgfmt) return M_PROPERTY_UNAVAILABLE; - return property_imgparams(p, action, arg); + return property_imgparams(&p, action, arg); } static int mp_property_vd_imgparams(void *ctx, struct m_property *prop, @@ -2417,7 +2454,7 @@ static int mp_property_vd_imgparams(void *ctx, struct m_property *prop, struct mp_codec_params *c = track && track->stream ? track->stream->codec : NULL; if (vo_c->filter->input_params.imgfmt) { - return property_imgparams(vo_c->filter->input_params, action, arg); + return property_imgparams(&vo_c->filter->input_params, action, arg); } else if (c && c->disp_w && c->disp_h) { // Simplistic fallback for stupid scripts querying "width"/"height" // before the first frame is decoded. @@ -2591,6 +2628,26 @@ static int mp_property_hidpi_scale(void *ctx, struct m_property *prop, return m_property_double_ro(action, arg, cmd->cached_window_scale); } +static void update_hidpi_window_scale(struct MPContext *mpctx, bool hidpi_scale) +{ + struct command_ctx *cmd = mpctx->command_ctx; + struct vo *vo = mpctx->video_out; + if (!vo || cmd->cached_window_scale <= 0) + return; + + double scale = hidpi_scale ? cmd->cached_window_scale : 1 / cmd->cached_window_scale; + + int s[2]; + if (vo_control(vo, VOCTRL_GET_UNFS_WINDOW_SIZE, s) <= 0 || s[0] < 1 || s[1] < 1) + return; + + s[0] *= scale; + s[1] *= scale; + if (s[0] <= 0 || s[1] <= 0) + return; + vo_control(vo, VOCTRL_SET_UNFS_WINDOW_SIZE, s); +} + static int mp_property_focused(void *ctx, struct m_property *prop, int action, void *arg) { @@ -2739,8 +2796,15 @@ static int mp_property_perf_info(void *ctx, struct m_property *p, int action, static int mp_property_vo(void *ctx, struct m_property *p, int action, void *arg) { MPContext *mpctx = ctx; - return m_property_strdup_ro(action, arg, - mpctx->video_out ? mpctx->video_out->driver->name : NULL); + return m_property_strdup_ro(action, arg, mpctx->video_out ? + mpctx->video_out->driver->name : NULL); +} + +static int mp_property_gpu_context(void *ctx, struct m_property *p, int action, void *arg) +{ + MPContext *mpctx = ctx; + return m_property_strdup_ro(action, arg, mpctx->video_out ? + mpctx->video_out->context_name : NULL); } static int mp_property_osd_dim(void *ctx, struct m_property *prop, @@ -2790,6 +2854,23 @@ static int mp_property_osd_ass(void *ctx, struct m_property *prop, return m_property_read_sub(props, action, arg); } +static int mp_property_term_size(void *ctx, struct m_property *prop, + int action, void *arg) +{ + int w = -1, h = -1; + terminal_get_size(&w, &h); + if (w == -1 || h == -1) + return M_PROPERTY_UNAVAILABLE; + + struct m_sub_property props[] = { + {"w", SUB_PROP_INT(w)}, + {"h", SUB_PROP_INT(h)}, + {0} + }; + + return m_property_read_sub(props, action, arg); +} + static int mp_property_mouse_pos(void *ctx, struct m_property *prop, int action, void *arg) { @@ -2875,9 +2956,10 @@ static int mp_property_sub_delay(void *ctx, struct m_property *prop, { MPContext *mpctx = ctx; struct MPOpts *opts = mpctx->opts; + int track_ind = *(int *)prop->priv; switch (action) { case M_PROPERTY_PRINT: - *(char **)arg = format_delay(opts->subs_rend->sub_delay); + *(char **)arg = format_delay(opts->subs_shared->sub_delay[track_ind]); return M_PROPERTY_OK; } return mp_property_generic_option(mpctx, prop, action, arg); @@ -2902,8 +2984,9 @@ static int mp_property_sub_pos(void *ctx, struct m_property *prop, { MPContext *mpctx = ctx; struct MPOpts *opts = mpctx->opts; + int track_ind = *(int *)prop->priv; if (action == M_PROPERTY_PRINT) { - *(char **)arg = talloc_asprintf(NULL, "%4.2f%%/100", opts->subs_rend->sub_pos); + *(char **)arg = talloc_asprintf(NULL, "%4.2f%%/100", opts->subs_shared->sub_pos[track_ind]); return M_PROPERTY_OK; } return mp_property_generic_option(mpctx, prop, action, arg); @@ -2932,11 +3015,14 @@ static int mp_property_sub_ass_extradata(void *ctx, struct m_property *prop, return M_PROPERTY_NOT_IMPLEMENTED; } -static int get_sub_text(void *ctx, struct m_property *prop, - int action, void *arg, int sub_index) +static int mp_property_sub_text(void *ctx, struct m_property *prop, + int action, void *arg) { - int type = *(int *)prop->priv; MPContext *mpctx = ctx; + const int *def = prop->priv; + int sub_index = def[0]; + int type = def[1]; + struct track *track = mpctx->current_track[sub_index][STREAM_SUB]; struct dec_sub *sub = track ? track->d_sub : NULL; double pts = mpctx->playback_pts; @@ -2958,18 +3044,6 @@ static int get_sub_text(void *ctx, struct m_property *prop, return M_PROPERTY_NOT_IMPLEMENTED; } -static int mp_property_sub_text(void *ctx, struct m_property *prop, - int action, void *arg) -{ - return get_sub_text(ctx, prop, action, arg, 0); -} - -static int mp_property_secondary_sub_text(void *ctx, struct m_property *prop, - int action, void *arg) -{ - return get_sub_text(ctx, prop, action, arg, 1); -} - static struct sd_times get_times(void *ctx, struct m_property *prop, int action, void *arg) { @@ -3239,7 +3313,7 @@ static int mp_property_packet_bitrate(void *ctx, struct m_property *prop, if (rate < 1000) { *(char **)arg = talloc_asprintf(NULL, "%d kbps", (int)rate); } else { - *(char **)arg = talloc_asprintf(NULL, "%.3f mbps", rate / 1000.0); + *(char **)arg = talloc_asprintf(NULL, "%.3f Mbps", rate / 1000.0); } return M_PROPERTY_OK; } @@ -3624,29 +3698,32 @@ static int mp_property_bindings(void *ctx, struct m_property *prop, return M_PROPERTY_NOT_IMPLEMENTED; } - -static int mp_property_script_props(void *ctx, struct m_property *prop, - int action, void *arg) +static int mp_property_mdata(void *ctx, struct m_property *prop, + int action, void *arg) { MPContext *mpctx = ctx; - struct command_ctx *cmd = mpctx->command_ctx; - if (!cmd->shared_script_warning) { - MP_WARN(mpctx, "The shared-script-properties property is deprecated and will " - "be removed in the future. Use the user-data property instead.\n"); - cmd->shared_script_warning = true; - } + mpv_node *node = &mpctx->command_ctx->mdata; + switch (action) { case M_PROPERTY_GET_TYPE: - *(struct m_option *)arg = script_props_type; + *(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_NODE}; return M_PROPERTY_OK; case M_PROPERTY_GET: - m_option_copy(&script_props_type, arg, &cmd->script_props); + case M_PROPERTY_GET_NODE: + m_option_copy(&mdata_type, arg, node); return M_PROPERTY_OK; case M_PROPERTY_SET: - m_option_copy(&script_props_type, &cmd->script_props, arg); + case M_PROPERTY_SET_NODE: { + m_option_copy(&mdata_type, node, arg); + talloc_steal(mpctx->command_ctx, node_get_alloc(node)); mp_notify_property(mpctx, prop->name); + + struct vo *vo = mpctx->video_out; + if (vo) + vo_control(vo, VOCTRL_UPDATE_MENU, arg); return M_PROPERTY_OK; } + } return M_PROPERTY_NOT_IMPLEMENTED; } @@ -3673,8 +3750,9 @@ static int do_op_udata(struct udata_ctx* ctx, int action, void *arg) assert(node); m_option_copy(&udata_type, arg, node); return M_PROPERTY_OK; + case M_PROPERTY_FIXED_LEN_PRINT: case M_PROPERTY_PRINT: { - char *str = m_option_pretty_print(&udata_type, node); + char *str = m_option_pretty_print(&udata_type, node, action == M_PROPERTY_FIXED_LEN_PRINT); *(char **)arg = str; return str != NULL; } @@ -3781,7 +3859,7 @@ static int do_list_udata(int item, int action, void *arg, void *ctx) { struct udata_ctx nctx = *(struct udata_ctx*)ctx; nctx.node = &nctx.node->u.list->values[item]; - nctx.ta_parent = &nctx.node->u.list; + nctx.ta_parent = nctx.node->u.list; return do_op_udata(&nctx, action, arg); } @@ -3807,7 +3885,7 @@ static int mp_property_udata(void *ctx, struct m_property *prop, .mpctx = mpctx, .path = path, .node = &mpctx->command_ctx->udata, - .ta_parent = &mpctx->command_ctx, + .ta_parent = mpctx->command_ctx, }; int ret = do_op_udata(&nctx, action, arg); @@ -3885,6 +3963,7 @@ static const struct m_property mp_properties_base[] = { {"clock", mp_property_clock}, {"seekable", mp_property_seekable}, {"partially-seekable", mp_property_partially_seekable}, + {"deinterlace-active", mp_property_deinterlace}, {"idle-active", mp_property_idle}, {"window-id", mp_property_window_id}, @@ -3904,11 +3983,12 @@ static const struct m_property mp_properties_base[] = { // Audio {"mixer-active", mp_property_mixer_active}, {"volume", mp_property_volume}, + {"volume-gain", mp_property_volume_gain}, {"ao-volume", mp_property_ao_volume}, {"ao-mute", mp_property_ao_mute}, {"audio-delay", mp_property_audio_delay}, - {"audio-codec-name", mp_property_audio_codec_name}, - {"audio-codec", mp_property_audio_codec}, + M_PROPERTY_ALIAS("audio-codec-name", "current-tracks/audio/codec"), + M_PROPERTY_ALIAS("audio-codec", "current-tracks/audio/codec-desc"), {"audio-params", mp_property_audio_params}, {"audio-out-params", mp_property_audio_out_params}, {"aid", property_switch_track, .priv = (void *)(const int[]){0, STREAM_AUDIO}}, @@ -3917,12 +3997,13 @@ static const struct m_property mp_properties_base[] = { {"current-ao", mp_property_ao}, // Video + {"video-target-params", mp_property_tgt_imgparams}, {"video-out-params", mp_property_vo_imgparams}, {"video-dec-params", mp_property_dec_imgparams}, {"video-params", mp_property_vd_imgparams}, - {"video-format", mp_property_video_format}, {"video-frame-info", mp_property_video_frame_info}, - {"video-codec", mp_property_video_codec}, + M_PROPERTY_ALIAS("video-format", "current-tracks/video/codec"), + M_PROPERTY_ALIAS("video-codec", "current-tracks/video/codec-desc"), M_PROPERTY_ALIAS("dwidth", "video-out-params/dw"), M_PROPERTY_ALIAS("dheight", "video-out-params/dh"), M_PROPERTY_ALIAS("width", "video-params/w"), @@ -3932,6 +4013,7 @@ static const struct m_property mp_properties_base[] = { {"vo-passes", mp_property_vo_passes}, {"perf-info", mp_property_perf_info}, {"current-vo", mp_property_vo}, + {"current-gpu-context", mp_property_gpu_context}, {"container-fps", mp_property_fps}, {"estimated-vf-fps", mp_property_vf_fps}, {"video-aspect-override", mp_property_video_aspect_override}, @@ -3956,16 +4038,20 @@ static const struct m_property mp_properties_base[] = { {"sid", property_switch_track, .priv = (void *)(const int[]){0, STREAM_SUB}}, {"secondary-sid", property_switch_track, .priv = (void *)(const int[]){1, STREAM_SUB}}, - {"sub-delay", mp_property_sub_delay}, + {"sub-delay", mp_property_sub_delay, .priv = (void *)&(const int){0}}, + {"secondary-sub-delay", mp_property_sub_delay, + .priv = (void *)&(const int){1}}, {"sub-speed", mp_property_sub_speed}, - {"sub-pos", mp_property_sub_pos}, + {"sub-pos", mp_property_sub_pos, .priv = (void *)&(const int){0}}, + {"secondary-sub-pos", mp_property_sub_pos, + .priv = (void *)&(const int){1}}, {"sub-ass-extradata", mp_property_sub_ass_extradata}, {"sub-text", mp_property_sub_text, - .priv = (void *)&(const int){SD_TEXT_TYPE_PLAIN}}, - {"secondary-sub-text", mp_property_secondary_sub_text, - .priv = (void *)&(const int){SD_TEXT_TYPE_PLAIN}}, + .priv = (void *)&(const int[]){0, SD_TEXT_TYPE_PLAIN}}, + {"secondary-sub-text", mp_property_sub_text, + .priv = (void *)&(const int[]){1, SD_TEXT_TYPE_PLAIN}}, {"sub-text-ass", mp_property_sub_text, - .priv = (void *)&(const int){SD_TEXT_TYPE_ASS}}, + .priv = (void *)&(const int[]){0, SD_TEXT_TYPE_ASS}}, {"sub-start", mp_property_sub_start, .priv = (void *)&(const int){0}}, {"secondary-sub-start", mp_property_sub_start, @@ -4020,8 +4106,10 @@ static const struct m_property mp_properties_base[] = { {"command-list", mp_property_commands}, {"input-bindings", mp_property_bindings}, - {"shared-script-properties", mp_property_script_props}, + {"menu-data", mp_property_mdata}, + {"user-data", mp_property_udata}, + {"term-size", mp_property_term_size}, M_PROPERTY_ALIAS("video", "vid"), M_PROPERTY_ALIAS("audio", "aid"), @@ -4054,17 +4142,18 @@ static const char *const *const mp_event_property_change[] = { "secondary-sub-text", "audio-bitrate", "video-bitrate", "sub-bitrate", "decoder-frame-drop-count", "frame-drop-count", "video-frame-info", "vf-metadata", "af-metadata", "sub-start", "sub-end", "secondary-sub-start", - "secondary-sub-end", "video-out-params", "video-dec-params", "video-params"), + "secondary-sub-end", "video-out-params", "video-dec-params", "video-params", + "deinterlace-active", "video-target-params"), E(MP_EVENT_DURATION_UPDATE, "duration"), E(MPV_EVENT_VIDEO_RECONFIG, "video-out-params", "video-params", "video-format", "video-codec", "video-bitrate", "dwidth", "dheight", "width", "height", "container-fps", "aspect", "aspect-name", "vo-configured", "current-vo", - "video-dec-params", "osd-dimensions", - "hwdec", "hwdec-current", "hwdec-interop"), + "video-dec-params", "osd-dimensions", "hwdec", "hwdec-current", "hwdec-interop", + "window-id", "track-list", "current-tracks"), E(MPV_EVENT_AUDIO_RECONFIG, "audio-format", "audio-codec", "audio-bitrate", - "samplerate", "channels", "audio", "volume", "mute", - "current-ao", "audio-codec-name", "audio-params", - "audio-out-params", "volume-max", "mixer-active"), + "samplerate", "channels", "audio", "volume", "volume-gain", "mute", + "current-ao", "audio-codec-name", "audio-params", "track-list", "current-tracks", + "audio-out-params", "volume-max", "volume-gain-min", "volume-gain-max", "mixer-active"), E(MPV_EVENT_SEEK, "seeking", "core-idle", "eof-reached"), E(MPV_EVENT_PLAYBACK_RESTART, "seeking", "core-idle", "eof-reached"), E(MP_EVENT_METADATA_UPDATE, "metadata", "filtered-metadata", "media-title"), @@ -4247,6 +4336,9 @@ static const struct property_osd_display { {"volume", "Volume", .msg = "Volume: ${?volume:${volume}% ${?mute==yes:(Muted)}}${!volume:${volume}}", .osd_progbar = OSD_VOLUME, .marker = 100}, + {"volume-gain", "Volume gain", + .msg = "Volume gain: ${?volume-gain:${volume-gain} dB ${?mute==yes:(Muted)}}${!volume-gain:${volume-gain}}", + .osd_progbar = OSD_VOLUME, .marker = 0}, {"ao-volume", "AO Volume", .msg = "AO Volume: ${?ao-volume:${ao-volume}% ${?ao-mute==yes:(Muted)}}${!ao-volume:${ao-volume}}", .osd_progbar = OSD_VOLUME, .marker = 100}, @@ -4273,7 +4365,9 @@ static const struct property_osd_display { {"sub", "Subtitles"}, {"secondary-sid", "Secondary subtitles"}, {"sub-pos", "Sub position"}, + {"secondary-sub-pos", "Secondary sub position"}, {"sub-delay", "Sub delay"}, + {"secondary-sub-delay", "Secondary sub delay"}, {"sub-speed", "Sub speed"}, {"sub-visibility", .msg = "Subtitles ${!sub-visibility==yes:hidden}" @@ -4285,6 +4379,7 @@ static const struct property_osd_display { {"sub-scale", "Sub Scale"}, {"sub-ass-vsfilter-aspect-compat", "Subtitle VSFilter aspect compat"}, {"sub-ass-override", "ASS subtitle style override"}, + {"secondary-sub-ass-override", "Secondary sub ASS subtitle style override"}, {"vf", "Video filters", .msg = "Video filters:\n${vf}"}, {"af", "Audio filters", .msg = "Audio filters:\n${af}"}, {"ab-loop-a", "A-B loop start"}, @@ -4474,8 +4569,8 @@ static void recreate_overlays(struct MPContext *mpctx) struct sub_bitmap b = { .bitmap = s->planes[0], .stride = s->stride[0], - .w = s->w, .dw = s->w, - .h = s->h, .dh = s->h, + .w = s->w, .dw = o->dw, + .h = s->h, .dh = o->dh, .x = o->x, .y = o->y, }; @@ -4572,7 +4667,12 @@ static void cmd_overlay_add(void *pcmd) int offset = cmd->args[4].v.i; char *fmt = cmd->args[5].v.s; int w = cmd->args[6].v.i, h = cmd->args[7].v.i, stride = cmd->args[8].v.i; + int dw = cmd->args[9].v.i, dh = cmd->args[10].v.i; + if (dw <= 0) + dw = w; + if (dh <= 0) + dh = h; if (strcmp(fmt, "bgra") != 0) { MP_ERR(mpctx, "overlay-add: unsupported OSD format '%s'\n", fmt); goto error; @@ -4589,6 +4689,8 @@ static void cmd_overlay_add(void *pcmd) .source = mp_image_alloc(IMGFMT_BGRA, w, h), .x = x, .y = y, + .dw = dw, + .dh = dh, }; if (!overlay.source) goto error; @@ -5408,15 +5510,22 @@ static void cmd_sub_step_seek(void *p) a[1] = cmd->args[0].v.i; if (sub_control(sub, SD_CTRL_SUB_STEP, a) > 0) { if (step) { - mpctx->opts->subs_rend->sub_delay -= a[0] - refpts; + mpctx->opts->subs_shared->sub_delay[track_ind] -= a[0] - refpts; m_config_notify_change_opt_ptr_notify(mpctx->mconfig, - &mpctx->opts->subs_rend->sub_delay); - show_property_osd(mpctx, "sub-delay", cmd->on_osd); + &mpctx->opts->subs_shared->sub_delay[track_ind]); + show_property_osd( + mpctx, + track_ind == 0 ? "sub-delay" : "secondary-sub-delay", + cmd->on_osd); } else { // We can easily seek/step to the wrong subtitle line (because - // video frame PTS and sub PTS rarely match exactly). Add an - // arbitrary forward offset as a workaround. - a[0] += SUB_SEEK_OFFSET; + // video frame PTS and sub PTS rarely match exactly). + // sub/sd_ass.c adds SUB_SEEK_OFFSET as a workaround, and we + // need an even bigger offset without a video. + if (!mpctx->current_track[0][STREAM_VIDEO] || + mpctx->current_track[0][STREAM_VIDEO]->image) { + a[0] += SUB_SEEK_WITHOUT_VIDEO_OFFSET - SUB_SEEK_OFFSET; + } mark_seek(mpctx); queue_seek(mpctx, MPSEEK_ABSOLUTE, a[0], MPSEEK_EXACT, MPSEEK_FLAG_DELAY); @@ -5472,29 +5581,84 @@ static void cmd_expand_path(void *p) }; } +static void cmd_escape_ass(void *p) +{ + struct mp_cmd_ctx *cmd = p; + bstr dst = {0}; + + osd_mangle_ass(&dst, cmd->args[0].v.s, true); + + cmd->result = (mpv_node){ + .format = MPV_FORMAT_STRING, + .u.string = dst.len ? (char *)dst.start : talloc_strdup(NULL, ""), + }; +} + +static struct load_action get_load_action(struct MPContext *mpctx, int action_flag) +{ + switch (action_flag) { + case 0: // replace + return (struct load_action){LOAD_TYPE_REPLACE, .play = true}; + case 1: // append + return (struct load_action){LOAD_TYPE_APPEND, .play = false}; + case 2: // append-play + return (struct load_action){LOAD_TYPE_APPEND, .play = true}; + case 3: // insert-next + return (struct load_action){LOAD_TYPE_INSERT_NEXT, .play = false}; + case 4: // insert-next-play + return (struct load_action){LOAD_TYPE_INSERT_NEXT, .play = true}; + case 5: // insert-at + return (struct load_action){LOAD_TYPE_INSERT_AT, .play = false}; + case 6: // insert-at-play + return (struct load_action){LOAD_TYPE_INSERT_AT, .play = true}; + default: // default: replace + return (struct load_action){LOAD_TYPE_REPLACE, .play = true}; + } +} + +static struct playlist_entry *get_insert_entry(struct MPContext *mpctx, struct load_action *action, + int insert_at_idx) +{ + switch (action->type) { + case LOAD_TYPE_INSERT_NEXT: + return playlist_get_next(mpctx->playlist, +1); + case LOAD_TYPE_INSERT_AT: + return playlist_entry_from_index(mpctx->playlist, insert_at_idx); + case LOAD_TYPE_REPLACE: + case LOAD_TYPE_APPEND: + default: + return NULL; + } +} + static void cmd_loadfile(void *p) { struct mp_cmd_ctx *cmd = p; struct MPContext *mpctx = cmd->mpctx; char *filename = cmd->args[0].v.s; - int append = cmd->args[1].v.i; + int action_flag = cmd->args[1].v.i; + int insert_at_idx = cmd->args[2].v.i; + + struct load_action action = get_load_action(mpctx, action_flag); - if (!append) + if (action.type == LOAD_TYPE_REPLACE) playlist_clear(mpctx->playlist); struct playlist_entry *entry = playlist_entry_new(filename); - if (cmd->args[2].v.str_list) { - char **pairs = cmd->args[2].v.str_list; + if (cmd->args[3].v.str_list) { + char **pairs = cmd->args[3].v.str_list; for (int i = 0; pairs[i] && pairs[i + 1]; i += 2) playlist_entry_add_param(entry, bstr0(pairs[i]), bstr0(pairs[i + 1])); } - playlist_add(mpctx->playlist, entry); + + struct playlist_entry *at = get_insert_entry(mpctx, &action, insert_at_idx); + playlist_insert_at(mpctx->playlist, entry, at); struct mpv_node *res = &cmd->result; node_init(res, MPV_FORMAT_NODE_MAP, NULL); node_map_add_int64(res, "playlist_entry_id", entry->id); - if (!append || (append == 2 && !mpctx->playlist->current)) { + if (action.type == LOAD_TYPE_REPLACE || (action.play && !mpctx->playlist->current)) { if (mpctx->opts->position_save_on_quit) // requested in issue #1148 mp_write_watch_later_conf(mpctx); mp_set_playlist_entry(mpctx, entry); @@ -5508,25 +5672,37 @@ static void cmd_loadlist(void *p) struct mp_cmd_ctx *cmd = p; struct MPContext *mpctx = cmd->mpctx; char *filename = cmd->args[0].v.s; - int append = cmd->args[1].v.i; + int action_flag = cmd->args[1].v.i; + int insert_at_idx = cmd->args[2].v.i; + + struct load_action action = get_load_action(mpctx, action_flag); struct playlist *pl = playlist_parse_file(filename, cmd->abort->cancel, mpctx->global); if (pl) { prepare_playlist(mpctx, pl); struct playlist_entry *new = pl->current; - if (!append) + if (action.type == LOAD_TYPE_REPLACE) playlist_clear(mpctx->playlist); struct playlist_entry *first = playlist_entry_from_index(pl, 0); int num_entries = pl->num_entries; - playlist_append_entries(mpctx->playlist, pl); + + struct playlist_entry *at = get_insert_entry(mpctx, &action, insert_at_idx); + if (at == NULL) { + playlist_append_entries(mpctx->playlist, pl); + } else { + int at_index = playlist_entry_to_index(mpctx->playlist, at); + playlist_transfer_entries_to(mpctx->playlist, at_index, pl); + } talloc_free(pl); if (!new) new = playlist_get_first(mpctx->playlist); - if ((!append || (append == 2 && !mpctx->playlist->current)) && new) + if ((action.type == LOAD_TYPE_REPLACE || + (action.play && !mpctx->playlist->current)) && new) { mp_set_playlist_entry(mpctx, new); + } struct mpv_node *res = &cmd->result; node_init(res, MPV_FORMAT_NODE_MAP, NULL); @@ -5752,6 +5928,10 @@ static void cmd_track_reload(void *p) } struct track *nt = mpctx->tracks[nt_num]; + + if (!nt->lang) + nt->lang = mp_guess_lang_from_filename(nt, nt->external_filename); + mp_switch_track(mpctx, nt->type, nt, 0); print_track_list(mpctx, "Reloaded:"); } @@ -5787,7 +5967,7 @@ static void cmd_run(void *p) char **args = talloc_zero_array(NULL, char *, cmd->num_args + 1); for (int n = 0; n < cmd->num_args; n++) args[n] = cmd->args[n].v.s; - mp_msg_flush_status_line(mpctx->log); + mp_msg_flush_status_line(mpctx->log, true); struct mp_subprocess_opts opts = { .exe = args[0], .args = args, @@ -6176,7 +6356,7 @@ static void cmd_mouse(void *p) if (button == -1) {// no button if (pre_key) - mp_input_put_key_artificial(mpctx->input, pre_key); + mp_input_put_key_artificial(mpctx->input, pre_key, 1); mp_input_set_mouse_pos_artificial(mpctx->input, x, y); return; } @@ -6194,9 +6374,9 @@ static void cmd_mouse(void *p) } button += dbc ? MP_MBTN_DBL_BASE : MP_MBTN_BASE; if (pre_key) - mp_input_put_key_artificial(mpctx->input, pre_key); + mp_input_put_key_artificial(mpctx->input, pre_key, 1); mp_input_set_mouse_pos_artificial(mpctx->input, x, y); - mp_input_put_key_artificial(mpctx->input, button); + mp_input_put_key_artificial(mpctx->input, button, 1); } static void cmd_key(void *p) @@ -6207,7 +6387,7 @@ static void cmd_key(void *p) const char *key_name = cmd->args[0].v.s; if (key_name[0] == '\0' && action == MP_KEY_STATE_UP) { - mp_input_put_key_artificial(mpctx->input, MP_INPUT_RELEASE_ALL); + mp_input_put_key_artificial(mpctx->input, MP_INPUT_RELEASE_ALL, 1); } else { int code = mp_input_get_key_from_name(key_name); if (code < 0) { @@ -6215,7 +6395,8 @@ static void cmd_key(void *p) cmd->success = false; return; } - mp_input_put_key_artificial(mpctx->input, code | action); + double scale = action == 0 ? cmd->args[1].v.d : 1; + mp_input_put_key_artificial(mpctx->input, code | action, scale); } } @@ -6248,6 +6429,32 @@ static void cmd_apply_profile(void *p) } } +static void cmd_load_config_file(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; + + char *config_file = cmd->args[0].v.s; + int r = m_config_parse_config_file(mpctx->mconfig, mpctx->global, + config_file, NULL, 0); + + if (r < 1) { + cmd->success = false; + return; + } + + mp_notify_property(mpctx, "profile-list"); +} + +static void cmd_load_input_conf(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; + + char *config_file = cmd->args[0].v.s; + cmd->success = mp_input_load_config_file(mpctx->input, config_file); +} + static void cmd_load_script(void *p) { struct mp_cmd_ctx *cmd = p; @@ -6349,6 +6556,26 @@ static void cmd_dump_cache_ab(void *p) cmd->args[0].v.s); } +static void cmd_begin_vo_dragging(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; + struct vo *vo = mpctx->video_out; + + if (vo) + vo_control(vo, VOCTRL_BEGIN_DRAGGING, NULL); +} + +static void cmd_context_menu(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; + struct vo *vo = mpctx->video_out; + + if (vo) + vo_control(vo, VOCTRL_SHOW_MENU, NULL); +} + /* This array defines all known commands. * The first field the command name used in libmpv and input.conf. * The second field is the handler function (see mp_cmd_def.handler and @@ -6474,6 +6701,8 @@ const struct mp_cmd_def mp_cmds[] = { .is_noisy = true }, { "expand-path", cmd_expand_path, { {"text", OPT_STRING(v.s)} }, .is_noisy = true }, + { "escape-ass", cmd_escape_ass, { {"text", OPT_STRING(v.s)} }, + .is_noisy = true }, { "show-progress", cmd_show_progress, .allow_auto_repeat = true, .is_noisy = true }, @@ -6600,8 +6829,13 @@ const struct mp_cmd_def mp_cmds[] = { {"flags", OPT_CHOICE(v.i, {"replace", 0}, {"append", 1}, - {"append-play", 2}), + {"append-play", 2}, + {"insert-next", 3}, + {"insert-next-play", 4}, + {"insert-at", 5}, + {"insert-at-play", 6}), .flags = MP_CMD_OPT_ARG}, + {"index", OPT_INT(v.i), OPTDEF_INT(-1)}, {"options", OPT_KEYVALUELIST(v.str_list), .flags = MP_CMD_OPT_ARG}, }, }, @@ -6611,8 +6845,13 @@ const struct mp_cmd_def mp_cmds[] = { {"flags", OPT_CHOICE(v.i, {"replace", 0}, {"append", 1}, - {"append-play", 2}), + {"append-play", 2}, + {"insert-next", 3}, + {"insert-next-play", 4}, + {"insert-at", 5}, + {"insert-at-play", 6}), .flags = MP_CMD_OPT_ARG}, + {"index", OPT_INT(v.i), OPTDEF_INT(-1)}, }, .spawn_thread = true, .can_abort = true, @@ -6740,7 +6979,9 @@ const struct mp_cmd_def mp_cmds[] = { {"fmt", OPT_STRING(v.s)}, {"w", OPT_INT(v.i)}, {"h", OPT_INT(v.i)}, - {"stride", OPT_INT(v.i)}, }}, + {"stride", OPT_INT(v.i)}, + {"dw", OPT_INT(v.i), OPTDEF_INT(0)}, + {"dh", OPT_INT(v.i), OPTDEF_INT(0)}, }}, { "overlay-remove", cmd_overlay_remove, { {"id", OPT_INT(v.i)} } }, { "osd-overlay", cmd_osd_overlay, @@ -6769,7 +7010,8 @@ const struct mp_cmd_def mp_cmds[] = { .flags = MP_CMD_OPT_ARG}}}, { "keybind", cmd_key_bind, { {"name", OPT_STRING(v.s)}, {"cmd", OPT_STRING(v.s)} }}, - { "keypress", cmd_key, { {"name", OPT_STRING(v.s)} }, + { "keypress", cmd_key, { {"name", OPT_STRING(v.s)}, + {"scale", OPT_DOUBLE(v.d), OPTDEF_DOUBLE(1)} }, .priv = &(const int){0}}, { "keydown", cmd_key, { {"name", OPT_STRING(v.s)} }, .priv = &(const int){MP_KEY_STATE_DOWN}}, @@ -6782,6 +7024,10 @@ const struct mp_cmd_def mp_cmds[] = { .flags = MP_CMD_OPT_ARG}, } }, + { "load-config-file", cmd_load_config_file, {{"filename", OPT_STRING(v.s)}} }, + + { "load-input-conf", cmd_load_input_conf, {{"filename", OPT_STRING(v.s)}} }, + { "load-script", cmd_load_script, {{"filename", OPT_STRING(v.s)}} }, { "dump-cache", cmd_dump_cache, { {"start", OPT_TIME(v.d), @@ -6800,6 +7046,10 @@ const struct mp_cmd_def mp_cmds[] = { { "ab-loop-align-cache", cmd_align_cache_ab }, + { "begin-vo-dragging", cmd_begin_vo_dragging }, + + { "context-menu", cmd_context_menu }, + {0} }; @@ -6821,6 +7071,11 @@ void command_uninit(struct MPContext *mpctx) mpctx->command_ctx = NULL; } +static int str_compare(const void *a, const void *b) +{ + return strcmp(*(const char **)a, *(const char **)b); +} + void command_init(struct MPContext *mpctx) { struct command_ctx *ctx = talloc(NULL, struct command_ctx); @@ -6835,6 +7090,11 @@ void command_init(struct MPContext *mpctx) talloc_zero_array(ctx, struct m_property, num_base + num_opts + 1); memcpy(ctx->properties, mp_properties_base, sizeof(mp_properties_base)); + const char **prop_names = talloc_array(NULL, const char *, num_base); + for (int i = 0; i < num_base; ++i) + prop_names[i] = mp_properties_base[i].name; + qsort(prop_names, num_base, sizeof(const char *), str_compare); + int count = num_base; for (int n = 0; n < num_opts; n++) { struct m_config_option *co = m_config_get_co_index(mpctx->mconfig, n); @@ -6868,14 +7128,18 @@ void command_init(struct MPContext *mpctx) } // The option might be covered by a manual property already. - if (m_property_list_find(ctx->properties, prop.name)) + if (bsearch(&prop.name, prop_names, num_base, sizeof(const char *), str_compare)) continue; ctx->properties[count++] = prop; } + node_init(&ctx->mdata, MPV_FORMAT_NODE_ARRAY, NULL); + talloc_steal(ctx, ctx->mdata.u.list); + node_init(&ctx->udata, MPV_FORMAT_NODE_MAP, NULL); talloc_steal(ctx, ctx->udata.u.list); + talloc_free(prop_names); } static void command_event(struct MPContext *mpctx, int event, void *arg) @@ -6891,6 +7155,9 @@ static void command_event(struct MPContext *mpctx, int event, void *arg) if (event == MPV_EVENT_PLAYBACK_RESTART) ctx->last_seek_time = mp_time_sec(); + if (event == MPV_EVENT_END_FILE) + mp_msg_flush_status_line(mpctx->log, false); + if (event == MPV_EVENT_END_FILE || event == MPV_EVENT_FILE_LOADED) { // Update chapters - does nothing if something else is visible. set_osd_bar_chapters(mpctx, OSD_BAR_SEEK); @@ -6921,6 +7188,27 @@ void handle_command_updates(struct MPContext *mpctx) // Depends on polling demuxer wakeup callback notifications. cache_dump_poll(mpctx); + + // Potentially run the commands now (idle) instead of waiting for a file to load. + if (mpctx->stop_play == PT_STOP) + run_command_opts(mpctx); +} + +void run_command_opts(struct MPContext *mpctx) +{ + struct MPOpts *opts = mpctx->opts; + struct command_ctx *ctx = mpctx->command_ctx; + + if (!opts->input_commands || ctx->command_opts_processed) + return; + + // Take easy way out and add these to the input queue. + for (int i = 0; opts->input_commands[i]; i++) { + struct mp_cmd *cmd = mp_input_parse_cmd(mpctx->input, bstr0(opts->input_commands[i]), + "the command line"); + mp_input_queue_cmd(mpctx->input, cmd); + } + ctx->command_opts_processed = true; } void mp_notify(struct MPContext *mpctx, int event, void *arg) @@ -6987,8 +7275,11 @@ void mp_option_change_callback(void *ctx, struct m_config_option *co, int flags, if (sub) { int ret = sub_control(sub, SD_CTRL_UPDATE_OPTS, (void *)(uintptr_t)flags); - if (ret == CONTROL_OK && flags & (UPDATE_SUB_FILT | UPDATE_SUB_HARD)) + if (ret == CONTROL_OK && flags & (UPDATE_SUB_FILT | UPDATE_SUB_HARD)) { sub_redecode_cached_packets(sub); + if (track->selected) + reselect_demux_stream(mpctx, track, true); + } } } osd_changed(mpctx->osd); @@ -7016,13 +7307,15 @@ void mp_option_change_callback(void *ctx, struct m_config_option *co, int flags, mpctx->ipc_ctx = mp_init_ipc(mpctx->clients, mpctx->global); } - if (opt_ptr == &opts->vo->video_driver_list) { + if (opt_ptr == &opts->vo->video_driver_list || + opt_ptr == &opts->ra_ctx_opts->context_name || + opt_ptr == &opts->ra_ctx_opts->context_type) { struct track *track = mpctx->current_track[0][STREAM_VIDEO]; uninit_video_out(mpctx); handle_force_window(mpctx, true); reinit_video_chain(mpctx); if (track) - reselect_demux_stream(mpctx, track, true); + queue_seek(mpctx, MPSEEK_RELATIVE, 0.0, MPSEEK_EXACT, 0); mp_wakeup_core(mpctx); } @@ -7042,11 +7335,23 @@ void mp_option_change_callback(void *ctx, struct m_config_option *co, int flags, if (flags & UPDATE_LAVFI_COMPLEX) update_lavfi_complex(mpctx); + if (flags & UPDATE_VIDEO) { + if (mpctx->video_out) { + vo_control(mpctx->video_out, VOCTRL_UPDATE_RENDER_OPTS, NULL); + mp_wakeup_core(mpctx); + } + } + if (opt_ptr == &opts->vo->android_surface_size) { if (mpctx->video_out) vo_control(mpctx->video_out, VOCTRL_EXTERNAL_RESIZE, NULL); } + if (opt_ptr == &opts->input_commands) { + mpctx->command_ctx->command_opts_processed = false; + run_command_opts(mpctx); + } + if (opt_ptr == &opts->playback_speed) { update_playback_speed(mpctx); mp_wakeup_core(mpctx); @@ -7103,6 +7408,9 @@ void mp_option_change_callback(void *ctx, struct m_config_option *co, int flags, if (opt_ptr == &opts->vo->window_scale) update_window_scale(mpctx); + if (opt_ptr == &opts->vo->hidpi_window_scale) + update_hidpi_window_scale(mpctx, opts->vo->hidpi_window_scale); + if (opt_ptr == &opts->cursor_autohide_delay) mpctx->mouse_timer = 0; diff --git a/player/command.h b/player/command.h index 185b78f..31e3b32 100644 --- a/player/command.h +++ b/player/command.h @@ -70,6 +70,7 @@ void run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mp_abort_entry *abort, void (*on_completion)(struct mp_cmd_ctx *cmd), void *on_completion_priv); +void run_command_opts(struct MPContext *mpctx); void mp_cmd_ctx_complete(struct mp_cmd_ctx *cmd); PRINTF_ATTRIBUTE(3, 4) void mp_cmd_msg(struct mp_cmd_ctx *cmd, int status, const char *msg, ...); diff --git a/player/configfiles.c b/player/configfiles.c index 9441638..2b94308 100644 --- a/player/configfiles.c +++ b/player/configfiles.c @@ -251,6 +251,9 @@ static bool needs_config_quoting(const char *s) static void write_filename(struct MPContext *mpctx, FILE *file, char *filename) { + if (mpctx->opts->ignore_path_in_watch_later_config && !mp_is_url(bstr0(filename))) + filename = mp_basename(filename); + if (mpctx->opts->write_filename_in_watch_later_config) { char write_name[1024] = {0}; for (int n = 0; filename[n] && n < sizeof(write_name) - 1; n++) @@ -280,7 +283,7 @@ static void write_redirect(struct MPContext *mpctx, char *path) static void write_redirects_for_parent_dirs(struct MPContext *mpctx, char *path) { - if (mp_is_url(bstr0(path))) + if (mp_is_url(bstr0(path)) || mpctx->opts->ignore_path_in_watch_later_config) return; // Write redirect entries for the file's parent directories to allow @@ -403,7 +406,7 @@ void mp_delete_watch_later_conf(struct MPContext *mpctx, const char *file) talloc_free(fname); } - if (mp_is_url(bstr0(file))) + if (mp_is_url(bstr0(file)) || mpctx->opts->ignore_path_in_watch_later_config) return; void *ctx = talloc_new(NULL); diff --git a/player/core.h b/player/core.h index 8a49585..c44868c 100644 --- a/player/core.h +++ b/player/core.h @@ -120,6 +120,7 @@ struct track { char *title; bool default_track, forced_track, dependent_track; bool visual_impaired_track, hearing_impaired_track; + bool forced_select; // if the track was selected because it is forced bool image; bool attached_picture; char *lang; @@ -131,6 +132,8 @@ struct track { char *external_filename; bool auto_loaded; + bool demuxer_ready; // if more packets should be read (subtitles only) + struct demuxer *demuxer; // Invariant: !stream || stream->demuxer == demuxer struct sh_stream *stream; @@ -191,6 +194,8 @@ struct ao_chain { double start_pts; bool start_pts_known; + bool audio_started; + struct track *track; struct mp_pin *filter_src; struct mp_pin *dec_src; @@ -402,6 +407,9 @@ typedef struct MPContext { int last_chapter_seek; bool last_chapter_flag; + /* Heuristic for potentially redrawing subs. */ + bool redraw_subs; + bool paused; // internal pause state bool playback_active; // not paused, restarting, loading, unloading bool in_playloop; @@ -621,6 +629,7 @@ void mp_load_builtin_scripts(struct MPContext *mpctx); int64_t mp_load_user_script(struct MPContext *mpctx, const char *fname); // sub.c +void redraw_subs(struct MPContext *mpctx); void reset_subtitle_state(struct MPContext *mpctx); void reinit_sub(struct MPContext *mpctx, struct track *track); void reinit_sub_all(struct MPContext *mpctx); diff --git a/player/external_files.c b/player/external_files.c index e9a6081..2e00912 100644 --- a/player/external_files.c +++ b/player/external_files.c @@ -142,6 +142,14 @@ static struct bstr guess_lang_from_filename(struct bstr name, int *fn_start) return (struct bstr){name.start + i + 1, n}; } +char *mp_guess_lang_from_filename(void* ctx, const char *filename) +{ + bstr filename_no_ext = bstr_strip_ext(bstr0(filename)); + int start = 0; // only used in append_dir_subtitles() + char *lang = bstrto0(ctx, guess_lang_from_filename(filename_no_ext, &start)); + return lang; +} + static void append_dir_subtitles(struct mpv_global *global, struct MPOpts *opts, struct subfn **slist, int *nsub, struct bstr path, const char *fname, diff --git a/player/external_files.h b/player/external_files.h index 20b37c3..5d42c55 100644 --- a/player/external_files.h +++ b/player/external_files.h @@ -34,5 +34,6 @@ struct subfn *find_external_files(struct mpv_global *global, const char *fname, bool mp_might_be_subtitle_file(const char *filename); void mp_update_subtitle_exts(struct MPOpts *opts); +char *mp_guess_lang_from_filename(void *talloc_ctx, const char *filename); #endif /* MPLAYER_FINDFILES_H */ diff --git a/player/javascript/defaults.js b/player/javascript/defaults.js index d906ec2..9f130c9 100644 --- a/player/javascript/defaults.js +++ b/player/javascript/defaults.js @@ -177,28 +177,6 @@ mp.abort_async_command = function abort_async_command(id) { mp._abort_async_command(id); } -// shared-script-properties - always an object, even if without properties -function shared_script_property_set(name, val) { - if (arguments.length > 1) - return mp.commandv("change-list", "shared-script-properties", "append", "" + name + "=" + val); - else - return mp.commandv("change-list", "shared-script-properties", "remove", name); -} - -function shared_script_property_get(name) { - return mp.get_property_native("shared-script-properties")[name]; -} - -function shared_script_property_observe(name, cb) { - return mp.observe_property("shared-script-properties", "native", - function shared_props_cb(_name, val) { cb(name, val[name]) } - ); -} - -mp.utils.shared_script_property_set = shared_script_property_set; -mp.utils.shared_script_property_get = shared_script_property_get; -mp.utils.shared_script_property_observe = shared_script_property_observe; - // osd-ass var next_assid = 1; mp.create_osd_overlay = function create_osd_overlay(format) { @@ -268,10 +246,10 @@ mp.get_osd_margins = function get_osd_margins() { // {cb: fn, forced: bool, maybe input: str, repeatable: bool, complex: bool} var binds = new_cache(); -function dispatch_key_binding(name, state, key_name) { +function dispatch_key_binding(name, state, key_name, key_text) { var cb = binds[name] ? binds[name].cb : false; if (cb) // "script-binding [<script_name>/]<name>" command was invoked - cb(state, key_name); + cb(state, key_name, key_text); } var binds_tid = 0; // flush timer id. actual id's are always true-thy @@ -329,11 +307,12 @@ function add_binding(forced, key, name, fn, opts) { fn({event: "press", is_mouse: false}); }); var KEY_STATES = { u: "up", d: "down", r: "repeat", p: "press" }; - key_data.cb = function key_cb(state, key_name) { + key_data.cb = function key_cb(state, key_name, key_text) { fn({ event: KEY_STATES[state[0]] || "unknown", is_mouse: state[1] == "m", - key_name: key_name || undefined + key_name: key_name || undefined, + key_text: key_text || undefined }); } } else { @@ -665,6 +644,56 @@ function read_options(opts, id, on_update, conf_override) { mp.options = { read_options: read_options }; /********************************************************************** +* input +*********************************************************************/ +mp.input = { + get: function(t) { + mp.commandv("script-message-to", "console", "get-input", mp.script_name, + JSON.stringify({ + prompt: t.prompt, + default_text: t.default_text, + cursor_position: t.cursor_position, + id: t.id, + })); + + mp.register_script_message("input-event", function (type, text, cursor_position) { + if (t[type]) { + var result = t[type](text, cursor_position); + + if (type == "complete" && result) { + mp.commandv("script-message-to", "console", "complete", + JSON.stringify(result[0]), result[1]); + } + } + + if (type == "closed") { + mp.unregister_script_message("input-event"); + } + }) + + return true; + }, + terminate: function () { + mp.commandv("script-message-to", "console", "disable"); + }, + log: function (message, style, terminal_style) { + mp.commandv("script-message-to", "console", "log", JSON.stringify({ + text: message, + style: style, + terminal_style: terminal_style, + })); + }, + log_error: function (message) { + mp.commandv("script-message-to", "console", "log", + JSON.stringify({ text: message, error: true })); + }, + set_log: function (log) { + mp.commandv("script-message-to", "console", "set-log", + JSON.stringify(log)); + } +} + +/********************************************************************** * various *********************************************************************/ g.print = mp.msg.info; // convenient alias diff --git a/player/loadfile.c b/player/loadfile.c index 1d25dc3..7421a47 100644 --- a/player/loadfile.c +++ b/player/loadfile.c @@ -278,6 +278,8 @@ static void print_stream(struct MPContext *mpctx, struct track *t) APPEND(b, " '%s'", t->title); const char *codec = s ? s->codec->codec : NULL; APPEND(b, " (%s", codec ? codec : "<unknown>"); + if (s && s->codec->codec_profile) + APPEND(b, " [%s]", s->codec->codec_profile); if (t->type == STREAM_VIDEO) { if (s && s->codec->disp_w) APPEND(b, " %dx%d", s->codec->disp_w, s->codec->disp_h); @@ -483,9 +485,10 @@ static int match_lang(char **langs, const char *lang) * Forced tracks are preferred when the user prefers not to display subtitles */ // Return whether t1 is preferred over t2 -static bool compare_track(struct track *t1, struct track *t2, char **langs, - bool os_langs, struct MPOpts *opts, int preferred_program) +static bool compare_track(struct track *t1, struct track *t2, char **langs, bool os_langs, + bool forced, struct MPOpts *opts, int preferred_program) { + bool sub = t2->type == STREAM_SUB; if (!opts->autoload_files && t1->is_external != t2->is_external) return !t1->is_external; bool ext1 = t1->is_external && !t1->no_default; @@ -503,16 +506,17 @@ static bool compare_track(struct track *t1, struct track *t2, char **langs, (t2->program_id == preferred_program)) return t1->program_id == preferred_program; } - int forced = t1->type == STREAM_SUB ? opts->subs_fallback_forced : 1; - bool force_match = forced == 1 || (t1->forced_track && forced == 2) || - (!t1->forced_track && !forced); int l1 = match_lang(langs, t1->lang), l2 = match_lang(langs, t2->lang); if (!os_langs && l1 != l2) - return l1 > l2 && force_match; - if (t1->default_track != t2->default_track) - return t1->default_track && force_match; + return l1 > l2; + if (forced) + return t1->forced_track; + if (sub && !t2->forced_select && t2->forced_track) + return !t1->forced_track; + if (t1->default_track != t2->default_track && !t2->forced_select) + return t1->default_track; if (os_langs && l1 != l2) - return l1 > l2 && force_match; + return l1 > l2; if (t1->attached_picture != t2->attached_picture) return !t1->attached_picture; if (t1->stream && t2->stream && opts->hls_bitrate >= 0 && @@ -624,11 +628,7 @@ struct track *select_default_track(struct MPContext *mpctx, int order, } const char *audio_lang = get_audio_lang(mpctx); bool sub = type == STREAM_SUB; - bool fallback_forced = sub && opts->subs_fallback_forced; - bool audio_matches = false; - bool sub_fallback = false; struct track *pick = NULL; - struct track *forced_pick = NULL; for (int n = 0; n < mpctx->num_tracks; n++) { struct track *track = mpctx->tracks[n]; if (track->type != type) @@ -643,45 +643,27 @@ struct track *select_default_track(struct MPContext *mpctx, int order, continue; if (duplicate_track(mpctx, order, type, track)) continue; - if (!pick || compare_track(track, pick, langs, os_langs, mpctx->opts, preferred_program)) + if (sub) { + // Subtitle specific auto-selecting crap. + bool audio_matches = mp_match_lang_single(audio_lang, track->lang); + bool forced = track->forced_track && (opts->subs_fallback_forced == 2 || + (audio_matches && opts->subs_fallback_forced == 1)); + bool lang_match = !os_langs && match_lang(langs, track->lang) > 0; + bool subs_fallback = (track->is_external && !track->no_default) || opts->subs_fallback == 2 || + (opts->subs_fallback == 1 && track->default_track); + bool subs_matching_audio = (!match_lang(langs, audio_lang) || opts->subs_with_matching_audio == 2 || + (opts->subs_with_matching_audio == 1 && track->forced_track)); + if (subs_matching_audio && ((!pick && (forced || lang_match || subs_fallback)) || + (pick && compare_track(track, pick, langs, os_langs, forced, mpctx->opts, preferred_program)))) + { + pick = track; + pick->forced_select = forced; + } + } else if (!pick || compare_track(track, pick, langs, os_langs, false, mpctx->opts, preferred_program)) { pick = track; - - // Autoselecting forced sub tracks requires the following: - // 1. Matches the audio language or --subs-fallback-forced=always. - // 2. Matches the users list of preferred languages or none were specified (i.e. slang was not set). - // 3. A track *wasn't* already selected by slang previously or the track->lang matches pick->lang and isn't forced. - bool valid_forced_slang = (os_langs || (mp_match_lang_single(pick->lang, track->lang) && !pick->forced_track) || - (match_lang(langs, track->lang) && !match_lang(langs, pick->lang))); - bool audio_lang_match = mp_match_lang_single(audio_lang, track->lang); - if (fallback_forced && track->forced_track && valid_forced_slang && audio_lang_match && - (!forced_pick || compare_track(track, forced_pick, langs, os_langs, mpctx->opts, preferred_program))) - { - forced_pick = track; } } - // If we found a forced track, use that. - if (forced_pick) - pick = forced_pick; - - // Clear out any picks for these special cases for subtitles - if (pick) { - audio_matches = mp_match_lang_single(pick->lang, audio_lang); - sub_fallback = (pick->is_external && !pick->no_default) || opts->subs_fallback == 2 || - (opts->subs_fallback == 1 && pick->default_track); - } - if (pick && !forced_pick && sub && (!match_lang(langs, pick->lang) || os_langs) && !sub_fallback) - pick = NULL; - // Handle this after matching langs and selecting a fallback. - if (pick && sub && (!opts->subs_with_matching_audio && audio_matches)) - pick = NULL; - // Handle edge cases if we picked a track that doesn't match the --subs-fallback-force value - if (pick && sub && ((!pick->forced_track && opts->subs_fallback_forced == 2) || - (pick->forced_track && !opts->subs_fallback_forced))) - { - pick = NULL; - } - if (pick && pick->attached_picture && !mpctx->opts->audio_display) pick = NULL; if (pick && !opts->autoload_files && pick->is_external) diff --git a/player/lua.c b/player/lua.c index 41fd520..6354769 100644 --- a/player/lua.c +++ b/player/lua.c @@ -61,6 +61,9 @@ static const char * const builtin_lua_scripts[][2] = { {"mp.assdraw", # include "player/lua/assdraw.lua.inc" }, + {"mp.input", +# include "player/lua/input.lua.inc" + }, {"mp.options", # include "player/lua/options.lua.inc" }, @@ -509,7 +512,7 @@ static int script_log(lua_State *L) const char *s = lua_tostring(L, -1); if (s == NULL) return luaL_error(L, "Invalid argument"); - mp_msg(ctx->log, msgl, "%s%s", s, i > 0 ? " " : ""); + mp_msg(ctx->log, msgl, (i == 2 ? "%s" : " %s"), s); lua_pop(L, 1); // args... tostring } mp_msg(ctx->log, msgl, "\n"); @@ -1184,11 +1187,11 @@ static int script_format_json(lua_State *L, void *tmp) char *dst = talloc_strdup(tmp, ""); if (json_write(&dst, &node) >= 0) { lua_pushstring(L, dst); - lua_pushnil(L); - } else { - lua_pushnil(L); - lua_pushstring(L, "error"); + return 1; } + + lua_pushnil(L); + lua_pushstring(L, "error"); return 2; } diff --git a/player/lua/auto_profiles.lua b/player/lua/auto_profiles.lua index 9dca878..a0f5802 100644 --- a/player/lua/auto_profiles.lua +++ b/player/lua/auto_profiles.lua @@ -164,8 +164,8 @@ local function compile_cond(name, s) return chunk end -local function load_profiles() - for i, v in ipairs(mp.get_property_native("profile-list")) do +local function load_profiles(profiles_property) + for _, v in ipairs(profiles_property) do local cond = v["profile-cond"] if cond and #cond > 0 then local profile = { @@ -182,17 +182,25 @@ local function load_profiles() end end -load_profiles() +mp.observe_property("profile-list", "native", function (_, profiles_property) + profiles = {} + watched_properties = {} + cached_properties = {} + properties_to_profiles = {} + mp.unobserve_property(on_property_change) -if #profiles < 1 and mp.get_property("load-auto-profiles") == "auto" then - -- make it exit immediately - _G.mp_event_loop = function() end - return -end + load_profiles(profiles_property) + + if #profiles < 1 and mp.get_property("load-auto-profiles") == "auto" then + -- make it exit immediately + _G.mp_event_loop = function() end + return + end + + on_idle() -- re-evaluate all profiles immediately +end) mp.register_idle(on_idle) for _, name in ipairs({"on_load", "on_preloaded", "on_before_start_file"}) do mp.add_hook(name, 50, on_hook) end - -on_idle() -- re-evaluate all profiles immediately diff --git a/player/lua/console.lua b/player/lua/console.lua index 44e9436..bbfaf47 100644 --- a/player/lua/console.lua +++ b/player/lua/console.lua @@ -27,11 +27,13 @@ local opts = { -- multiplied by "scale". font_size = 16, border_size = 1, + case_sensitive = true, -- Remove duplicate entries in history as to only keep the latest one. history_dedup = true, -- The ratio of font height to font width. -- Adjusts table width of completion suggestions. - font_hw_ratio = 2.0, + -- Values in the range 1.8..2.5 make sense for common monospace fonts. + font_hw_ratio = 'auto', } function detect_platform() @@ -48,6 +50,7 @@ end local platform = detect_platform() if platform == 'windows' then opts.font = 'Consolas' + opts.case_sensitive = false elseif platform == 'darwin' then opts.font = 'Menlo' else @@ -66,11 +69,21 @@ local styles = { -- cccc66 cc9966 cc99cc 537bd2 debug = '{\\1c&Ha09f93&}', - verbose = '{\\1c&H99cc99&}', + v = '{\\1c&H99cc99&}', warn = '{\\1c&H66ccff&}', error = '{\\1c&H7a77f2&}', fatal = '{\\1c&H5791f9&\\b1}', suggestion = '{\\1c&Hcc99cc&}', + selected_suggestion = '{\\1c&H2fbdfa&\\b1}', +} + +local terminal_styles = { + debug = '\027[1;30m', + v = '\027[32m', + warn = '\027[33m', + error = '\027[31m', + fatal = '\027[1;31m', + selected_suggestion = '\027[7m', } local repl_active = false @@ -78,15 +91,26 @@ local insert_mode = false local pending_update = false local line = '' local cursor = 1 -local history = {} +local default_prompt = '>' +local prompt = default_prompt +local default_id = 'default' +local id = default_id +local histories = {[id] = {}} +local history = histories[id] local history_pos = 1 -local log_buffer = {} -local suggestion_buffer = {} +local log_buffers = {[id] = {}} local key_bindings = {} local global_margins = { t = 0, b = 0 } +local input_caller +local suggestion_buffer = {} +local selected_suggestion_index +local completion_start_position +local completion_append local file_commands = {} local path_separator = platform == 'windows' and '\\' or '/' +local completion_old_line +local completion_old_cursor local update_timer = nil update_timer = mp.add_periodic_timer(0.05, function() @@ -107,9 +131,88 @@ mp.observe_property("user-data/osc/margins", "native", function(_, val) update() end) +do + local width_length_ratio = 0.5 + local osd_width, osd_height = 100, 100 + + ---Update osd resolution if valid + local function update_osd_resolution() + local dim = mp.get_property_native('osd-dimensions') + if not dim or dim.w == 0 or dim.h == 0 then + return + end + osd_width = dim.w + osd_height = dim.h + end + + local text_osd = mp.create_osd_overlay('ass-events') + text_osd.compute_bounds, text_osd.hidden = true, true + + local function measure_bounds(ass_text) + update_osd_resolution() + text_osd.res_x, text_osd.res_y = osd_width, osd_height + text_osd.data = ass_text + local res = text_osd:update() + return res.x0, res.y0, res.x1, res.y1 + end + + ---Measure text width and normalize to a font size of 1 + ---text has to be ass safe + local function normalized_text_width(text, size, horizontal) + local align, rotation = horizontal and 7 or 1, horizontal and 0 or -90 + local template = '{\\pos(0,0)\\rDefault\\blur0\\bord0\\shad0\\q2\\an%s\\fs%s\\fn%s\\frz%s}%s' + local x1, y1 = nil, nil + size = size / 0.8 + -- prevent endless loop + local repetitions_left = 5 + repeat + size = size * 0.8 + local ass = assdraw.ass_new() + ass.text = template:format(align, size, opts.font, rotation, text) + _, _, x1, y1 = measure_bounds(ass.text) + repetitions_left = repetitions_left - 1 + -- make sure nothing got clipped + until (x1 and x1 < osd_width and y1 < osd_height) or repetitions_left == 0 + local width = (repetitions_left == 0 and not x1) and 0 or (horizontal and x1 or y1) + return width / size, horizontal and osd_width or osd_height + end + + local function fit_on_osd(text) + local estimated_width = #text * width_length_ratio + if osd_width >= osd_height then + -- Fill the osd as much as possible, bigger is more accurate. + return math.min(osd_width / estimated_width, osd_height), true + else + return math.min(osd_height / estimated_width, osd_width), false + end + end + + local measured_font_hw_ratio = nil + function get_font_hw_ratio() + local font_hw_ratio = tonumber(opts.font_hw_ratio) + if font_hw_ratio then + return font_hw_ratio + end + if not measured_font_hw_ratio then + local alphabet = 'abcdefghijklmnopqrstuvwxyz' + local text = alphabet:rep(3) + update_osd_resolution() + local size, horizontal = fit_on_osd(text) + local normalized_width = normalized_text_width(text, size * 0.9, horizontal) + measured_font_hw_ratio = #text / normalized_width * 0.95 + end + return measured_font_hw_ratio + end +end + -- Add a line to the log buffer (which is limited to 100 lines) -function log_add(style, text) - log_buffer[#log_buffer + 1] = { style = style, text = text } +function log_add(text, style, terminal_style) + local log_buffer = log_buffers[id] + log_buffer[#log_buffer + 1] = { + text = text, + style = style or '', + terminal_style = terminal_style or '', + } if #log_buffer > 100 then table.remove(log_buffer, 1) end @@ -126,19 +229,7 @@ end -- Escape a string for verbatim display on the OSD function ass_escape(str) - -- There is no escape for '\' in ASS (I think?) but '\' is used verbatim if - -- it isn't followed by a recognised character, so add a zero-width - -- non-breaking space - str = str:gsub('\\', '\\\239\187\191') - str = str:gsub('{', '\\{') - str = str:gsub('}', '\\}') - -- Precede newlines with a ZWNBSP to prevent ASS's weird collapsing of - -- consecutive newlines - str = str:gsub('\n', '\239\187\191\\N') - -- Turn leading spaces into hard spaces to prevent ASS from stripping them - str = str:gsub('\\N ', '\\N\\h') - str = str:gsub('^ ', '\\h') - return str + return mp.command_native({'escape-ass', str}) end -- Takes a list of strings, a max width in characters and @@ -206,7 +297,9 @@ function format_table(list, width_max, rows_max) local spaces = math.floor((width_max - width_total) / (column_count - 1)) spaces = math.max(spaces_min, math.min(spaces_max, spaces)) - local spacing = column_count > 1 and string.format('%' .. spaces .. 's', ' ') or '' + local spacing = column_count > 1 + and ass_escape(string.format('%' .. spaces .. 's', ' ')) + or '' local rows = {} for row = 1, row_count do @@ -217,12 +310,17 @@ function format_table(list, width_max, rows_max) -- more then 99 leads to 'invalid format (width or precision too long)' local format_string = column == column_count and '%s' or '%-' .. math.min(column_widths[column], 99) .. 's' - columns[column] = string.format(format_string, list[i]) + columns[column] = ass_escape(string.format(format_string, list[i])) + + if i == selected_suggestion_index then + columns[column] = styles.selected_suggestion .. columns[column] + .. '{\\b0}'.. styles.suggestion + end end -- first row is at the bottom rows[row_count - row + 1] = table.concat(columns, spacing) end - return table.concat(rows, '\n'), row_count + return table.concat(rows, ass_escape('\n')), row_count end local function print_to_terminal() @@ -233,13 +331,19 @@ local function print_to_terminal() end local log = '' - for _, log_line in ipairs(log_buffer) do - log = log .. log_line.text + for _, log_line in ipairs(log_buffers[id]) do + log = log .. log_line.terminal_style .. log_line.text .. '\027[0m' end - local suggestions = table.concat(suggestion_buffer, '\t') - if suggestions ~= '' then - suggestions = suggestions .. '\n' + local suggestions = '' + for i, suggestion in ipairs(suggestion_buffer) do + if i == selected_suggestion_index then + suggestions = suggestions .. terminal_styles.selected_suggestion .. + suggestion .. '\027[0m' + else + suggestions = suggestions .. suggestion + end + suggestions = suggestions .. (i < #suggestion_buffer and '\t' or '\n') end local before_cur = line:sub(1, cursor - 1) @@ -249,22 +353,18 @@ local function print_to_terminal() after_cur = ' ' end - mp.osd_message(log .. suggestions .. '> ' .. before_cur .. '\027[7m' .. - after_cur:sub(1, 1) .. '\027[0m' .. after_cur:sub(2), 999) + mp.osd_message(log .. suggestions .. prompt .. ' ' .. before_cur .. + '\027[7m' .. after_cur:sub(1, 1) .. '\027[0m' .. + after_cur:sub(2), 999) end -- Render the REPL and console as an ASS OSD function update() pending_update = false - -- Print to the terminal when there is no VO. Check both vo-configured so - -- it works with --force-window --idle and no video tracks, and whether - -- there is a video track so that the condition doesn't become true while - -- switching VO at runtime, making mp.osd_message() print to the VO's OSD. - -- This issue does not happen when switching VO without any video track - -- regardless of the condition used. - if not mp.get_property_native('vo-configured') - and not mp.get_property('current-tracks/video') then + -- Unlike vo-configured, current-vo doesn't become falsy while switching VO, + -- which would print the log to the OSD. + if not mp.get_property('current-vo') then print_to_terminal() return end @@ -300,8 +400,9 @@ function update() -- thin as possible and make it appear to be 1px wide by giving it 0.5px -- horizontal borders. local cheight = opts.font_size * 8 - local cglyph = '{\\r' .. - '\\1a&H44&\\3a&H44&\\4a&H99&' .. + local cglyph = '{\\rDefault' .. + (mp.get_property_native('focused') == false + and '\\alpha&HFF&' or '\\1a&H44&\\3a&H44&\\4a&H99&') .. '\\1c&Heeeeee&\\3c&Heeeeee&\\4c&H000000&' .. '\\xbord0.5\\ybord0\\xshad0\\yshad1\\p4\\pbo24}' .. 'm 0 0 l 1 0 l 1 ' .. cheight .. ' l 0 ' .. cheight .. @@ -317,12 +418,13 @@ function update() local screeny_factor = (1 - global_margins.t - global_margins.b) local lines_max = math.ceil(screeny * screeny_factor / opts.font_size - 1.5) -- Estimate how many characters fit in one line - local width_max = math.ceil(screenx / opts.font_size * opts.font_hw_ratio) + local width_max = math.ceil(screenx / opts.font_size * get_font_hw_ratio()) local suggestions, rows = format_table(suggestion_buffer, width_max, lines_max) - local suggestion_ass = style .. styles.suggestion .. ass_escape(suggestions) + local suggestion_ass = style .. styles.suggestion .. suggestions local log_ass = '' + local log_buffer = log_buffers[id] local log_messages = #log_buffer local log_max_lines = math.max(0, lines_max - rows) if log_max_lines < log_messages then @@ -339,7 +441,7 @@ function update() if #suggestions > 0 then ass:append(suggestion_ass .. '\\N') end - ass:append(style .. '> ' .. before_cur) + ass:append(style .. ass_escape(prompt) .. ' ' .. before_cur) ass:append(cglyph) ass:append(style .. after_cur) @@ -348,7 +450,7 @@ function update() ass:new_event() ass:an(1) ass:pos(2, screeny - 2 - global_margins.b * screeny) - ass:append(style .. '{\\alpha&HFF&}> ' .. before_cur) + ass:append(style .. '{\\alpha&HFF&}' .. ass_escape(prompt) .. ' ' .. before_cur) ass:append(cglyph) ass:append(style .. '{\\alpha&HFF&}' .. after_cur) @@ -362,12 +464,28 @@ function set_active(active) repl_active = true insert_mode = false mp.enable_key_bindings('console-input', 'allow-hide-cursor+allow-vo-dragging') - mp.enable_messages('terminal-default') define_key_bindings() + + if not input_caller then + prompt = default_prompt + id = default_id + history = histories[id] + history_pos = #history + 1 + mp.enable_messages('terminal-default') + end else repl_active = false + suggestion_buffer = {} undefine_key_bindings() mp.enable_messages('silent:terminal-default') + + if input_caller then + mp.commandv('script-message-to', input_caller, 'input-event', + 'closed', line, cursor) + input_caller = nil + line = '' + cursor = 1 + end collectgarbage() end update() @@ -429,6 +547,16 @@ function len_utf8(str) return len end +local function handle_edit() + suggestion_buffer = {} + update() + + if input_caller then + mp.commandv('script-message-to', input_caller, 'input-event', 'edited', + line) + end +end + -- Insert a character at the current cursor position (any_unicode) function handle_char_input(c) if insert_mode then @@ -437,8 +565,7 @@ function handle_char_input(c) line = line:sub(1, cursor - 1) .. c .. line:sub(cursor) end cursor = cursor + #c - suggestion_buffer = {} - update() + handle_edit() end -- Remove the character behind the cursor (Backspace) @@ -447,16 +574,14 @@ function handle_backspace() local prev = prev_utf8(line, cursor) line = line:sub(1, prev - 1) .. line:sub(cursor) cursor = prev - suggestion_buffer = {} - update() + handle_edit() end -- Remove the character in front of the cursor (Del) function handle_del() if cursor > line:len() then return end line = line:sub(1, cursor - 1) .. line:sub(next_utf8(line, cursor)) - suggestion_buffer = {} - update() + handle_edit() end -- Toggle insert mode (Ins) @@ -467,12 +592,14 @@ end -- Move the cursor to the next character (Right) function next_char(amount) cursor = next_utf8(line, cursor) + suggestion_buffer = {} update() end -- Move the cursor to the previous character (Left) function prev_char(amount) cursor = prev_utf8(line, cursor) + suggestion_buffer = {} update() end @@ -482,8 +609,7 @@ function clear() cursor = 1 insert_mode = false history_pos = #history + 1 - suggestion_buffer = {} - update() + handle_edit() end -- Close the REPL if the current line is empty, otherwise delete the next @@ -521,7 +647,8 @@ function help_command(param) end end if not cmd then - log_add(styles.error, 'No command matches "' .. param .. '"!') + log_add('No command matches "' .. param .. '"!\n', styles.error, + terminal_styles.error) return end output = output .. 'Command "' .. cmd.name .. '"\n' @@ -536,7 +663,7 @@ function help_command(param) output = output .. 'This command supports variable arguments.\n' end end - log_add('', output) + log_add(output) end -- Add a line to the history and deduplicate @@ -556,20 +683,25 @@ end -- Run the current command and clear the line (Enter) function handle_enter() - if line == '' then + if line == '' and input_caller == nil then return end - if history[#history] ~= line then + if history[#history] ~= line and line ~= '' then history_add(line) end - -- match "help [<text>]", return <text> or "", strip all whitespace - local help = line:match('^%s*help%s+(.-)%s*$') or - (line:match('^%s*help$') and '') - if help then - help_command(help) + if input_caller then + mp.commandv('script-message-to', input_caller, 'input-event', 'submit', + line) else - mp.command(line) + -- match "help [<text>]", return <text> or "", strip all whitespace + local help = line:match('^%s*help%s+(.-)%s*$') or + (line:match('^%s*help$') and '') + if help then + help_command(help) + else + mp.command(line) + end end clear() @@ -607,6 +739,7 @@ function go_history(new_pos) end cursor = line:len() + 1 insert_mode = false + suggestion_buffer = {} update() end @@ -632,6 +765,7 @@ function prev_word() -- string in order to do a "backwards" find. This wouldn't be as annoying -- to do if Lua didn't insist on 1-based indexing. cursor = line:len() - select(2, line:reverse():find('%s*[^%s]*', line:len() - cursor + 2)) + 1 + suggestion_buffer = {} update() end @@ -639,6 +773,7 @@ end -- the next word. (Ctrl+Right) function next_word() cursor = select(2, line:find('%s*[^%s]*', cursor)) + 1 + suggestion_buffer = {} update() end @@ -659,19 +794,21 @@ local function command_list_and_help() end local function property_list() - local option_info = { - 'name', 'type', 'set-from-commandline', 'set-locally', 'default-value', - 'min', 'max', 'choices', - } - local properties = mp.get_property_native('property-list') + for _, sub_property in pairs({'video', 'audio', 'sub', 'sub2'}) do + properties[#properties + 1] = 'current-tracks/' .. sub_property + end + for _, option in ipairs(mp.get_property_native('options')) do properties[#properties + 1] = 'options/' .. option properties[#properties + 1] = 'file-local-options/' .. option properties[#properties + 1] = 'option-info/' .. option - for _, sub_property in ipairs(option_info) do + for _, sub_property in pairs({ + 'name', 'type', 'set-from-commandline', 'set-locally', + 'default-value', 'min', 'max', 'choices', + }) do properties[#properties + 1] = 'option-info/' .. option .. '/' .. sub_property end @@ -789,7 +926,7 @@ function build_completers() { pattern = '^%s*change[-_]list%s+"?([%w_-]+)"?%s+"()%a*$', list = list_option_verb_list, append = '" ' }, { pattern = '^%s*([av]f)%s+()%a*$', list = list_option_verb_list, append = ' ' }, { pattern = '^%s*([av]f)%s+"()%a*$', list = list_option_verb_list, append = '" ' }, - { pattern = '${=?()[%w_/-]*$', list = property_list, append = '}' }, + { pattern = '${[=>]?()[%w_/-]*$', list = property_list, append = '}' }, } for _, command in pairs({'set', 'add', 'cycle', 'cycle[-_]values', 'multiply'}) do @@ -820,32 +957,6 @@ function build_completers() return completers end --- Use 'list' to find possible tab-completions for 'part.' --- Returns a list of all potential completions and the longest --- common prefix of all the matching list items. -function complete_match(part, list) - local completions = {} - local prefix = nil - - for _, candidate in ipairs(list) do - if candidate:sub(1, part:len()) == part then - if prefix and prefix ~= candidate then - local prefix_len = part:len() - while prefix:sub(1, prefix_len + 1) - == candidate:sub(1, prefix_len + 1) do - prefix_len = prefix_len + 1 - end - prefix = candidate:sub(1, prefix_len) - else - prefix = candidate - end - completions[#completions + 1] = candidate - end - end - - return completions, prefix -end - function common_prefix_length(s1, s2) local common_count = 0 for i = 1, #s1 do @@ -873,8 +984,101 @@ function max_overlap_length(s1, s2) return 0 end +-- If str starts with the first or last characters of prefix, strip them. +local function strip_common_characters(str, prefix) + return str:sub(1 + math.max( + common_prefix_length(prefix, str), + max_overlap_length(prefix, str))) +end + +-- Find the longest common case-sensitive prefix of the entries in "list". +local function find_common_prefix(list) + local prefix = list[1] + + for i = 2, #list do + prefix = prefix:sub(1, common_prefix_length(prefix, list[i])) + end + + return prefix +end + +-- Return the entries of "list" beginning with "part" and the longest common +-- prefix of the matches. +local function complete_match(part, list) + local completions = {} + + for _, candidate in pairs(list) do + if candidate:sub(1, part:len()) == part then + completions[#completions + 1] = candidate + end + end + + local prefix = find_common_prefix(completions) + + if opts.case_sensitive then + return completions, prefix + end + + completions = {} + local lower_case_completions = {} + local lower_case_part = part:lower() + + for _, candidate in pairs(list) do + if candidate:sub(1, part:len()):lower() == lower_case_part then + completions[#completions + 1] = candidate + lower_case_completions[#lower_case_completions + 1] = candidate:lower() + end + end + + local lower_case_prefix = find_common_prefix(lower_case_completions) + + -- Behave like GNU readline with completion-ignore-case On. + -- part = 'fooBA', completions = {'foobarbaz', 'fooBARqux'} => + -- prefix = 'fooBARqux', lower_case_prefix = 'foobar', return 'fooBAR' + if prefix then + return completions, prefix:sub(1, lower_case_prefix:len()) + end + + -- part = 'fooba', completions = {'fooBARbaz', 'fooBarqux'} => + -- prefix = nil, lower_case_prefix ='foobar', return 'fooBAR' + if lower_case_prefix then + return completions, completions[1]:sub(1, lower_case_prefix:len()) + end + + return {}, part +end + +local function cycle_through_suggestions(backwards) + selected_suggestion_index = selected_suggestion_index + (backwards and -1 or 1) + + if selected_suggestion_index > #suggestion_buffer then + selected_suggestion_index = 1 + elseif selected_suggestion_index < 1 then + selected_suggestion_index = #suggestion_buffer + end + + local before_cur = line:sub(1, completion_start_position - 1) .. + suggestion_buffer[selected_suggestion_index] .. completion_append + line = before_cur .. strip_common_characters(line:sub(cursor), completion_append) + cursor = before_cur:len() + 1 + update() +end + -- Complete the option or property at the cursor (TAB) -function complete() +function complete(backwards) + if #suggestion_buffer > 0 then + cycle_through_suggestions(backwards) + return + end + + if input_caller then + completion_old_line = line + completion_old_cursor = cursor + mp.commandv('script-message-to', input_caller, 'input-event', + 'complete', line:sub(1, cursor - 1)) + return + end + local before_cur = line:sub(1, cursor - 1) local after_cur = line:sub(cursor) @@ -882,47 +1086,56 @@ function complete() for _, completer in ipairs(build_completers()) do -- Completer patterns should return the start of the word to be -- completed as the first capture. - local s, s2 = before_cur:match(completer.pattern) - if not s then + local s2 + completion_start_position, s2 = before_cur:match(completer.pattern) + if not completion_start_position then -- Multiple input commands can be separated by semicolons, so all -- completions that are anchored at the start of the string with -- '^' can start from a semicolon as well. Replace ^ with ; and try -- to match again. - s, s2 = before_cur:match(completer.pattern:gsub('^^', ';')) + completion_start_position, s2 = + before_cur:match(completer.pattern:gsub('^^', ';')) end - if s then + if completion_start_position then local hint if s2 then - hint = s - s = s2 + hint = completion_start_position + completion_start_position = s2 + end + + -- Expand ~ in file completion. + if completer.list == file_list and hint:find('^~' .. path_separator) then + local home = mp.command_native({'expand-path', '~/'}) + before_cur = before_cur:sub(1, completion_start_position - #hint - 1) .. + home .. + before_cur:sub(completion_start_position - #hint + 1) + hint = home .. hint:sub(2) + completion_start_position = completion_start_position + #home - 1 end -- If the completer's pattern found a word, check the completer's -- list for possible completions - local part = before_cur:sub(s) + local part = before_cur:sub(completion_start_position) local completions, prefix = complete_match(part, completer.list(hint)) if #completions > 0 then -- If there was only one full match from the list, add -- completer.append to the final string. This is normally a -- space or a quotation mark followed by a space. - local after_cur_index = 1 + completion_append = completer.append or '' if #completions == 1 then - local append = completer.append or '' - prefix = prefix .. append - - -- calculate offset into after_cur - local prefix_len = common_prefix_length(append, after_cur) - local overlap_size = max_overlap_length(append, after_cur) - after_cur_index = math.max(prefix_len, overlap_size) + 1 + prefix = prefix .. completion_append + after_cur = strip_common_characters(after_cur, completion_append) else table.sort(completions) suggestion_buffer = completions + selected_suggestion_index = 0 end -- Insert the completion and update - before_cur = before_cur:sub(1, s - 1) .. prefix + before_cur = before_cur:sub(1, completion_start_position - 1) .. + prefix cursor = before_cur:len() + 1 - line = before_cur .. after_cur:sub(after_cur_index) + line = before_cur .. after_cur update() return end @@ -933,12 +1146,14 @@ end -- Move the cursor to the beginning of the line (HOME) function go_home() cursor = 1 + suggestion_buffer = {} update() end -- Move the cursor to the end of the line (END) function go_end() cursor = line:len() + 1 + suggestion_buffer = {} update() end @@ -950,7 +1165,7 @@ function del_word() before_cur = before_cur:gsub('[^%s]+%s*$', '', 1) line = before_cur .. after_cur cursor = before_cur:len() + 1 - update() + handle_edit() end -- Delete from the cursor to the end of the word (Ctrl+Del) @@ -962,25 +1177,25 @@ function del_next_word() after_cur = after_cur:gsub('^%s*[^%s]+', '', 1) line = before_cur .. after_cur - update() + handle_edit() end -- Delete from the cursor to the end of the line (Ctrl+K) function del_to_eol() line = line:sub(1, cursor - 1) - update() + handle_edit() end -- Delete from the cursor back to the start of the line (Ctrl+U) function del_to_start() line = line:sub(cursor) cursor = 1 - update() + handle_edit() end -- Empty the log buffer of all messages (Ctrl+L) function clear_log_buffer() - log_buffer = {} + log_buffers[id] = {} update() end @@ -1047,7 +1262,7 @@ function paste(clip) local after_cur = line:sub(cursor) line = before_cur .. text .. after_cur cursor = cursor + text:len() - update() + handle_edit() end -- List of input bindings. This is a weird mashup between common GUI text-input @@ -1087,6 +1302,7 @@ function get_bindings() { 'alt+f', next_word }, { 'tab', complete }, { 'ctrl+i', complete }, + { 'shift+tab', function() complete(true) end }, { 'ctrl+a', go_home }, { 'home', go_home }, { 'ctrl+e', go_end }, @@ -1151,16 +1367,105 @@ mp.add_key_binding(nil, 'enable', function() set_active(true) end) +mp.register_script_message('disable', function() + set_active(false) +end) + -- Add a script-message to show the REPL and fill it with the provided text mp.register_script_message('type', function(text, cursor_pos) show_and_type(text, cursor_pos) end) +mp.register_script_message('get-input', function (script_name, args) + if repl_active then + return + end + + input_caller = script_name + args = utils.parse_json(args) + prompt = args.prompt or default_prompt + line = args.default_text or '' + cursor = tonumber(args.cursor_position) or line:len() + 1 + id = args.id or script_name .. prompt + if histories[id] == nil then + histories[id] = {} + log_buffers[id] = {} + end + history = histories[id] + history_pos = #history + 1 + + set_active(true) + mp.commandv('script-message-to', input_caller, 'input-event', 'opened') +end) + +mp.register_script_message('log', function (message) + -- input.get's edited handler is invoked after submit, so avoid modifying + -- the default log. + if input_caller == nil then + return + end + + message = utils.parse_json(message) + + log_add(message.text .. '\n', + message.error and styles.error or message.style, + message.error and terminal_styles.error or message.terminal_style) +end) + +mp.register_script_message('set-log', function (log) + if input_caller == nil then + return + end + + log = utils.parse_json(log) + log_buffers[id] = {} + + for i = 1, #log do + if type(log[i]) == 'table' then + log[i].text = log[i].text .. '\n' + log[i].style = log[i].style or '' + log[i].terminal_style = log[i].terminal_style or '' + log_buffers[id][i] = log[i] + else + log_buffers[id][i] = { + text = log[i] .. '\n', + style = '', + terminal_style = '', + } + end + end + + update() +end) + +mp.register_script_message('complete', function(list, start_pos) + if line ~= completion_old_line or cursor ~= completion_old_cursor then + return + end + + local completions, prefix = complete_match(line:sub(start_pos, cursor), + utils.parse_json(list)) + local before_cur = line:sub(1, start_pos - 1) .. prefix + local after_cur = line:sub(cursor) + cursor = before_cur:len() + 1 + line = before_cur .. after_cur + + if #completions > 1 then + suggestion_buffer = completions + selected_suggestion_index = 0 + completion_start_position = start_pos + completion_append = '' + end + + update() +end) + -- Redraw the REPL when the OSD size changes. This is needed because the -- PlayRes of the OSD will need to be adjusted. mp.observe_property('osd-width', 'native', update) mp.observe_property('osd-height', 'native', update) mp.observe_property('display-hidpi-scale', 'native', update) +mp.observe_property('focused', 'native', update) -- Enable log messages. In silent mode, mpv will queue log messages in a buffer -- until enable_messages is called again without the silent: prefix. @@ -1185,20 +1490,8 @@ mp.register_event('log-message', function(e) if e.level == 'trace' then return end -- Use color for debug/v/warn/error/fatal messages. - local style = '' - if e.level == 'debug' then - style = styles.debug - elseif e.level == 'v' then - style = styles.verbose - elseif e.level == 'warn' then - style = styles.warn - elseif e.level == 'error' then - style = styles.error - elseif e.level == 'fatal' then - style = styles.fatal - end - - log_add(style, '[' .. e.prefix .. '] ' .. e.text) + log_add('[' .. e.prefix .. '] ' .. e.text, styles[e.level], + terminal_styles[e.level]) end) collectgarbage() diff --git a/player/lua/defaults.lua b/player/lua/defaults.lua index 233d1d6..baa3a24 100644 --- a/player/lua/defaults.lua +++ b/player/lua/defaults.lua @@ -809,28 +809,4 @@ function mp_utils.subprocess_detached(t) mp.commandv("run", unpack(t.args)) end -function mp_utils.shared_script_property_set(name, value) - if value ~= nil then - -- no such thing as change-list with mpv_node, so build a string value - mp.commandv("change-list", "shared-script-properties", "append", - name .. "=" .. value) - else - mp.commandv("change-list", "shared-script-properties", "remove", name) - end -end - -function mp_utils.shared_script_property_get(name) - local map = mp.get_property_native("shared-script-properties") - return map and map[name] -end - --- cb(name, value) on change and on init -function mp_utils.shared_script_property_observe(name, cb) - -- it's _very_ wasteful to observe the mpv core "super" property for every - -- shared sub-property, but then again you shouldn't use this - mp.observe_property("shared-script-properties", "native", function(_, val) - cb(name, val and val[name]) - end) -end - return {} diff --git a/player/lua/input.lua b/player/lua/input.lua new file mode 100644 index 0000000..24283e4 --- /dev/null +++ b/player/lua/input.lua @@ -0,0 +1,69 @@ +--[[ +This file is part of mpv. + +mpv is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +mpv is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with mpv. If not, see <http://www.gnu.org/licenses/>. +]] + +local utils = require "mp.utils" +local input = {} + +function input.get(t) + mp.commandv("script-message-to", "console", "get-input", + mp.get_script_name(), utils.format_json({ + prompt = t.prompt, + default_text = t.default_text, + cursor_position = t.cursor_position, + id = t.id, + })) + + mp.register_script_message("input-event", function (type, text, cursor_position) + if t[type] then + local suggestions, completion_start_position = t[type](text, cursor_position) + + if type == "complete" and suggestions then + mp.commandv("script-message-to", "console", "complete", + utils.format_json(suggestions), completion_start_position) + end + end + + if type == "closed" then + mp.unregister_script_message("input-event") + end + end) + + return true +end + +function input.terminate() + mp.commandv("script-message-to", "console", "disable") +end + +function input.log(message, style, terminal_style) + mp.commandv("script-message-to", "console", "log", utils.format_json({ + text = message, + style = style, + terminal_style = terminal_style, + })) +end + +function input.log_error(message) + mp.commandv("script-message-to", "console", "log", + utils.format_json({ text = message, error = true })) +end + +function input.set_log(log) + mp.commandv("script-message-to", "console", "set-log", utils.format_json(log)) +end + +return input diff --git a/player/lua/meson.build b/player/lua/meson.build index 362c87c..1d87938 100644 --- a/player/lua/meson.build +++ b/player/lua/meson.build @@ -1,5 +1,6 @@ lua_files = ['defaults.lua', 'assdraw.lua', 'options.lua', 'osc.lua', - 'ytdl_hook.lua', 'stats.lua', 'console.lua', 'auto_profiles.lua'] + 'ytdl_hook.lua', 'stats.lua', 'console.lua', 'auto_profiles.lua', + 'input.lua'] foreach file: lua_files lua_file = custom_target(file, input: join_paths(source_root, 'player', 'lua', file), diff --git a/player/lua/osc.lua b/player/lua/osc.lua index 45a5d90..3ba1890 100644 --- a/player/lua/osc.lua +++ b/player/lua/osc.lua @@ -38,6 +38,7 @@ local user_opts = { seekrangeseparate = true, -- whether the seekranges overlay on the bar-style seekbar seekrangealpha = 200, -- transparency of seekranges seekbarkeyframes = true, -- use keyframes when dragging the seekbar + scrollcontrols = true, -- allow scrolling when hovering certain OSC elements title = "${media-title}", -- string compatible with property-expansion -- to be shown as OSC title tooltipborder = 1, -- border of tooltip in bottom/topbar @@ -51,6 +52,7 @@ local user_opts = { boxvideo = false, -- apply osc_param.video_margins to video windowcontrols = "auto", -- whether to show window controls windowcontrols_alignment = "right", -- which side to show window controls on + windowcontrols_title = "${media-title}", -- same as title but for windowcontrols greenandgrumpy = false, -- disable santa hat livemarkers = true, -- update seekbar chapter markers on duration change chapters_osd = true, -- whether to show chapters OSD on next/prev @@ -125,6 +127,7 @@ local state = { input_enabled = true, showhide_enabled = false, windowcontrols_buttons = false, + windowcontrols_title = false, dmx_cache = 0, using_video_margins = false, border = true, @@ -410,10 +413,10 @@ function set_track(type, next) mp.commandv("set", type, new_track_mpv) - if new_track_osc == 0 then + if new_track_osc == 0 then show_message(nicetypes[type] .. " Track: none") else - show_message(nicetypes[type] .. " Track: " + show_message(nicetypes[type] .. " Track: " .. new_track_osc .. "/" .. #tracks_osc[type] .. " [".. (tracks_osc[type][new_track_osc].lang or "unknown") .."] " .. (tracks_osc[type][new_track_osc].title or "")) @@ -436,7 +439,7 @@ end function window_controls_enabled() val = user_opts.windowcontrols if val == "auto" then - return not state.border + return not (state.border and state.title_bar) else return val ~= "no" end @@ -952,10 +955,7 @@ function show_message(text, duration) -- may slow down massively on huge input text = string.sub(text, 0, 4000) - -- replace actual linebreaks with ASS linebreaks - text = string.gsub(text, "\n", "\\N") - - state.message_text = text + state.message_text = mp.command_native({"escape-ass", text}) if not state.message_hide_timer then state.message_hide_timer = mp.add_timeout(0, request_tick) @@ -1158,10 +1158,9 @@ function window_controls(topbar) -- Window Title ne = new_element("wctitle", "button") ne.content = function () - local title = mp.command_native({"expand-text", user_opts.title}) - -- escape ASS, and strip newlines and trailing slashes - title = title:gsub("\\n", " "):gsub("\\$", ""):gsub("{","\\{") - return not (title == "") and title or "mpv" + local title = mp.command_native({"expand-text", user_opts.windowcontrols_title}) + title = title:gsub("\n", " ") + return title ~= "" and mp.command_native({"escape-ass", title}) or "mpv" end local left_pad = 5 local right_pad = 10 @@ -1789,9 +1788,8 @@ function osc_init() ne.content = function () local title = state.forced_title or mp.command_native({"expand-text", user_opts.title}) - -- escape ASS, and strip newlines and trailing slashes - title = title:gsub("\\n", " "):gsub("\\$", ""):gsub("{","\\{") - return not (title == "") and title or "mpv" + title = title:gsub("\n", " ") + return title ~= "" and mp.command_native({"escape-ass", title}) or "mpv" end ne.eventresponder["mbtn_left_up"] = function () @@ -1937,10 +1935,13 @@ function osc_init() function () set_track("audio", -1) end ne.eventresponder["shift+mbtn_left_down"] = function () show_message(get_tracklist("audio"), 2) end - ne.eventresponder["wheel_down_press"] = - function () set_track("audio", 1) end - ne.eventresponder["wheel_up_press"] = - function () set_track("audio", -1) end + + if user_opts.scrollcontrols then + ne.eventresponder["wheel_down_press"] = + function () set_track("audio", 1) end + ne.eventresponder["wheel_up_press"] = + function () set_track("audio", -1) end + end --cy_sub ne = new_element("cy_sub", "button") @@ -1960,10 +1961,13 @@ function osc_init() function () set_track("sub", -1) end ne.eventresponder["shift+mbtn_left_down"] = function () show_message(get_tracklist("sub"), 2) end - ne.eventresponder["wheel_down_press"] = - function () set_track("sub", 1) end - ne.eventresponder["wheel_up_press"] = - function () set_track("sub", -1) end + + if user_opts.scrollcontrols then + ne.eventresponder["wheel_down_press"] = + function () set_track("sub", 1) end + ne.eventresponder["wheel_up_press"] = + function () set_track("sub", -1) end + end --tog_fs ne = new_element("tog_fs", "button") @@ -2053,10 +2057,13 @@ function osc_init() "absolute-percent", "exact") end ne.eventresponder["reset"] = function (element) element.state.lastseek = nil end - ne.eventresponder["wheel_up_press"] = - function () mp.commandv("osd-auto", "seek", 10) end - ne.eventresponder["wheel_down_press"] = - function () mp.commandv("osd-auto", "seek", -10) end + + if user_opts.scrollcontrols then + ne.eventresponder["wheel_up_press"] = + function () mp.commandv("osd-auto", "seek", 10) end + ne.eventresponder["wheel_down_press"] = + function () mp.commandv("osd-auto", "seek", -10) end + end -- tc_left (current pos) @@ -2140,10 +2147,12 @@ function osc_init() ne.eventresponder["mbtn_left_up"] = function () mp.commandv("cycle", "mute") end - ne.eventresponder["wheel_up_press"] = - function () mp.commandv("osd-auto", "add", "volume", 5) end - ne.eventresponder["wheel_down_press"] = - function () mp.commandv("osd-auto", "add", "volume", -5) end + if user_opts.scrollcontrols then + ne.eventresponder["wheel_up_press"] = + function () mp.commandv("osd-auto", "add", "volume", 5) end + ne.eventresponder["wheel_down_press"] = + function () mp.commandv("osd-auto", "add", "volume", -5) end + end -- load layout @@ -2439,6 +2448,18 @@ function render() if osc_param.areas["window-controls-title"] then for _,cords in ipairs(osc_param.areas["window-controls-title"]) do + if state.osc_visible then -- activate only when OSC is actually visible + set_virt_mouse_area(cords.x1, cords.y1, cords.x2, cords.y2, "window-controls-title") + end + if state.osc_visible ~= state.windowcontrols_title then + if state.osc_visible then + mp.enable_key_bindings("window-controls-title", "allow-vo-dragging") + else + mp.disable_key_bindings("window-controls-title", "allow-vo-dragging") + end + state.windowcontrols_title = state.osc_visible + end + if mouse_hit_coords(cords.x1, cords.y1, cords.x2, cords.y2) then mouse_over_osc = true end @@ -2709,7 +2730,7 @@ function update_duration_watch() if want_watch ~= duration_watched then if want_watch then - mp.observe_property("duration", nil, on_duration) + mp.observe_property("duration", "native", on_duration) else mp.unobserve_property(on_duration) end @@ -2722,8 +2743,8 @@ update_duration_watch() mp.register_event("shutdown", shutdown) mp.register_event("start-file", request_init) -mp.observe_property("track-list", nil, request_init) -mp.observe_property("playlist", nil, request_init) +mp.observe_property("track-list", "native", request_init) +mp.observe_property("playlist", "native", request_init) mp.observe_property("chapter-list", "native", function(_, list) list = list or {} -- safety, shouldn't return nil table.sort(list, function(a, b) return a.time < b.time end) @@ -2760,6 +2781,12 @@ mp.observe_property("border", "bool", request_init_resize() end ) +mp.observe_property("title-bar", "bool", + function(name, val) + state.title_bar = val + request_init_resize() + end +) mp.observe_property("window-maximized", "bool", function(name, val) state.maximized = val @@ -2915,3 +2942,4 @@ mp.register_script_message("osc-idlescreen", idlescreen_visibility) set_virt_mouse_area(0, 0, 0, 0, "input") set_virt_mouse_area(0, 0, 0, 0, "window-controls") +set_virt_mouse_area(0, 0, 0, 0, "window-controls-title") diff --git a/player/lua/stats.lua b/player/lua/stats.lua index 16e8b68..3d093c7 100644 --- a/player/lua/stats.lua +++ b/player/lua/stats.lua @@ -30,6 +30,8 @@ local o = { print_perfdata_passes = false, -- when true, print the full information about all passes filter_params_max_length = 100, -- a filter list longer than this many characters will be shown one filter per line instead show_frame_info = false, -- whether to show the current frame info + term_width_limit = -1, -- overwrites the terminal width + term_height_limit = -1, -- overwrites the terminal height debug = false, -- Graph options and style @@ -83,6 +85,15 @@ local o = { } options.read_options(o) +o.term_width_limit = tonumber(o.term_width_limit) or -1 +o.term_height_limit = tonumber(o.term_height_limit) or -1 +if o.term_width_limit < 0 then + o.term_width_limit = nil +end +if o.term_height_limit < 0 then + o.term_height_limit = nil +end + local format = string.format local max = math.max local min = math.min @@ -118,9 +129,6 @@ local function graph_add_value(graph, value) graph.max = max(graph.max, value) end --- "\\<U+2060>" in UTF-8 (U+2060 is WORD-JOINER) -local ESC_BACKSLASH = "\\" .. string.char(0xE2, 0x81, 0xA0) - local function no_ASS(t) if not o.use_ass then return t @@ -128,16 +136,7 @@ local function no_ASS(t) -- mp.osd_message supports ass-escape using osd-ass-cc/{0|1} return ass_stop .. t .. ass_start else - -- mp.set_osd_ass doesn't support ass-escape. roll our own. - -- similar to mpv's sub/osd_libass.c:mangle_ass(...), excluding - -- space after newlines because no_ASS is not used with multi-line. - -- space at the beginning is replaced with "\\h" because it matters - -- at the beginning of a line, and we can't know where our output - -- ends up. no issue if it ends up at the middle of a line. - return tostring(t) - :gsub("\\", ESC_BACKSLASH) - :gsub("{", "\\{") - :gsub("^ ", "\\h") + return mp.command_native({"escape-ass", tostring(t)}) end end @@ -222,7 +221,7 @@ local function generate_graph(values, i, len, v_max, v_avg, scale, x_tics) local bg_box = format("{\\bord0.5}{\\3c&H%s&}{\\1c&H%s&}m 0 %f l %f %f %f 0 0 0", o.plot_bg_border_color, o.plot_bg_color, y_max, x_max, y_max, x_max) - return format("%s{\\r}{\\pbo%f}{\\shad0}{\\alpha&H00}{\\p1}%s{\\p0}{\\bord0}{\\1c&H%s}{\\p1}%s{\\p0}%s", + return format("%s{\\r}{\\rDefault}{\\pbo%f}{\\shad0}{\\alpha&H00}{\\p1}%s{\\p0}{\\bord0}{\\1c&H%s}{\\p1}%s{\\p0}%s", o.prefix_sep, y_offset, bg_box, o.plot_color, table.concat(s), text_style()) end @@ -277,7 +276,13 @@ local function sorted_keys(t, comp_fn) return keys end -local function append_perfdata(s, dedicated_page, print_passes) +local function scroll_hint() + local hint = format("(hint: scroll with %s/%s)", o.key_scroll_up, o.key_scroll_down) + if not o.use_ass then return " " .. hint end + return format(" {\\fs%s}%s{\\fs%s}", o.font_size * 0.66, hint, o.font_size) +end + +local function append_perfdata(header, s, dedicated_page, print_passes) local vo_p = mp.get_property_native("vo-passes") if not vo_p then return @@ -318,11 +323,12 @@ local function append_perfdata(s, dedicated_page, print_passes) -- ensure that the fixed title is one element and every scrollable line is -- also one single element. - s[#s+1] = format("%s%s%s%s{\\fs%s}%s%s{\\fs%s}", + local h = dedicated_page and header or s + h[#h+1] = format("%s%s%s%s{\\fs%s}%s{\\fs%s}%s", dedicated_page and "" or o.nl, dedicated_page and "" or o.indent, b("Frame Timings:"), o.prefix_sep, o.font_size * 0.66, - "(last/average/peak μs)", - dedicated_page and " (hint: scroll with ↑↓)" or "", o.font_size) + "(last/average/peak μs)", o.font_size, + dedicated_page and scroll_hint() or "") for _,frame in ipairs(sorted_keys(vo_p)) do -- ensure fixed display order local data = vo_p[frame] @@ -363,11 +369,6 @@ local function append_perfdata(s, dedicated_page, print_passes) end end -local function ellipsis(s, maxlen) - if not maxlen or s:len() <= maxlen then return s end - return s:sub(1, maxlen - 3) .. "..." -end - -- command prefix tokens to strip - includes generic property commands local cmd_prefixes = { osd_auto=1, no_osd=1, osd_bar=1, osd_msg=1, osd_msg_bar=1, raw=1, sync=1, @@ -419,7 +420,7 @@ local function keyname_cells(k) return klen end -local function get_kbinfo_lines(width) +local function get_kbinfo_lines() -- active keys: only highest priority of each key, and not our (stats) keys local bindings = mp.get_property_native("input-bindings", {}) local active = {} -- map: key-name -> bind-info @@ -482,8 +483,6 @@ local function get_kbinfo_lines(width) or format("{\\q2\\fn%s}%s {\\fn%s}{\\fs%d\\u1}", o.font_mono, kspaces, o.font, 1.3*o.font_size) local spost = term and "" or format("{\\u0\\fs%d}", o.font_size) - local _, itabs = o.indent:gsub("\t", "") - local cutoff = term and (width or 79) - o.indent:len() - itabs * 7 - spre:len() -- create the display lines local info_lines = {} @@ -497,38 +496,25 @@ local function get_kbinfo_lines(width) if bind.comment then bind.cmd = bind.cmd .. " # " .. bind.comment end - append(info_lines, ellipsis(bind.cmd, cutoff), - { prefix = kpre .. no_ASS(align_right(bind.key)) .. kpost }) + append(info_lines, bind.cmd, { prefix = kpre .. no_ASS(align_right(bind.key)) .. kpost }) end return info_lines end -local function append_general_perfdata(s, offset) - local perf_info = mp.get_property_native("perf-info") or {} - local count = 0 - for _, data in ipairs(perf_info) do - count = count + 1 - end - offset = max(1, min((offset or 1), count)) - - local i = 0 - for _, data in ipairs(perf_info) do - i = i + 1 - if i >= offset then - append(s, data.text or data.value, {prefix="["..tostring(i).."] "..data.name..":"}) - - if o.plot_perfdata and o.use_ass and data.value then - buf = perf_buffers[data.name] - if not buf then - buf = {0, pos = 1, len = 50, max = 0} - perf_buffers[data.name] = buf - end - graph_add_value(buf, data.value) - s[#s+1] = generate_graph(buf, buf.pos, buf.len, buf.max, nil, 0.8, 1) +local function append_general_perfdata(s) + for i, data in ipairs(mp.get_property_native("perf-info") or {}) do + append(s, data.text or data.value, {prefix="["..tostring(i).."] "..data.name..":"}) + + if o.plot_perfdata and o.use_ass and data.value then + buf = perf_buffers[data.name] + if not buf then + buf = {0, pos = 1, len = 50, max = 0} + perf_buffers[data.name] = buf end + graph_add_value(buf, data.value) + s[#s] = s[#s] .. generate_graph(buf, buf.pos, buf.len, buf.max, nil, 0.8, 1) end end - return offset end local function append_display_sync(s) @@ -806,6 +792,7 @@ local function append_img_params(s, r, ro) end local indent = o.prefix_sep .. o.prefix_sep + r = ro or r local pixel_format = r["hw-pixelformat"] or r["pixelformat"] append(s, pixel_format, {prefix="Format:"}) @@ -828,7 +815,7 @@ local function append_fps(s, prop, eprop) local unit = prop == "display-fps" and " Hz" or " fps" local suffix = single and "" or " (specified)" local esuffix = single and "" or " (estimated)" - local prefix = prop == "display-fps" and "Refresh Rate:" or "Frame rate:" + local prefix = prop == "display-fps" and "Refresh Rate:" or "Frame Rate:" local nl = o.nl local indent = o.indent @@ -855,6 +842,8 @@ local function add_video_out(s) append(s, vo, {prefix_sep="", nl="", indent=""}) append_property(s, "display-names", {prefix_sep="", prefix="(", suffix=")", no_prefix_markup=true, nl="", indent=" "}) + append(s, mp.get_property_native("current-gpu-context"), + {prefix="Context:", nl="", indent=o.prefix_sep .. o.prefix_sep}) append_property(s, "avsync", {prefix="A-V:"}) append_fps(s, "display-fps", "estimated-display-fps") if append_property(s, "decoder-frame-drop-count", @@ -862,9 +851,9 @@ local function add_video_out(s) append_property(s, "frame-drop-count", {suffix=" (output)", nl="", indent=""}) end append_display_sync(s) - append_perfdata(s, false, o.print_perfdata_passes) + append_perfdata(nil, s, false, o.print_perfdata_passes) - if mp.get_property_native("deinterlace") then + if mp.get_property_native("deinterlace-active") then append_property(s, "deinterlace", {prefix="Deinterlacing:"}) end @@ -902,12 +891,11 @@ local function add_video(s) return end - local osd_dims = mp.get_property_native("osd-dimensions") - local scaled_width = osd_dims["w"] - osd_dims["ml"] - osd_dims["mr"] - local scaled_height = osd_dims["h"] - osd_dims["mt"] - osd_dims["mb"] - append(s, "", {prefix=o.nl .. o.nl .. "Video:", nl="", indent=""}) - if append_property(s, "video-codec", {prefix_sep="", nl="", indent=""}) then + local track = mp.get_property_native("current-tracks/video") + if track and append(s, track["codec-desc"], {prefix_sep="", nl="", indent=""}) then + append(s, track["codec-profile"], {prefix="[", nl="", indent=" ", prefix_sep="", + no_prefix_markup=true, suffix="]"}) append_property(s, "hwdec-current", {prefix="HW:", nl="", indent=o.prefix_sep .. o.prefix_sep, no_prefix_markup=false, suffix=""}, {no=true, [""]=true}) @@ -947,19 +935,39 @@ end local function add_audio(s) local r = mp.get_property_native("audio-params") -- in case of e.g. lavfi-complex there can be no input audio, only output - if not r then - r = mp.get_property_native("audio-out-params") - end + local ro = mp.get_property_native("audio-out-params") or r + r = r or ro if not r then return end + local merge = function(r, ro, prop) + local a = r[prop] or ro[prop] + local b = ro[prop] or r[prop] + return (a == b or a == nil) and a or (a .. " ➜ " .. b) + end + append(s, "", {prefix=o.nl .. o.nl .. "Audio:", nl="", indent=""}) - append_property(s, "audio-codec", {prefix_sep="", nl="", indent=""}) - local cc = append(s, r["channel-count"], {prefix="Channels:"}) - append(s, r["format"], {prefix="Format:", nl=cc and "" or o.nl, + local track = mp.get_property_native("current-tracks/audio") + if track then + append(s, track["codec-desc"], {prefix_sep="", nl="", indent=""}) + append(s, track["codec-profile"], {prefix="[", nl="", indent=" ", prefix_sep="", + no_prefix_markup=true, suffix="]"}) + end + append_property(s, "current-ao", {prefix="AO:", nl="", + indent=o.prefix_sep .. o.prefix_sep}) + local dev = append_property(s, "audio-device", {prefix="Device:"}) + local ao_mute = mp.get_property_native("ao-mute") and " (Muted)" or "" + append_property(s, "ao-volume", {prefix="AO Volume:", suffix="%" .. ao_mute, + nl=dev and "" or o.nl, + indent=dev and o.prefix_sep .. o.prefix_sep}) + if math.abs(mp.get_property_native("audio-delay")) > 1e-6 then + append_property(s, "audio-delay", {prefix="A-V delay:"}) + end + local cc = append(s, merge(r, ro, "channel-count"), {prefix="Channels:"}) + append(s, merge(r, ro, "format"), {prefix="Format:", nl=cc and "" or o.nl, indent=cc and o.prefix_sep .. o.prefix_sep}) - append(s, r["samplerate"], {prefix="Sample Rate:", suffix=" Hz"}) + append(s, merge(r, ro, "samplerate"), {prefix="Sample Rate:", suffix=" Hz"}) append_property(s, "packet-audio-bitrate", {prefix="Bitrate:", suffix=" kbps"}) append_filters(s, "af", "Filters:") end @@ -987,6 +995,91 @@ local function eval_ass_formatting() end end +-- assumptions: +-- s is composed of SGR escape sequences and/or printable UTF8 sequences +-- printable codepoints occupy one terminal cell (we don't have wcwidth) +-- tabstops are 8, 16, 24..., and the output starts at 0 or a tabstop +-- note: if maxwidth <= 2 and s doesn't fit: the result is "..." (more than 2) +function term_ellipsis(s, maxwidth) + local TAB, ESC, SGR_END = 9, 27, ("m"):byte() + local width, ellipsis = 0, "..." + local fit_len, in_sgr + + for i = 1, #s do + local x = s:byte(i) + + if in_sgr then + in_sgr = x ~= SGR_END + elseif x == ESC then + in_sgr = true + ellipsis = "\27[0m..." -- ensure SGR reset + elseif x < 128 or x >= 192 then -- non UTF8-continuation + -- tab adds till the next stop, else add 1 + width = width + (x == TAB and 8 - width % 8 or 1) + + if fit_len == nil and width > maxwidth - 3 then + fit_len = i - 1 -- adding "..." still fits maxwidth + end + if width > maxwidth then + return s:sub(1, fit_len) .. ellipsis + end + end + end + + return s +end + +local function term_ellipsis_array(arr, from, to, max_width) + for i = from, to do + arr[i] = term_ellipsis(arr[i], max_width) + end + return arr +end + +-- split str into a table +-- example: local t = split(s, "\n") +-- plain: whether pat is a plain string (default false - pat is a pattern) +local function split(str, pat, plain) + local init = 1 + local r, i, find, sub = {}, 1, string.find, string.sub + repeat + local f0, f1 = find(str, pat, init, plain) + r[i], i = sub(str, init, f0 and f0 - 1), i+1 + init = f0 and f1 + 1 + until f0 == nil + return r +end + +-- Composes the output with header and scrollable content +-- Returns string of the finished page and the actually chosen offset +-- +-- header : table of the header where each entry is one line +-- content : table of the content where each entry is one line +-- apply_scroll: scroll the content +local function finalize_page(header, content, apply_scroll) + local term_size = mp.get_property_native("term-size", {}) + local term_width = o.term_width_limit or term_size.w or 80 + local term_height = o.term_height_limit or term_size.h or 24 + local from, to = 1, #content + if apply_scroll and term_height > 0 then + -- Up to 40 lines for libass because it can put a big performance toll on + -- libass to process many lines which end up outside (below) the screen. + -- In the terminal reduce height by 2 for the status line (can be more then one line) + local max_content_lines = (o.use_ass and 40 or term_height - 2) - #header + -- in the terminal the scrolling should stop once the last line is visible + local max_offset = o.use_ass and #content or #content - max_content_lines + 1 + from = max(1, min((pages[curr_page].offset or 1), max_offset)) + to = min(#content, from + max_content_lines - 1) + pages[curr_page].offset = from + end + local output = table.concat(header) .. table.concat(content, "", from, to) + if not o.use_ass and term_width > 0 then + local t = split(output, "\n", true) + -- limit width for the terminal + output = table.concat(term_ellipsis_array(t, 1, #t, term_width), "\n") + end + return output, from +end -- Returns an ASS string with "normal" stats local function default_stats() @@ -997,70 +1090,44 @@ local function default_stats() add_video_out(stats) add_video(stats) add_audio(stats) - return table.concat(stats) -end - -local function scroll_vo_stats(stats, fixed_items, offset) - local ret = {} - local count = #stats - fixed_items - offset = max(1, min((offset or 1), count)) - - for i, line in pairs(stats) do - if i <= fixed_items or i >= fixed_items + offset then - ret[#ret+1] = stats[i] - end - end - return ret, offset + return finalize_page({}, stats, false) end -- Returns an ASS string with extended VO stats local function vo_stats() - local stats = {} + local header, content = {}, {} eval_ass_formatting() - add_header(stats) - - -- first line (title) added next is considered fixed - local fixed_items = #stats + 1 - append_perfdata(stats, true, true) - - local page = pages[o.key_page_2] - stats, page.offset = scroll_vo_stats(stats, fixed_items, page.offset) - return table.concat(stats) + add_header(header) + append_perfdata(header, content, true, true) + header = {table.concat(header)} + return finalize_page(header, content, true) end local kbinfo_lines = nil -local function keybinding_info(after_scroll) +local function keybinding_info(after_scroll, bindlist) local header = {} local page = pages[o.key_page_4] eval_ass_formatting() add_header(header) - append(header, "", {prefix=format("%s: {\\fs%s}%s{\\fs%s}", page.desc, - o.font_size * 0.66, "(hint: scroll with ↑↓)", o.font_size), nl="", - indent=""}) + append(header, "", {prefix=format("%s:%s", page.desc, scroll_hint()), nl="", indent=""}) + header = {table.concat(header)} if not kbinfo_lines or not after_scroll then - kbinfo_lines = get_kbinfo_lines() + kbinfo_lines = get_kbinfo_lines(o.term_width_limit) end - -- up to 20 lines for the terminal - so that mpv can also print - -- the status line without scrolling, and up to 40 lines for libass - -- because it can put a big performance toll on libass to process - -- many lines which end up outside (below) the screen. - local term = not o.use_ass - local nlines = #kbinfo_lines - page.offset = max(1, min((page.offset or 1), term and nlines - 20 or nlines)) - local maxline = min(nlines, page.offset + (term and 20 or 40)) - return table.concat(header) .. - table.concat(kbinfo_lines, "", page.offset, maxline) + + return finalize_page(header, kbinfo_lines, not bindlist) end local function perf_stats() - local stats = {} + local header, content = {}, {} eval_ass_formatting() - add_header(stats) + add_header(header) local page = pages[o.key_page_0] - append(stats, "", {prefix=page.desc .. ":", nl="", indent=""}) - page.offset = append_general_perfdata(stats, page.offset) - return table.concat(stats) + append(header, "", {prefix=format("%s:%s", page.desc, scroll_hint()), nl="", indent=""}) + append_general_perfdata(content) + header = {table.concat(header)} + return finalize_page(header, content, true) end local function opt_time(t) @@ -1076,18 +1143,18 @@ local function cache_stats() eval_ass_formatting() add_header(stats) - append(stats, "", {prefix="Cache info:", nl="", indent=""}) + append(stats, "", {prefix="Cache Info:", nl="", indent=""}) local info = mp.get_property_native("demuxer-cache-state") if info == nil then append(stats, "Unavailable.", {}) - return table.concat(stats) + return finalize_page({}, stats, false) end local a = info["reader-pts"] local b = info["cache-end"] - append(stats, opt_time(a) .. " - " .. opt_time(b), {prefix = "Packet queue:"}) + append(stats, opt_time(a) .. " - " .. opt_time(b), {prefix = "Packet Queue:"}) local r = nil if a ~= nil and b ~= nil then @@ -1101,7 +1168,7 @@ local function cache_stats() nil, 0.8, 1) r_graph = o.prefix_sep .. r_graph end - append(stats, opt_time(r), {prefix = "Read-ahead:", suffix = r_graph}) + append(stats, opt_time(r), {prefix = "Readahead:", suffix = r_graph}) -- These states are not necessarily exclusive. They're about potentially -- separate mechanisms, whose states may be decoupled. @@ -1140,17 +1207,17 @@ local function cache_stats() else fc = "(disabled)" end - append(stats, fc, {prefix = "Disk cache:"}) + append(stats, fc, {prefix = "Disk Cache:"}) - append(stats, info["debug-low-level-seeks"], {prefix = "Media seeks:"}) - append(stats, info["debug-byte-level-seeks"], {prefix = "Stream seeks:"}) + append(stats, info["debug-low-level-seeks"], {prefix = "Media Seeks:"}) + append(stats, info["debug-byte-level-seeks"], {prefix = "Stream Seeks:"}) append(stats, "", {prefix=o.nl .. o.nl .. "Ranges:", nl="", indent=""}) append(stats, info["bof-cached"] and "yes" or "no", - {prefix = "Start cached:"}) + {prefix = "Start Cached:"}) append(stats, info["eof-cached"] and "yes" or "no", - {prefix = "End cached:"}) + {prefix = "End Cached:"}) local ranges = info["seekable-ranges"] or {} for n, r in ipairs(ranges) do @@ -1159,7 +1226,7 @@ local function cache_stats() {prefix = format("Range %s:", n)}) end - return table.concat(stats) + return finalize_page({}, stats, false) end -- Record 1 sample of cache statistics. @@ -1188,8 +1255,8 @@ pages = { [o.key_page_1] = { f = default_stats, desc = "Default" }, [o.key_page_2] = { f = vo_stats, desc = "Extended Frame Timings", scroll = true }, [o.key_page_3] = { f = cache_stats, desc = "Cache Statistics" }, - [o.key_page_4] = { f = keybinding_info, desc = "Active key bindings", scroll = true }, - [o.key_page_0] = { f = perf_stats, desc = "Internal performance info", scroll = true }, + [o.key_page_4] = { f = keybinding_info, desc = "Active Key Bindings", scroll = true }, + [o.key_page_0] = { f = perf_stats, desc = "Internal Performance Info", scroll = true }, } @@ -1409,9 +1476,8 @@ if o.bindlist ~= "no" then mp.add_timeout(0, function() -- wait for all other scripts to finish init o.ass_formatting = false o.no_ass_indent = " " - eval_ass_formatting() - io.write(pages[o.key_page_4].desc .. ":" .. - table.concat(get_kbinfo_lines(width)) .. "\n") + o.term_size = { w = width , h = 0} + io.write(keybinding_info(false, true) .. "\n") mp.command("quit") end) end diff --git a/player/main.c b/player/main.c index 27cf9b4..48d29b5 100644 --- a/player/main.c +++ b/player/main.c @@ -71,7 +71,7 @@ static const char def_config[] = ; #if HAVE_COCOA -#include "osdep/macosx_events.h" +#include "osdep/mac/app_bridge.h" #endif #ifndef FULLCONFIG @@ -184,16 +184,17 @@ void mp_destroy(struct MPContext *mpctx) cocoa_set_input_context(NULL); #endif - if (cas_terminal_owner(mpctx, mpctx)) { - terminal_uninit(); - cas_terminal_owner(mpctx, NULL); - } - mp_input_uninit(mpctx->input); uninit_libav(mpctx->global); mp_msg_uninit(mpctx->global); + + if (cas_terminal_owner(mpctx, mpctx)) { + terminal_uninit(); + cas_terminal_owner(mpctx, NULL); + } + assert(!mpctx->num_abort_list); talloc_free(mpctx->abort_list); mp_mutex_destroy(&mpctx->abort_lock); @@ -389,7 +390,7 @@ int mp_initialize(struct MPContext *mpctx, char **options) MP_STATS(mpctx, "start init"); #if HAVE_COCOA - mpv_handle *ctx = mp_new_client(mpctx->clients, "osx"); + mpv_handle *ctx = mp_new_client(mpctx->clients, "mac"); cocoa_set_mpv_handle(ctx); #endif @@ -419,7 +420,6 @@ int mp_initialize(struct MPContext *mpctx, char **options) int mpv_main(int argc, char *argv[]) { - mp_thread_set_name("mpv"); struct MPContext *mpctx = mp_create(); if (!mpctx) return 1; diff --git a/player/meson.build b/player/meson.build index dc334b8..be1e812 100644 --- a/player/meson.build +++ b/player/meson.build @@ -1,10 +1,11 @@ subdir('javascript') subdir('lua') -# Meson doesn't allow having multiple build targets with the same name in the same file. -# Just generate the com in here for windows builds. -if win32 and get_option('cplayer') +# Older versions of meson don't allow multiple build targets with the same name in the same +# file. Generate it here for compatibility reasons for windows. +if win32 and get_option('cplayer') and meson.version().version_compare('< 1.3.0') wrapper_sources= '../osdep/win32-console-wrapper.c' executable('mpv', wrapper_sources, c_args: '-municode', link_args: '-municode', name_suffix: 'com', install: true) + warning('mpv.com executable will be generated in the player subdirectory.') endif diff --git a/player/misc.c b/player/misc.c index b91d52a..1b265e1 100644 --- a/player/misc.c +++ b/player/misc.c @@ -147,7 +147,12 @@ double get_track_seek_offset(struct MPContext *mpctx, struct track *track) if (track->type == STREAM_AUDIO) return -opts->audio_delay; if (track->type == STREAM_SUB) - return -opts->subs_rend->sub_delay; + { + for (int n = 0; n < num_ptracks[STREAM_SUB]; n++) { + if (mpctx->current_track[n][STREAM_SUB] == track) + return -opts->subs_shared->sub_delay[n]; + } + } } return 0; } @@ -247,7 +252,8 @@ void error_on_track(struct MPContext *mpctx, struct track *track) if (track->type == STREAM_VIDEO) MP_INFO(mpctx, "Video: no video\n"); if (mpctx->opts->stop_playback_on_init_failure || - !(mpctx->vo_chain || mpctx->ao_chain)) + (!mpctx->current_track[0][STREAM_AUDIO] && + !mpctx->current_track[0][STREAM_VIDEO])) { if (!mpctx->stop_play) mpctx->stop_play = PT_ERROR; @@ -317,7 +323,7 @@ void merge_playlist_files(struct playlist *pl) edl = talloc_strdup_append_buffer(edl, e->filename); } playlist_clear(pl); - playlist_add_file(pl, edl); + playlist_append_file(pl, edl); talloc_free(edl); } diff --git a/player/osd.c b/player/osd.c index dc03229..96ab287 100644 --- a/player/osd.c +++ b/player/osd.c @@ -112,7 +112,7 @@ static void term_osd_update_title(struct MPContext *mpctx) void term_osd_set_subs(struct MPContext *mpctx, const char *text) { - if (mpctx->video_out || !text || !mpctx->opts->subs_rend->sub_visibility) + if (mpctx->video_out || !text || !mpctx->opts->subs_shared->sub_visibility[0]) text = ""; // disable if (strcmp(mpctx->term_osd_subs ? mpctx->term_osd_subs : "", text) == 0) return; @@ -190,10 +190,9 @@ static char *get_term_status_msg(struct MPContext *mpctx) saddf(&line, ": "); // Playback position - double speed = opts->term_remaining_playtime ? mpctx->video_speed : 1; sadd_hhmmssff(&line, get_playback_time(mpctx), opts->osd_fractions); saddf(&line, " / "); - sadd_hhmmssff(&line, get_time_length(mpctx) / speed, opts->osd_fractions); + sadd_hhmmssff(&line, get_time_length(mpctx), opts->osd_fractions); sadd_percentage(&line, get_percent_pos(mpctx)); diff --git a/player/playloop.c b/player/playloop.c index 60596da..12239d6 100644 --- a/player/playloop.c +++ b/player/playloop.c @@ -419,6 +419,7 @@ static void mp_seek(MPContext *mpctx, struct seek_params seek) update_ab_loop_clip(mpctx); mpctx->current_seek = seek; + redraw_subs(mpctx); } // This combines consecutive seek requests. @@ -665,6 +666,9 @@ static void handle_osd_redraw(struct MPContext *mpctx) if (!want_redraw) return; vo_redraw(mpctx->video_out); + // Even though we just redrew, it may need to be done again for certain + // cases of subtitles on an image. + redraw_subs(mpctx); } static void clear_underruns(struct MPContext *mpctx) @@ -799,6 +803,22 @@ int get_cache_buffering_percentage(struct MPContext *mpctx) return mpctx->demuxer ? mpctx->cache_buffer : -1; } +static void handle_update_subtitles(struct MPContext *mpctx) +{ + if (mpctx->video_status == STATUS_EOF) { + update_subtitles(mpctx, mpctx->playback_pts); + return; + } + + for (int n = 0; n < mpctx->num_tracks; n++) { + struct track *track = mpctx->tracks[n]; + if (track->type == STREAM_SUB && !track->demuxer_ready) { + update_subtitles(mpctx, mpctx->playback_pts); + break; + } + } +} + static void handle_cursor_autohide(struct MPContext *mpctx) { struct MPOpts *opts = mpctx->opts; @@ -1030,8 +1050,12 @@ int handle_force_window(struct MPContext *mpctx, bool force) break; } } + + // Use a 16:9 aspect ratio so that fullscreen on a 16:9 screen will not + // have vertical margins, which can lead to a different size or position + // of subtitles than with 16:9 videos. int w = 960; - int h = 480; + int h = 540; struct mp_image_params p = { .imgfmt = config_format, .w = w, .h = h, @@ -1132,6 +1156,7 @@ static void handle_playback_restart(struct MPContext *mpctx) mpctx->hrseek_active = false; mpctx->restart_complete = true; mpctx->current_seek = (struct seek_params){0}; + run_command_opts(mpctx); handle_playback_time(mpctx); mp_notify(mpctx, MPV_EVENT_PLAYBACK_RESTART, NULL); update_core_idle_state(mpctx); @@ -1224,8 +1249,8 @@ void run_playloop(struct MPContext *mpctx) handle_dummy_ticks(mpctx); update_osd_msg(mpctx); - if (mpctx->video_status == STATUS_EOF) - update_subtitles(mpctx, mpctx->playback_pts); + + handle_update_subtitles(mpctx); handle_each_frame_screenshot(mpctx); diff --git a/player/screenshot.c b/player/screenshot.c index e4d0912..aa637e6 100644 --- a/player/screenshot.c +++ b/player/screenshot.c @@ -77,7 +77,8 @@ static char *stripext(void *talloc_ctx, const char *s) } static bool write_screenshot(struct mp_cmd_ctx *cmd, struct mp_image *img, - const char *filename, struct image_writer_opts *opts) + const char *filename, struct image_writer_opts *opts, + bool overwrite) { struct MPContext *mpctx = cmd->mpctx; struct image_writer_opts *gopts = mpctx->opts->screenshot_image_opts; @@ -88,7 +89,7 @@ static bool write_screenshot(struct mp_cmd_ctx *cmd, struct mp_image *img, mp_core_unlock(mpctx); bool ok = img && write_image(img, &opts_copy, filename, mpctx->global, - mpctx->screenshot_ctx->log); + mpctx->screenshot_ctx->log, overwrite); mp_core_lock(mpctx); @@ -166,7 +167,7 @@ static char *create_fname(struct MPContext *mpctx, char *template, goto error_exit; char fmtstr[] = {'%', '0', digits, 'd', '\0'}; res = talloc_asprintf_append(res, fmtstr, *frameno); - if (*frameno < 100000 - 1) { + if (*frameno < INT_MAX - 1) { (*frameno) += 1; (*sequence) += 1; } @@ -496,7 +497,7 @@ void cmd_screenshot_to_file(void *p) cmd->success = false; return; } - cmd->success = write_screenshot(cmd, image, filename, &opts); + cmd->success = write_screenshot(cmd, image, filename, &opts, true); talloc_free(image); } @@ -537,7 +538,7 @@ void cmd_screenshot(void *p) if (image) { char *filename = gen_fname(cmd, image_writer_file_ext(opts)); if (filename) { - cmd->success = write_screenshot(cmd, image, filename, NULL); + cmd->success = write_screenshot(cmd, image, filename, NULL, false); if (cmd->success) { node_init(res, MPV_FORMAT_NODE_MAP, NULL); node_map_add_string(res, "filename", filename); diff --git a/player/sub.c b/player/sub.c index f3e42fe..65e5732 100644 --- a/player/sub.c +++ b/player/sub.c @@ -54,6 +54,19 @@ static void reset_subtitles(struct MPContext *mpctx, struct track *track) term_osd_set_subs(mpctx, NULL); } +// Only matters for subs on an image. +void redraw_subs(struct MPContext *mpctx) +{ + for (int n = 0; n < num_ptracks[STREAM_SUB]; n++) { + if (mpctx->current_track[n][STREAM_SUB] && + mpctx->current_track[n][STREAM_SUB]->d_sub) + { + mpctx->redraw_subs = true; + break; + } + } +} + void reset_subtitle_state(struct MPContext *mpctx) { for (int n = 0; n < mpctx->num_tracks; n++) @@ -100,33 +113,43 @@ static bool update_subtitle(struct MPContext *mpctx, double video_pts, sub_preload(dec_sub); } - if (!sub_read_packets(dec_sub, video_pts, mpctx->paused)) - return false; + bool packets_read = false; + bool sub_updated = false; + sub_read_packets(dec_sub, video_pts, mpctx->paused, &packets_read, &sub_updated); - // Handle displaying subtitles on terminal; never done for secondary subs - if (mpctx->current_track[0][STREAM_SUB] == track && !mpctx->video_out) { - char *text = sub_get_text(dec_sub, video_pts, SD_TEXT_TYPE_PLAIN); - term_osd_set_subs(mpctx, text); - talloc_free(text); - } + double osd_pts = osd_get_force_video_pts(mpctx->osd); + + // Check if we need to update subtitles for these special cases. Always + // update on discontinuities like seeking or a new file. + if (sub_updated || mpctx->redraw_subs || osd_pts == MP_NOPTS_VALUE) { + // Always force a redecode of all packets if we have a refresh. + if (mpctx->redraw_subs) + sub_redecode_cached_packets(dec_sub); + + // Handle displaying subtitles on terminal; never done for secondary subs + if (mpctx->current_track[0][STREAM_SUB] == track && !mpctx->video_out) { + char *text = sub_get_text(dec_sub, video_pts, SD_TEXT_TYPE_PLAIN); + term_osd_set_subs(mpctx, text); + talloc_free(text); + } - // Handle displaying subtitles on VO with no video being played. This is - // quite different, because normally subtitles are redrawn on new video - // frames, using the video frames' timestamps. - if (mpctx->video_out && mpctx->video_status == STATUS_EOF && - (mpctx->opts->subs_rend->sub_past_video_end || - !mpctx->current_track[0][STREAM_VIDEO] || - mpctx->current_track[0][STREAM_VIDEO]->image)) { - if (osd_get_force_video_pts(mpctx->osd) != video_pts) { - osd_set_force_video_pts(mpctx->osd, video_pts); - osd_query_and_reset_want_redraw(mpctx->osd); - vo_redraw(mpctx->video_out); - // Force an arbitrary minimum FPS - mp_set_timeout(mpctx, 0.1); + // Handle displaying subtitles on VO with no video being played. This is + // quite different, because normally subtitles are redrawn on new video + // frames, using the video frames' timestamps. + if (mpctx->video_out && mpctx->video_status == STATUS_EOF && + (mpctx->opts->subs_rend->sub_past_video_end || + !mpctx->current_track[0][STREAM_VIDEO] || + mpctx->current_track[0][STREAM_VIDEO]->image)) { + if (osd_pts != video_pts) { + osd_set_force_video_pts(mpctx->osd, video_pts); + osd_query_and_reset_want_redraw(mpctx->osd); + vo_redraw(mpctx->video_out); + } } } - return true; + mpctx->redraw_subs = false; + return packets_read; } // Return true if the subtitles for the given PTS are ready; false if the player @@ -199,12 +222,20 @@ void reinit_sub(struct MPContext *mpctx, struct track *track) sub_select(track->d_sub, true); int order = get_order(mpctx, track); osd_set_sub(mpctx->osd, order, track->d_sub); - sub_control(track->d_sub, SD_CTRL_SET_TOP, &order); // When paused we have to wait for packets to be available. - // So just retry until we get a packet in this case. - if (mpctx->playback_initialized) - while (!update_subtitles(mpctx, mpctx->playback_pts) && mpctx->paused); + // Retry on a timeout until we get a packet. If still not successful, + // then queue it for later in the playloop (but this will have a delay). + if (mpctx->playback_initialized) { + track->demuxer_ready = false; + int64_t end = mp_time_ns() + MP_TIME_MS_TO_NS(50); + while (!track->demuxer_ready && mp_time_ns() < end) + track->demuxer_ready = update_subtitles(mpctx, mpctx->playback_pts) || + !mpctx->paused; + if (!track->demuxer_ready) + mp_wakeup_core(mpctx); + + } } void reinit_sub_all(struct MPContext *mpctx) diff --git a/player/video.c b/player/video.c index 48a3165..f0372b6 100644 --- a/player/video.c +++ b/player/video.c @@ -120,6 +120,7 @@ void reset_video_state(struct MPContext *mpctx) mpctx->drop_message_shown = 0; mpctx->display_sync_drift_dir = 0; mpctx->display_sync_error = 0; + mpctx->display_sync_active = 0; mpctx->video_status = mpctx->vo_chain ? STATUS_SYNCING : STATUS_EOF; } @@ -129,9 +130,9 @@ void uninit_video_out(struct MPContext *mpctx) uninit_video_chain(mpctx); if (mpctx->video_out) { vo_destroy(mpctx->video_out); + mpctx->video_out = NULL; mp_notify(mpctx, MPV_EVENT_VIDEO_RECONFIG, NULL); } - mpctx->video_out = NULL; } static void vo_chain_uninit(struct vo_chain *vo_c) @@ -343,10 +344,9 @@ static void adjust_sync(struct MPContext *mpctx, double v_pts, double frame_time { struct MPOpts *opts = mpctx->opts; - if (mpctx->audio_status == STATUS_EOF) + if (mpctx->audio_status != STATUS_PLAYING) return; - mpctx->delay -= frame_time; double a_pts = written_audio_pts(mpctx) + opts->audio_delay - mpctx->delay; double av_delay = a_pts - v_pts; @@ -388,7 +388,9 @@ static void handle_new_frame(struct MPContext *mpctx) } } mpctx->time_frame += frame_time / mpctx->video_speed; - if (frame_time) + if (mpctx->ao_chain && mpctx->ao_chain->audio_started) + mpctx->delay -= frame_time; + if (mpctx->video_status >= STATUS_PLAYING) adjust_sync(mpctx, pts, frame_time); MP_TRACE(mpctx, "frametime=%5.3f\n", frame_time); } @@ -644,8 +646,9 @@ static void update_av_diff(struct MPContext *mpctx, double offset) if (mpctx->vo_chain && mpctx->vo_chain->is_sparse) return; - double a_pos = playing_audio_pts(mpctx); + double a_pos = written_audio_pts(mpctx); if (a_pos != MP_NOPTS_VALUE && mpctx->video_pts != MP_NOPTS_VALUE) { + a_pos -= mpctx->audio_speed * ao_get_delay(mpctx->ao); mpctx->last_av_difference = a_pos - mpctx->video_pts + opts->audio_delay + offset; } @@ -1041,19 +1044,6 @@ static void apply_video_crop(struct MPContext *mpctx, struct vo *vo) } } -static bool video_reconfig_needed(const struct mp_image_params *p1, - const struct mp_image_params *p2) -{ - return p1->imgfmt != p2->imgfmt || - p1->hw_subfmt != p2->hw_subfmt || - p1->w != p2->w || p1->h != p2->h || - p1->p_w != p2->p_w || p1->p_h != p2->p_h || - p1->force_window != p2->force_window || - p1->rotate != p2->rotate || - p1->stereo3d != p2->stereo3d || - !mp_rect_equals(&p1->crop, &p2->crop); -} - void write_video(struct MPContext *mpctx) { struct MPOpts *opts = mpctx->opts; @@ -1176,10 +1166,12 @@ void write_video(struct MPContext *mpctx) // Filter output is different from VO input? struct mp_image_params *p = &mpctx->next_frames[0]->params; - if (!vo->params || video_reconfig_needed(p, vo->params)) { + if (!vo->params || !mp_image_params_static_equal(p, vo->params)) { // Changing config deletes the current frame; wait until it's finished. - if (vo_still_displaying(vo)) + if (vo_still_displaying(vo)) { + vo_request_wakeup_on_done(vo); return; + } const struct vo_driver *info = mpctx->video_out->driver; char extra[20] = {0}; @@ -1257,7 +1249,7 @@ void write_video(struct MPContext *mpctx) diff /= mpctx->video_speed; if (mpctx->time_frame < 0) diff += mpctx->time_frame; - frame->duration = MPCLAMP(diff, 0, 10) * 1e9; + frame->duration = MP_TIME_S_TO_NS(MPCLAMP(diff, 0, 10)); } mpctx->video_pts = mpctx->next_frames[0]->pts; diff --git a/stream/stream.c b/stream/stream.c index dd67825..43a7f51 100644 --- a/stream/stream.c +++ b/stream/stream.c @@ -803,7 +803,7 @@ int stream_skip_bom(struct stream *s) struct bstr stream_read_complete(struct stream *s, void *talloc_ctx, int max_size) { - if (max_size > 1000000000) + if (max_size <= 0 || max_size > STREAM_MAX_READ_SIZE) abort(); int bufsize; diff --git a/stream/stream.h b/stream/stream.h index 423ba12..58b55e1 100644 --- a/stream/stream.h +++ b/stream/stream.h @@ -32,6 +32,9 @@ // it's guaranteed that you can seek back by <= of this size again. #define STREAM_BUFFER_SIZE 2048 +// Maximum size of a complete read. +#define STREAM_MAX_READ_SIZE (INT_MAX - 1) + // flags for stream_open_ext (this includes STREAM_READ and STREAM_WRITE) // stream->mode diff --git a/stream/stream_dvb.c b/stream/stream_dvb.c index 215e82c..96590d2 100644 --- a/stream/stream_dvb.c +++ b/stream/stream_dvb.c @@ -114,7 +114,7 @@ static fe_code_rate_t parse_fec(const char *cr) static fe_modulation_t parse_vdr_modulation(const char** modstring) { - const static struct { const char *s; fe_modulation_t v; } table[] = { + static const struct { const char *s; fe_modulation_t v; } table[] = { { "16", QAM_16 }, { "32", QAM_32 }, { "64", QAM_64 }, diff --git a/stream/stream_dvdnav.c b/stream/stream_dvdnav.c index d858c51..5dba92a 100644 --- a/stream/stream_dvdnav.c +++ b/stream/stream_dvdnav.c @@ -497,7 +497,7 @@ static int control(stream_t *stream, int cmd, void *arg) struct stream_dvd_info_req *req = arg; memset(req, 0, sizeof(*req)); req->num_subs = mp_dvdnav_number_of_subs(stream); - assert(sizeof(uint32_t) == sizeof(unsigned int)); + static_assert(sizeof(uint32_t) == sizeof(unsigned int), ""); memcpy(req->palette, priv->spu_clut, sizeof(req->palette)); return STREAM_OK; } diff --git a/stream/stream_file.c b/stream/stream_file.c index 4895a83..89e7a2d 100644 --- a/stream/stream_file.c +++ b/stream/stream_file.c @@ -50,6 +50,7 @@ #ifdef _WIN32 #include <windows.h> +#include <winioctl.h> #include <winternl.h> #include <io.h> @@ -306,6 +307,7 @@ static int open_f(stream_t *stream, const struct stream_open_args *args) } struct stat st; + bool is_sock_or_fifo = false; if (fstat(p->fd, &st) == 0) { if (S_ISDIR(st.st_mode)) { stream->is_directory = true; @@ -319,6 +321,9 @@ static int open_f(stream_t *stream, const struct stream_open_args *args) fcntl(p->fd, F_SETFL, val); #endif } else { +#ifndef __MINGW32__ + is_sock_or_fifo = S_ISSOCK(st.st_mode) || S_ISFIFO(st.st_mode); +#endif p->use_poll = true; } } @@ -340,7 +345,7 @@ static int open_f(stream_t *stream, const struct stream_open_args *args) stream->get_size = get_size; stream->close = s_close; - if (check_stream_network(p->fd)) { + if (is_sock_or_fifo || check_stream_network(p->fd)) { stream->streaming = true; #if HAVE_COCOA if (fcntl(p->fd, F_RDAHEAD, 0) < 0) { diff --git a/stream/stream_lavf.c b/stream/stream_lavf.c index c153ddd..a471c08 100644 --- a/stream/stream_lavf.c +++ b/stream/stream_lavf.c @@ -324,7 +324,7 @@ static int open_f(stream_t *stream) if (err < 0) { if (err == AVERROR_PROTOCOL_NOT_FOUND) MP_ERR(stream, "Protocol not found. Make sure" - " ffmpeg/Libav is compiled with networking support.\n"); + " FFmpeg is compiled with networking support.\n"); goto out; } diff --git a/sub/ass_mp.c b/sub/ass_mp.c index 634681f..3e9f833 100644 --- a/sub/ass_mp.c +++ b/sub/ass_mp.c @@ -341,7 +341,7 @@ static bool pack_rgba(struct mp_ass_packer *p, struct sub_bitmaps *res) // repacks all images). preferred_osd_format can be set to a desired // sub_bitmap_format. Currently, only SUBBITMAP_LIBASS is supported. void mp_ass_packer_pack(struct mp_ass_packer *p, ASS_Image **image_lists, - int num_image_lists, bool image_lists_changed, + int num_image_lists, bool image_lists_changed, bool video_color_space, int preferred_osd_format, struct sub_bitmaps *out) { int format = preferred_osd_format == SUBBITMAP_BGRA ? SUBBITMAP_BGRA @@ -361,6 +361,7 @@ void mp_ass_packer_pack(struct mp_ass_packer *p, ASS_Image **image_lists, .change_id = image_lists_changed, .format = SUBBITMAP_LIBASS, .parts = p->cached_parts, + .video_color_space = video_color_space, }; for (int n = 0; n < num_image_lists; n++) { diff --git a/sub/ass_mp.h b/sub/ass_mp.h index dc83e31..b4e16d6 100644 --- a/sub/ass_mp.h +++ b/sub/ass_mp.h @@ -57,7 +57,7 @@ struct sub_bitmaps; struct mp_ass_packer; struct mp_ass_packer *mp_ass_packer_alloc(void *ta_parent); void mp_ass_packer_pack(struct mp_ass_packer *p, ASS_Image **image_lists, - int num_image_lists, bool changed, + int num_image_lists, bool changed, bool video_color_space, int preferred_osd_format, struct sub_bitmaps *out); void mp_ass_get_bb(ASS_Image *image_list, ASS_Track *track, struct mp_osd_res *res, double *out_rc); diff --git a/sub/dec_sub.c b/sub/dec_sub.c index 18d826e..94ff3ba 100644 --- a/sub/dec_sub.c +++ b/sub/dec_sub.c @@ -20,6 +20,7 @@ #include <string.h> #include <math.h> #include <assert.h> +#include <limits.h> #include "demux/demux.h" #include "sd.h" @@ -47,7 +48,9 @@ struct dec_sub { struct mp_log *log; struct mpv_global *global; struct mp_subtitle_opts *opts; + struct mp_subtitle_shared_opts *shared_opts; struct m_config_cache *opts_cache; + struct m_config_cache *shared_opts_cache; struct mp_recorder_sink *recorder_sink; @@ -60,6 +63,7 @@ struct dec_sub { bool preload_attempted; double video_fps; double sub_speed; + bool sub_visible; struct mp_codec_params *codec; double start, end; @@ -68,7 +72,9 @@ struct dec_sub { struct sd *sd; struct demux_packet *new_segment; - struct demux_packet *cached_pkts[2]; + struct demux_packet **cached_pkts; + int cached_pkt_pos; + int num_cached_pkts; }; static void update_subtitle_speed(struct dec_sub *sub) @@ -91,20 +97,22 @@ static void update_subtitle_speed(struct dec_sub *sub) // Return the subtitle PTS used for a given video PTS. static double pts_to_subtitle(struct dec_sub *sub, double pts) { - struct mp_subtitle_opts *opts = sub->opts; + struct mp_subtitle_shared_opts *opts = sub->shared_opts; + float delay = sub->order < 0 ? 0.0f : opts->sub_delay[sub->order]; if (pts != MP_NOPTS_VALUE) - pts = (pts * sub->play_dir - opts->sub_delay) / sub->sub_speed; + pts = (pts * sub->play_dir - delay) / sub->sub_speed; return pts; } static double pts_from_subtitle(struct dec_sub *sub, double pts) { - struct mp_subtitle_opts *opts = sub->opts; + struct mp_subtitle_shared_opts *opts = sub->shared_opts; + float delay = sub->order < 0 ? 0.0f : opts->sub_delay[sub->order]; if (pts != MP_NOPTS_VALUE) - pts = (pts * sub->sub_speed + opts->sub_delay) * sub->play_dir; + pts = (pts * sub->sub_speed + delay) * sub->play_dir; return pts; } @@ -115,6 +123,17 @@ static void wakeup_demux(void *ctx) mp_dispatch_interrupt(q); } +static void sub_destroy_cached_pkts(struct dec_sub *sub) +{ + int index = 0; + while (index < sub->num_cached_pkts) { + TA_FREEP(&sub->cached_pkts[index]); + ++index; + } + sub->cached_pkt_pos = 0; + sub->num_cached_pkts = 0; +} + void sub_destroy(struct dec_sub *sub) { if (!sub) @@ -138,7 +157,9 @@ static struct sd *init_decoder(struct dec_sub *sub) .global = sub->global, .log = mp_log_new(sd, sub->log, driver->name), .opts = sub->opts, + .shared_opts = sub->shared_opts, .driver = driver, + .order = sub->order, .attachments = sub->attachments, .codec = sub->codec, .preload_ok = true, @@ -170,6 +191,7 @@ struct dec_sub *sub_create(struct mpv_global *global, struct track *track, .log = mp_log_new(sub, global->log, "sub"), .global = global, .opts_cache = m_config_cache_alloc(sub, global, &mp_subtitle_sub_opts), + .shared_opts_cache = m_config_cache_alloc(sub, global, &mp_subtitle_shared_sub_opts), .sh = track->stream, .codec = track->stream->codec, .attachments = talloc_steal(sub, attachments), @@ -181,6 +203,7 @@ struct dec_sub *sub_create(struct mpv_global *global, struct track *track, .end = MP_NOPTS_VALUE, }; sub->opts = sub->opts_cache->opts; + sub->shared_opts = sub->shared_opts_cache->opts; mp_mutex_init_type(&sub->lock, MP_MUTEX_RECURSIVE); sub->sd = init_decoder(sub); @@ -211,7 +234,6 @@ static void update_segment(struct dec_sub *sub) talloc_free(sub->sd); sub->sd = new; update_subtitle_speed(sub); - sub_control(sub, SD_CTRL_SET_TOP, &sub->order); } else { // We'll just keep the current decoder, and feed it possibly // invalid data (not our fault if it crashes or something). @@ -251,7 +273,7 @@ void sub_preload(struct dec_sub *sub) if (!pkt) break; sub->sd->driver->decode(sub->sd, pkt); - talloc_free(pkt); + MP_TARRAY_APPEND(sub, sub->cached_pkts, sub->num_cached_pkts, pkt); } demux_set_stream_wakeup_cb(sub->sh, NULL, NULL); @@ -266,12 +288,51 @@ static bool is_new_segment(struct dec_sub *sub, struct demux_packet *p) (p->start != sub->start || p->end != sub->end || p->codec != sub->codec); } -// Read packets from the demuxer stream passed to sub_create(). Return true if -// enough packets were read, false if the player should wait until the demuxer -// signals new packets available (and then should retry). -bool sub_read_packets(struct dec_sub *sub, double video_pts, bool force) +static bool is_packet_visible(struct demux_packet *p, double video_pts) +{ + return p && p->pts <= video_pts && (video_pts <= p->pts + p->sub_duration || + p->sub_duration < 0); +} + +static bool update_pkt_cache(struct dec_sub *sub, double video_pts) +{ + if (!sub->cached_pkts[sub->cached_pkt_pos]) + return false; + + struct demux_packet *pkt = sub->cached_pkts[sub->cached_pkt_pos]; + struct demux_packet *next_pkt = sub->cached_pkt_pos + 1 < sub->num_cached_pkts ? + sub->cached_pkts[sub->cached_pkt_pos + 1] : NULL; + if (!pkt) + return false; + + double pts = video_pts + sub->shared_opts->sub_delay[sub->order]; + double next_pts = next_pkt ? next_pkt->pts : INT_MAX; + double end_pts = pkt->sub_duration >= 0 ? pkt->pts + pkt->sub_duration : INT_MAX; + + if (next_pts < pts || end_pts < pts) { + if (sub->cached_pkt_pos + 1 < sub->num_cached_pkts) { + TA_FREEP(&sub->cached_pkts[sub->cached_pkt_pos]); + pkt = NULL; + sub->cached_pkt_pos++; + } + if (next_pts < pts) + return true; + } + + if (pkt && pkt->animated) + return true; + + return false; +} + +// Read packets from the demuxer stream passed to sub_create(). Signals if +// enough packets were read and if the subtitle state updated in anyway. If +// packets_read is false, the player should wait until the demuxer signals new +// packets and retry. +void sub_read_packets(struct dec_sub *sub, double video_pts, bool force, + bool *packets_read, bool *sub_updated) { - bool r = true; + *packets_read = true; mp_mutex_lock(&sub->lock); video_pts = pts_to_subtitle(sub, video_pts); while (1) { @@ -291,7 +352,8 @@ bool sub_read_packets(struct dec_sub *sub, double video_pts, bool force) break; // (Use this mechanism only if sub_delay matters to avoid corner cases.) - double min_pts = sub->opts->sub_delay < 0 || force ? video_pts : MP_NOPTS_VALUE; + float delay = sub->order < 0 ? 0.0f : sub->shared_opts->sub_delay[sub->order]; + double min_pts = delay < 0 || force ? video_pts : MP_NOPTS_VALUE; struct demux_packet *pkt; int st = demux_read_packet_async_until(sub->sh, min_pts, &pkt); @@ -301,24 +363,16 @@ bool sub_read_packets(struct dec_sub *sub, double video_pts, bool force) // happen for interleaved subtitle streams, which never return "wait" // when reading, unless min_pts is set. if (st <= 0) { - r = st < 0 || (sub->last_pkt_pts != MP_NOPTS_VALUE && - sub->last_pkt_pts > video_pts); + *packets_read = st < 0 || (sub->last_pkt_pts != MP_NOPTS_VALUE && + sub->last_pkt_pts > video_pts); break; } if (sub->recorder_sink) mp_recorder_feed_packet(sub->recorder_sink, pkt); - - // Update cached packets - if (sub->cached_pkts[0]) { - if (sub->cached_pkts[1]) - talloc_free(sub->cached_pkts[1]); - sub->cached_pkts[1] = sub->cached_pkts[0]; - } - sub->cached_pkts[0] = pkt; - sub->last_pkt_pts = pkt->pts; + MP_TARRAY_APPEND(sub, sub->cached_pkts, sub->num_cached_pkts, pkt); if (is_new_segment(sub, pkt)) { sub->new_segment = demux_copy_packet(pkt); @@ -330,19 +384,24 @@ bool sub_read_packets(struct dec_sub *sub, double video_pts, bool force) if (!(sub->preload_attempted && sub->sd->preload_ok)) sub->sd->driver->decode(sub->sd, pkt); } + if (sub->cached_pkts && sub->num_cached_pkts) { + bool visible = is_packet_visible(sub->cached_pkts[sub->cached_pkt_pos], video_pts); + *sub_updated = update_pkt_cache(sub, video_pts) || sub->sub_visible != visible; + sub->sub_visible = visible; + } mp_mutex_unlock(&sub->lock); - return r; } -// Redecode both cached packets if needed. +// Redecode all cached packets if needed. // Used with UPDATE_SUB_HARD and UPDATE_SUB_FILT. void sub_redecode_cached_packets(struct dec_sub *sub) { mp_mutex_lock(&sub->lock); - if (sub->cached_pkts[0]) - sub->sd->driver->decode(sub->sd, sub->cached_pkts[0]); - if (sub->cached_pkts[1]) - sub->sd->driver->decode(sub->sd, sub->cached_pkts[1]); + int index = sub->cached_pkt_pos; + while (index < sub->num_cached_pkts) { + sub->sd->driver->decode(sub->sd, sub->cached_pkts[index]); + ++index; + } mp_mutex_unlock(&sub->lock); } @@ -417,8 +476,7 @@ void sub_reset(struct dec_sub *sub) sub->sd->driver->reset(sub->sd); sub->last_pkt_pts = MP_NOPTS_VALUE; sub->last_vo_pts = MP_NOPTS_VALUE; - TA_FREEP(&sub->cached_pkts[0]); - TA_FREEP(&sub->cached_pkts[1]); + sub_destroy_cached_pkts(sub); TA_FREEP(&sub->new_segment); mp_mutex_unlock(&sub->lock); } @@ -455,6 +513,7 @@ int sub_control(struct dec_sub *sub, enum sd_ctrl cmd, void *arg) int flags = (uintptr_t)arg; if (m_config_cache_update(sub->opts_cache)) update_subtitle_speed(sub); + m_config_cache_update(sub->shared_opts_cache); propagate = true; if (flags & UPDATE_SUB_HARD) { // forget about the previous preload because @@ -489,10 +548,10 @@ void sub_set_play_dir(struct dec_sub *sub, int dir) bool sub_is_primary_visible(struct dec_sub *sub) { - return !!sub->opts->sub_visibility; + return sub->shared_opts->sub_visibility[0]; } bool sub_is_secondary_visible(struct dec_sub *sub) { - return !!sub->opts->sec_sub_visibility; + return sub->shared_opts->sub_visibility[1]; } diff --git a/sub/dec_sub.h b/sub/dec_sub.h index 9de6760..eb8406c 100644 --- a/sub/dec_sub.h +++ b/sub/dec_sub.h @@ -17,7 +17,6 @@ struct sd; enum sd_ctrl { SD_CTRL_SUB_STEP, SD_CTRL_SET_VIDEO_PARAMS, - SD_CTRL_SET_TOP, SD_CTRL_SET_VIDEO_DEF_FPS, SD_CTRL_UPDATE_OPTS, }; @@ -44,7 +43,8 @@ void sub_destroy(struct dec_sub *sub); bool sub_can_preload(struct dec_sub *sub); void sub_preload(struct dec_sub *sub); void sub_redecode_cached_packets(struct dec_sub *sub); -bool sub_read_packets(struct dec_sub *sub, double video_pts, bool force); +void sub_read_packets(struct dec_sub *sub, double video_pts, bool force, + bool *packets_read, bool *sub_updated); struct sub_bitmaps *sub_get_bitmaps(struct dec_sub *sub, struct mp_osd_res dim, int format, double pts); char *sub_get_text(struct dec_sub *sub, double pts, enum sd_text_type type); diff --git a/sub/draw_bmp.c b/sub/draw_bmp.c index 58db162..78e29f4 100644 --- a/sub/draw_bmp.c +++ b/sub/draw_bmp.c @@ -431,7 +431,7 @@ static bool render_rgba(struct mp_draw_sub_cache *p, struct part *part, mp_image_set_size(&src_img, sw, sh); src_img.planes[0] = s_ptr; src_img.stride[0] = s_stride; - src_img.params.alpha = MP_ALPHA_PREMUL; + src_img.params.repr.alpha = PL_ALPHA_PREMULTIPLIED; scaled = mp_image_alloc(IMGFMT_BGRA, dw, dh); if (!scaled) @@ -525,7 +525,7 @@ static bool reinit_to_video(struct mp_draw_sub_cache *p) struct mp_image_params *params = &p->params; mp_image_params_guess_csp(params); - bool need_premul = params->alpha != MP_ALPHA_PREMUL && + bool need_premul = params->repr.alpha != PL_ALPHA_PREMULTIPLIED && (mp_imgfmt_get_desc(params->imgfmt).flags & MP_IMGFLAG_ALPHA); // Intermediate format for video_overlay. Requirements: @@ -546,7 +546,7 @@ static bool reinit_to_video(struct mp_draw_sub_cache *p) mp_get_regular_imgfmt(&vfdesc, mp_repack_get_format_dst(p->video_to_f32)); assert(vfdesc.num_planes); // must have succeeded - if (params->color.space == MP_CSP_RGB && vfdesc.num_planes >= 3) { + if (params->repr.sys == PL_COLOR_SYSTEM_RGB && vfdesc.num_planes >= 3) { use_shortcut = true; if (vfdesc.component_type == MP_COMPONENT_TYPE_UINT && @@ -660,9 +660,11 @@ static bool reinit_to_video(struct mp_draw_sub_cache *p) return false; mp_image_params_guess_csp(&p->rgba_overlay->params); - p->rgba_overlay->params.alpha = MP_ALPHA_PREMUL; + p->rgba_overlay->params.repr.alpha = PL_ALPHA_PREMULTIPLIED; + p->overlay_tmp->params.repr = params->repr; p->overlay_tmp->params.color = params->color; + p->video_tmp->params.repr = params->repr; p->video_tmp->params.color = params->color; if (p->rgba_overlay->imgfmt == overlay_fmt) { @@ -675,12 +677,13 @@ static bool reinit_to_video(struct mp_draw_sub_cache *p) if (!p->video_overlay) return false; + p->video_overlay->params.repr = params->repr; p->video_overlay->params.color = params->color; p->video_overlay->params.chroma_location = params->chroma_location; - p->video_overlay->params.alpha = MP_ALPHA_PREMUL; + p->video_overlay->params.repr.alpha = PL_ALPHA_PREMULTIPLIED; if (p->scale_in_tiles) - p->video_overlay->params.chroma_location = MP_CHROMA_CENTER; + p->video_overlay->params.chroma_location = PL_CHROMA_CENTER; p->rgba_to_overlay = alloc_scaler(p); p->rgba_to_overlay->allow_zimg = true; @@ -724,13 +727,14 @@ static bool reinit_to_video(struct mp_draw_sub_cache *p) p->alpha_overlay->stride[0] = p->video_overlay->stride[aplane]; // Full range gray always has the same range as alpha. - p->alpha_overlay->params.color.levels = MP_CSP_LEVELS_PC; + p->alpha_overlay->params.repr.levels = PL_COLOR_LEVELS_FULL; mp_image_params_guess_csp(&p->alpha_overlay->params); p->calpha_overlay = talloc_steal(p, mp_image_alloc(calpha_fmt, w >> xs, h >> ys)); if (!p->calpha_overlay) return false; + p->calpha_overlay->params.repr = p->alpha_overlay->params.repr; p->calpha_overlay->params.color = p->alpha_overlay->params.color; p->calpha_to_f32 = mp_repack_create_planar(calpha_fmt, false, rflags); @@ -762,7 +766,7 @@ static bool reinit_to_video(struct mp_draw_sub_cache *p) if (!p->premul_tmp) return false; mp_image_set_params(p->premul_tmp, params); - p->premul_tmp->params.alpha = MP_ALPHA_PREMUL; + p->premul_tmp->params.repr.alpha = PL_ALPHA_PREMULTIPLIED; // Only zimg supports this. p->premul->force_scaler = MP_SWS_ZIMG; @@ -787,7 +791,7 @@ static bool reinit_to_overlay(struct mp_draw_sub_cache *p) return false; mp_image_params_guess_csp(&p->rgba_overlay->params); - p->rgba_overlay->params.alpha = MP_ALPHA_PREMUL; + p->rgba_overlay->params.repr.alpha = PL_ALPHA_PREMULTIPLIED; // Some non-sense with the intention to somewhat isolate the returned image. mp_image_setfmt(&p->res_overlay, p->rgba_overlay->imgfmt); diff --git a/sub/draw_bmp.h b/sub/draw_bmp.h index fda7797..b4c7378 100644 --- a/sub/draw_bmp.h +++ b/sub/draw_bmp.h @@ -15,7 +15,7 @@ struct mp_draw_sub_cache *mp_draw_sub_alloc_test(struct mp_image *dst); // Render the sub-bitmaps in sbs_list to dst. sbs_list must have been rendered // for an OSD resolution equivalent to dst's size (UB if not). -// Warning: if dst is a format with alpha, and dst is not set to MP_ALPHA_PREMUL +// Warning: if dst is a format with alpha, and dst is not set to PL_ALPHA_PREMULTIPLIED // (not done by default), this will be extremely slow. // Warning: the caller is responsible for ensuring that dst is writable. // cache: allocated instance; caches non-changing OSD parts etc. diff --git a/sub/filter_sdh.c b/sub/filter_sdh.c index 69fca9f..5adc1f9 100644 --- a/sub/filter_sdh.c +++ b/sub/filter_sdh.c @@ -33,6 +33,13 @@ // all SDH parts. // It is for filtering ASS encoded subtitles +static const char *const enclosure_pair[][2] = { + {"(", ")"}, + {"[", "]"}, + {"\uFF08", "\uFF09"}, + {0}, +}; + struct buffer { char *string; int length; @@ -58,6 +65,47 @@ static inline int append(struct sd_filter *sd, struct buffer *buf, char c) return c; } +static int get_char_bytes(char *str) +{ + // In case the first character is non-ASCII. + // Will only work with UTF-8 but you shouldn't be + // using anything else anyway. + if (str && str[0]) { + if (!(str[0] >> 7 & 1)) { + return strnlen(str, 1); + } else if (!(str[0] >> 5 & 1)) { + return strnlen(str, 2); + } else if (!(str[0] >> 4 & 1)) { + return strnlen(str, 3); + } else if (!(str[0] >> 3 & 1)) { + return strnlen(str, 4); + } + } + return 0; +} + +static const char *get_right_enclosure(char *left) +{ + // See if the right hand character is mapped. If not, just return the same thing. + for (int i = 0; enclosure_pair[i][0]; i++) { + if (strcmp(left, enclosure_pair[i][0]) == 0) + return enclosure_pair[i][1]; + } + return left; +} + +static bool valid_left_enclosure(struct sd_filter *sd, char *str) +{ + // All characters in this string are valid left hand enclosure characters. + char *enclosures = sd->opts->sub_filter_SDH_enclosures; + int len = strlen(enclosures); + for (int i = 0; i < len; i++) { + if (str && str[0] && str[0] == enclosures[i]) + return true; + } + return false; +} + // copy ass override tags, if they exist att current position, // from source string to destination buffer stopping at first @@ -86,7 +134,8 @@ static void copy_ass(struct sd_filter *sd, char **rpp, struct buffer *buf) return; } -static bool skip_bracketed(struct sd_filter *sd, char **rpp, struct buffer *buf); +static bool skip_enclosed(struct sd_filter *sd, char **rpp, struct buffer *buf, + const char *left, const char *right); // check for speaker label, like MAN: // normal subtitles may include mixed case text with : after so @@ -128,7 +177,7 @@ static void skip_speaker_label(struct sd_filter *sd, char **rpp, struct buffer * copy_ass(sd, &rp, buf); } else if (rp[0] == '[') { // not uncommon with [xxxx]: which should also be skipped - if (!skip_bracketed(sd, &rp, buf)) { + if (!skip_enclosed(sd, &rp, buf, "[", "]")) { buf->pos = old_pos; return; } @@ -174,94 +223,56 @@ static void skip_speaker_label(struct sd_filter *sd, char **rpp, struct buffer * return; } -// check for bracketed text, like [SOUND] -// and skip it while preserving ass tags -// any characters are allowed, brackets are seldom used in normal text +// Check for text enclosed in symbols, like (SOUND) +// and skip it while preserving ass tags. +// Parentheses are a special case since normal subtitles may have +// them so only upper case is accepted and lower case l which for +// some looks like upper case I. If sub_filter_SDH_harder is used, +// both upper and lower case is accepted. // -// Parameters: -// rpp read pointer pointer to source string, updated on return -// buf write buffer -// -// scan in source string -// the first character in source string must by the starting '[' -// and copy ass tags to destination string but -// skipping bracketed text if it looks like SDH -// -// return true if bracketed text was removed. -// if not valid SDH read pointer and write buffer position will be unchanged -// otherwise they point to next position after text and next write position -static bool skip_bracketed(struct sd_filter *sd, char **rpp, struct buffer *buf) -{ - char *rp = *rpp; - int old_pos = buf->pos; - - rp++; // skip past '[' - // skip past valid data searching for ] - while (*rp && rp[0] != ']') { - if (rp[0] == '{') { - copy_ass(sd, &rp, buf); - } else { - rp++; - } - } - if (!*rp) { - // ] was not found - buf->pos = old_pos; - return false; - } - rp++; // skip ] - // skip trailing spaces - while (rp[0] == ' ') { - rp++; - } - *rpp = rp; - - return true; -} - -// check for parenthesized text, like (SOUND) -// and skip it while preserving ass tags -// normal subtitles may include mixed case text in parentheses so -// only upper case is accepted and lower case l which for some -// looks like upper case I but if requested harder filtering -// both upper and lower case is accepted +// For other symbols, all text in between is removed. // // Parameters: // rpp read pointer pointer to source string, updated on return // buf write buffer // // scan in source string -// the first character in source string must be the starting '(' +// the first character in source string must be the starting left symbol // and copy ass tags to destination string but -// skipping parenthesized text if it looks like SDH +// skipping enclosed text if it looks like SDH // -// return true if parenthesized text was removed. +// return true if enclosed text was removed. // if not valid SDH read pointer and write buffer position will be unchanged // otherwise they point to next position after text and next write position -static bool skip_parenthesized(struct sd_filter *sd, char **rpp, struct buffer *buf) +static bool skip_enclosed(struct sd_filter *sd, char **rpp, struct buffer *buf, + const char *left, const char *right) { - int filter_harder = sd->opts->sub_filter_SDH_harder; + bool filter_harder = sd->opts->sub_filter_SDH_harder; char *rp = *rpp; int old_pos = buf->pos; + bool parenthesis = strcmp(left, "(") == 0 || strcmp(left, "\uFF08") == 0; - rp++; // skip past '(' - // skip past valid data searching for ) - bool only_digits = true; - while (*rp && rp[0] != ')') { + // skip past the left character + rp += get_char_bytes(rp); + // skip past valid data searching for the right character + bool only_digits = parenthesis; + while (*rp && rp[0] != right[0]) { if (rp[0] == '{') { copy_ass(sd, &rp, buf); - } else if ((mp_isalpha(rp[0]) && + } else if (parenthesis && ((mp_isalpha(rp[0]) && (filter_harder || mp_isupper(rp[0]) || rp[0] == 'l')) || mp_isdigit(rp[0]) || rp[0] == ' ' || rp[0] == '\'' || rp[0] == '#' || rp[0] == '.' || rp[0] == ',' || - rp[0] == '-' || rp[0] == '"' || rp[0] == '\\') { + rp[0] == '-' || rp[0] == '"' || rp[0] == '\\')) { if (!mp_isdigit(rp[0])) only_digits = false; rp++; - } else { + } else if (parenthesis) { buf->pos = old_pos; return false; + } else { + rp++; } } if (!*rp) { @@ -274,7 +285,8 @@ static bool skip_parenthesized(struct sd_filter *sd, char **rpp, struct buffer * buf->pos = old_pos; return false; } - rp++; // skip ) + // skip past the right character + rp += get_char_bytes(rp); // skip trailing spaces while (rp[0] == ' ') { rp++; @@ -371,14 +383,17 @@ static char *filter_SDH(struct sd_filter *sd, char *data, int length, ptrdiff_t // go through the rest of the line looking for SDH in () or [] while (*rp && !(rp[0] == '\\' && rp[1] == 'N')) { copy_ass(sd, &rp, buf); - if (rp[0] == '[') { - if (!skip_bracketed(sd, &rp, buf)) { - append(sd, buf, rp[0]); - rp++; - line_with_text = true; - } - } else if (rp[0] == '(') { - if (!skip_parenthesized(sd, &rp, buf)) { + char left[5] = {0}; + const char *right = NULL; + if (valid_left_enclosure(sd, rp)) { + int bytes = get_char_bytes(rp); + for (int i = 0; i < bytes; i++) + left[i] = rp[i]; + left[bytes] = '\0'; + right = get_right_enclosure(left); + } + if (left[0] && right && right[0]) { + if (!skip_enclosed(sd, &rp, buf, left, right)) { append(sd, buf, rp[0]); rp++; line_with_text = true; diff --git a/sub/img_convert.c b/sub/img_convert.c index a70bb0a..3c18e17 100644 --- a/sub/img_convert.c +++ b/sub/img_convert.c @@ -31,7 +31,8 @@ void mp_blur_rgba_sub_bitmap(struct sub_bitmap *d, double gblur) { struct mp_image *tmp1 = mp_image_alloc(IMGFMT_BGRA, d->w, d->h); - if (tmp1) { // on OOM, skip region + MP_HANDLE_OOM(tmp1); + { struct mp_image s = {0}; mp_image_setfmt(&s, IMGFMT_BGRA); mp_image_set_size(&s, d->w, d->h); @@ -119,7 +119,7 @@ bool osd_res_equals(struct mp_osd_res a, struct mp_osd_res b) struct osd_state *osd_create(struct mpv_global *global) { - assert(MAX_OSD_PARTS >= OSDTYPE_COUNT); + static_assert(MAX_OSD_PARTS >= OSDTYPE_COUNT, ""); struct osd_state *osd = talloc_zero(NULL, struct osd_state); *osd = (struct osd_state) { @@ -74,6 +74,8 @@ struct sub_bitmaps { int packed_w, packed_h; int change_id; // Incremented on each change (0 is never used) + + bool video_color_space; // True if the bitmap is in video color space }; struct sub_bitmap_list { @@ -243,5 +245,6 @@ void osd_set_external(struct osd_state *osd, struct osd_external_ass *ov); void osd_set_external_remove_owner(struct osd_state *osd, void *owner); void osd_get_text_size(struct osd_state *osd, int *out_screen_h, int *out_font_h); void osd_get_function_sym(char *buffer, size_t buffer_size, int osd_function); +void osd_mangle_ass(bstr *dst, const char *in, bool replace_newlines); #endif /* MPLAYER_SUB_H */ diff --git a/sub/osd_libass.c b/sub/osd_libass.c index a3b19c9..f2de27b 100644 --- a/sub/osd_libass.c +++ b/sub/osd_libass.c @@ -193,7 +193,7 @@ void osd_get_function_sym(char *buffer, size_t buffer_size, int osd_function) snprintf(buffer, buffer_size, "\xFF%c", osd_function); } -static void mangle_ass(bstr *dst, const char *in) +void osd_mangle_ass(bstr *dst, const char *in, bool replace_newlines) { const char *start = in; bool escape_ass = true; @@ -213,6 +213,14 @@ static void mangle_ass(bstr *dst, const char *in) } if (escape_ass && *in == '{') bstr_xappend(NULL, dst, bstr0("\\")); + // Replace newlines with \N for escape-ass. This is necessary to apply + // ASS tags past newlines and to preserve consecutive newlines with + // osd-overlay because update_external() adds a ASS event per line. + if (replace_newlines && *in == '\n') { + bstr_xappend(NULL, dst, bstr0("\\N")); + in += 1; + continue; + } // Libass will strip leading whitespace if (in[0] == ' ' && (in == start || in[-1] == '\n')) { bstr_xappend(NULL, dst, bstr0("\\h")); @@ -231,7 +239,7 @@ static ASS_Event *add_osd_ass_event_escaped(ASS_Track *track, const char *style, const char *text) { bstr buf = {0}; - mangle_ass(&buf, text); + osd_mangle_ass(&buf, text, false); ASS_Event *e = add_osd_ass_event(track, style, buf.start); talloc_free(buf.start); return e; @@ -378,12 +386,7 @@ static void get_osd_bar_box(struct osd_state *osd, struct osd_object *obj, *o_w = track->PlayResX * (opts->osd_bar_w / 100.0); *o_h = track->PlayResY * (opts->osd_bar_h / 100.0); - float base_size = 0.03125; - style->Outline *= *o_h / track->PlayResY / base_size; - // So that the chapter marks have space between them - style->Outline = MPMIN(style->Outline, *o_h / 5.2); - // So that the border is not 0 - style->Outline = MPMAX(style->Outline, *o_h / 32.0); + style->Outline = opts->osd_bar_border_size; // Rendering with shadow is broken (because there's more than one shape) style->Shadow = 0; @@ -471,7 +474,7 @@ static void update_progbar(struct osd_state *osd, struct osd_object *obj) // chapter marks for (int n = 0; n < obj->progbar_state.num_stops; n++) { float s = obj->progbar_state.stops[n] * width; - float dent = border * 1.3; + float dent = MPMAX(border * 1.3, 1.6); if (s > dent && s < width - dent) { ass_draw_move_to(d, s + dent, 0); @@ -683,7 +686,7 @@ struct sub_bitmaps *osd_object_get_bitmaps(struct osd_state *osd, struct sub_bitmaps out_imgs = {0}; mp_ass_packer_pack(obj->ass_packer, obj->ass_imgs, obj->num_externals + 1, - obj->changed, format, &out_imgs); + obj->changed, false, format, &out_imgs); obj->changed = false; @@ -11,14 +11,17 @@ #define SUB_GAP_KEEP 0.4 // slight offset when sub seeking or sub stepping #define SUB_SEEK_OFFSET 0.01 +#define SUB_SEEK_WITHOUT_VIDEO_OFFSET 0.1 struct sd { struct mpv_global *global; struct mp_log *log; struct mp_subtitle_opts *opts; + struct mp_subtitle_shared_opts *shared_opts; const struct sd_functions *driver; void *priv; + int order; struct attachment_list *attachments; struct mp_codec_params *codec; diff --git a/sub/sd_ass.c b/sub/sd_ass.c index 6742f6f..6fa4d1b 100644 --- a/sub/sd_ass.c +++ b/sub/sd_ass.c @@ -49,18 +49,24 @@ struct sd_ass_priv { struct sd_filter **filters; int num_filters; bool clear_once; - bool on_top; struct mp_ass_packer *packer; struct sub_bitmap_copy_cache *copy_cache; char last_text[500]; struct mp_image_params video_params; struct mp_image_params last_params; struct mp_osd_res osd; - int64_t *seen_packets; + struct seen_packet *seen_packets; int num_seen_packets; + bool *packets_animated; + int num_packets_animated; bool duration_unknown; }; +struct seen_packet { + int64_t pos; + double pts; +}; + static void mangle_colors(struct sd *sd, struct sub_bitmaps *parts); static void fill_plaintext(struct sd *sd, double pts); @@ -78,9 +84,10 @@ static const struct sd_filter_functions *const filters[] = { // Add default styles, if the track does not have any styles yet. // Apply style overrides if the user provides any. -static void mp_ass_add_default_styles(ASS_Track *track, struct mp_subtitle_opts *opts) +static void mp_ass_add_default_styles(ASS_Track *track, struct mp_subtitle_opts *opts, + struct mp_subtitle_shared_opts *shared_opts, int order) { - if (opts->ass_styles_file && opts->ass_style_override) + if (opts->ass_styles_file && shared_opts->ass_style_override[order]) ass_read_styles(track, opts->ass_styles_file, NULL); if (track->n_styles == 0) { @@ -96,7 +103,7 @@ static void mp_ass_add_default_styles(ASS_Track *track, struct mp_subtitle_opts mp_ass_set_style(style, track->PlayResY, opts->sub_style); } - if (opts->ass_style_override) + if (shared_opts->ass_style_override[order]) ass_process_force_style(track); } @@ -177,7 +184,7 @@ static void filters_init(struct sd *sd) .opts = mp_get_config_group(ft, sd->global, &mp_sub_filter_opts), .driver = filters[n], .codec = "ass", - .event_format = ctx->ass_track->event_format, + .event_format = talloc_strdup(ft, ctx->ass_track->event_format), }; if (ft->driver->init(ft)) { MP_TARRAY_APPEND(ctx, ctx->filters, ctx->num_filters, ft); @@ -207,13 +214,14 @@ static void assobjects_init(struct sd *sd) { struct sd_ass_priv *ctx = sd->priv; struct mp_subtitle_opts *opts = sd->opts; + struct mp_subtitle_shared_opts *shared_opts = sd->shared_opts; ctx->ass_library = mp_ass_init(sd->global, sd->opts->sub_style, sd->log); ass_set_extract_fonts(ctx->ass_library, opts->use_embedded_fonts); add_subtitle_fonts(sd); - if (opts->ass_style_override) + if (shared_opts->ass_style_override[sd->order]) ass_set_style_overrides(ctx->ass_library, opts->ass_style_override_list); ctx->ass_track = ass_new_track(ctx->ass_library); @@ -222,7 +230,7 @@ static void assobjects_init(struct sd *sd) ctx->shadow_track = ass_new_track(ctx->ass_library); ctx->shadow_track->PlayResX = MP_ASS_FONT_PLAYRESX; ctx->shadow_track->PlayResY = MP_ASS_FONT_PLAYRESY; - mp_ass_add_default_styles(ctx->shadow_track, opts); + mp_ass_add_default_styles(ctx->shadow_track, opts, shared_opts, sd->order); char *extradata = sd->codec->extradata; int extradata_size = sd->codec->extradata_size; @@ -233,7 +241,7 @@ static void assobjects_init(struct sd *sd) if (extradata) ass_process_codec_private(ctx->ass_track, extradata, extradata_size); - mp_ass_add_default_styles(ctx->ass_track, opts); + mp_ass_add_default_styles(ctx->ass_track, opts, shared_opts, sd->order); #if LIBASS_VERSION >= 0x01302000 ass_set_check_readorder(ctx->ass_track, sd->opts->sub_clear_on_seek ? 0 : 1); @@ -279,11 +287,55 @@ static int init(struct sd *sd) return 0; } +// Check if subtitle has events that would cause it to be animated inside {} +static bool is_animated(char *s) +{ + bool in_tag = false; + bool valid_event = false; + bool valid_tag = false; + while (*s) { + if (!in_tag && s[0] == '{') + in_tag = true; + if (s[0] == '\\') { + s++; + if (!s[0]) + break; + if (s[0] == 'k' || s[0] == 'K' || s[0] == 't') { + valid_event = true; + continue; + // just bruteforce the multi-letter ones + } else if (s[0] == 'f') { + if (!strncmp(s, "fad", 3)) { + valid_event = true; + continue; + } + } else if (s[0] == 'm') { + if (!strncmp(s, "move", 4)) { + valid_event = true; + continue; + } + } + } + if (in_tag && valid_event && s[0] == '}') { + valid_tag = true; + break; + } else if (s[0] == '}') { + in_tag = false; + valid_event = false; + valid_tag = false; + } + s++; + } + return valid_tag; +} + // Note: pkt is not necessarily a fully valid refcounted packet. static void filter_and_add(struct sd *sd, struct demux_packet *pkt) { struct sd_ass_priv *ctx = sd->priv; struct demux_packet *orig_pkt = pkt; + ASS_Track *track = ctx->ass_track; + int old_n_events = track->n_events; for (int n = 0; n < ctx->num_filters; n++) { struct sd_filter *ft = ctx->filters[n]; @@ -299,30 +351,51 @@ static void filter_and_add(struct sd *sd, struct demux_packet *pkt) llrint(pkt->pts * 1000), llrint(pkt->duration * 1000)); + // This bookkeeping is only ever needed for ASS subs + if (!ctx->is_converted) { + if (!pkt->seen) { + for (int n = track->n_events - 1; n >= 0; n--) { + if (n + 1 == old_n_events || pkt->animated) + break; + ASS_Event *event = &track->events[n]; + pkt->animated = (event->Effect && event->Effect[0]) || + is_animated(event->Text); + } + MP_TARRAY_APPEND(ctx, ctx->packets_animated, ctx->num_packets_animated, pkt->animated); + } else { + pkt->animated = ctx->packets_animated[pkt->seen_pos]; + } + } + if (pkt != orig_pkt) talloc_free(pkt); } -// Test if the packet with the given file position (used as unique ID) was -// already consumed. Return false if the packet is new (and add it to the -// internal list), and return true if it was already seen. -static bool check_packet_seen(struct sd *sd, int64_t pos) +// Test if the packet with the given file position and pts was already consumed. +// Return false if the packet is new (and add it to the internal list), and +// return true if it was already seen. +static bool check_packet_seen(struct sd *sd, struct demux_packet *packet) { struct sd_ass_priv *priv = sd->priv; int a = 0; int b = priv->num_seen_packets; while (a < b) { int mid = a + (b - a) / 2; - int64_t val = priv->seen_packets[mid]; - if (pos == val) + struct seen_packet *seen_packet = &priv->seen_packets[mid]; + if (packet->pos == seen_packet->pos && packet->pts == seen_packet->pts) { + packet->seen_pos = mid; return true; - if (pos > val) { + } + if (packet->pos > seen_packet->pos || + (packet->pos == seen_packet->pos && packet->pts > seen_packet->pts)) { a = mid + 1; } else { b = mid; } } - MP_TARRAY_INSERT_AT(priv, priv->seen_packets, priv->num_seen_packets, a, pos); + packet->seen_pos = a; + MP_TARRAY_INSERT_AT(priv, priv->seen_packets, priv->num_seen_packets, a, + (struct seen_packet){packet->pos, packet->pts}); return false; } @@ -332,9 +405,12 @@ static void decode(struct sd *sd, struct demux_packet *packet) { struct sd_ass_priv *ctx = sd->priv; ASS_Track *track = ctx->ass_track; + + packet->sub_duration = packet->duration; + if (ctx->converter) { if (!sd->opts->sub_clear_on_seek && packet->pos >= 0 && - check_packet_seen(sd, packet->pos)) + check_packet_seen(sd, packet)) return; double sub_pts = 0; @@ -373,7 +449,9 @@ static void decode(struct sd *sd, struct demux_packet *packet) } } else { // Note that for this packet format, libass has an internal mechanism - // for discarding duplicate (already seen) packets. + // for discarding duplicate (already seen) packets but we check this + // anyways for our purposes for ASS subtitles. + packet->seen = check_packet_seen(sd, packet); filter_and_add(sd, packet); } } @@ -382,6 +460,7 @@ static void configure_ass(struct sd *sd, struct mp_osd_res *dim, bool converted, ASS_Track *track) { struct mp_subtitle_opts *opts = sd->opts; + struct mp_subtitle_shared_opts *shared_opts = sd->shared_opts; struct sd_ass_priv *ctx = sd->priv; ASS_Renderer *priv = ctx->ass_renderer; @@ -397,7 +476,7 @@ static void configure_ass(struct sd *sd, struct mp_osd_res *dim, bool set_scale_by_window = true; bool total_override = false; // With forced overrides, apply the --sub-* specific options - if (converted || opts->ass_style_override == 3) { // 'force' + if (converted || shared_opts->ass_style_override[sd->order] == 3) { // 'force' set_scale_with_window = opts->sub_scale_with_window; set_use_margins = opts->sub_use_margins; set_scale_by_window = opts->sub_scale_by_window; @@ -406,8 +485,8 @@ static void configure_ass(struct sd *sd, struct mp_osd_res *dim, set_scale_with_window = opts->ass_scale_with_window; set_use_margins = opts->ass_use_margins; } - if (converted || opts->ass_style_override) { - set_sub_pos = 100.0f - opts->sub_pos; + if (converted || shared_opts->ass_style_override[sd->order]) { + set_sub_pos = 100.0f - shared_opts->sub_pos[sd->order]; set_line_spacing = opts->ass_line_spacing; set_hinting = opts->ass_hinting; set_font_scale = opts->sub_scale; @@ -427,12 +506,12 @@ static void configure_ass(struct sd *sd, struct mp_osd_res *dim, int set_force_flags = 0; if (total_override) set_force_flags |= ASS_OVERRIDE_BIT_STYLE | ASS_OVERRIDE_BIT_SELECTIVE_FONT_SCALE; - if (opts->ass_style_override == 4) // 'scale' + if (shared_opts->ass_style_override[sd->order] == 4) // 'scale' set_force_flags |= ASS_OVERRIDE_BIT_SELECTIVE_FONT_SCALE; if (converted) set_force_flags |= ASS_OVERRIDE_BIT_ALIGNMENT; #ifdef ASS_JUSTIFY_AUTO - if ((converted || opts->ass_style_override) && opts->ass_justify) + if ((converted || shared_opts->ass_style_override[sd->order]) && opts->ass_justify) set_force_flags |= ASS_OVERRIDE_BIT_JUSTIFY; #endif ass_set_selective_style_override_enabled(priv, set_force_flags); @@ -499,7 +578,7 @@ static long long find_timestamp(struct sd *sd, double pts) long long ts = llrint(pts * 1000); - if (!sd->opts->sub_fix_timing || sd->opts->ass_style_override == 0) + if (!sd->opts->sub_fix_timing || sd->shared_opts->ass_style_override[sd->order] == 0) return ts; // Try to fix small gaps and overlaps. @@ -561,8 +640,8 @@ static struct sub_bitmaps *get_bitmaps(struct sd *sd, struct mp_osd_res dim, { struct sd_ass_priv *ctx = sd->priv; struct mp_subtitle_opts *opts = sd->opts; - bool no_ass = !opts->ass_enabled || ctx->on_top || - opts->ass_style_override == 5; + struct mp_subtitle_shared_opts *shared_opts = sd->shared_opts; + bool no_ass = !opts->ass_enabled || shared_opts->ass_style_override[sd->order] == 5; bool converted = ctx->is_converted || no_ass; ASS_Track *track = no_ass ? ctx->shadow_track : ctx->ass_track; ASS_Renderer *renderer = ctx->ass_renderer; @@ -582,7 +661,7 @@ static struct sub_bitmaps *get_bitmaps(struct sd *sd, struct mp_osd_res dim, goto done; double scale = dim.display_par; - if (!converted && (!opts->ass_style_override || + if (!converted && (!shared_opts->ass_style_override[sd->order] || opts->ass_vsfilter_aspect_compat)) { // Let's use the original video PAR for vsfilter compatibility: @@ -595,7 +674,7 @@ static struct sub_bitmaps *get_bitmaps(struct sd *sd, struct mp_osd_res dim, ctx->ass_configured = true; } ass_set_pixel_aspect(renderer, scale); - if (!converted && (!opts->ass_style_override || + if (!converted && (!shared_opts->ass_style_override[sd->order] || opts->ass_vsfilter_blur_compat)) { ass_set_storage_size(renderer, ctx->video_params.w, ctx->video_params.h); @@ -603,18 +682,13 @@ static struct sub_bitmaps *get_bitmaps(struct sd *sd, struct mp_osd_res dim, ass_set_storage_size(renderer, 0, 0); } long long ts = find_timestamp(sd, pts); - if (ctx->duration_unknown && pts != MP_NOPTS_VALUE) { - mp_ass_flush_old_events(track, ts); - ctx->num_seen_packets = 0; - sd->preload_ok = false; - } if (no_ass) fill_plaintext(sd, pts); int changed; ASS_Image *imgs = ass_render_frame(renderer, track, ts, &changed); - mp_ass_packer_pack(ctx->packer, &imgs, 1, changed, format, res); + mp_ass_packer_pack(ctx->packer, &imgs, 1, changed, !converted, format, res); done: // mangle_colors() modifies the color field, so copy the thing _before_. @@ -650,7 +724,7 @@ static void ass_to_plaintext(struct buf *b, const char *in) if (in[0] == '}') { in += 1; in_tag = false; - } else if (in[0] == '\\' && in[1] == 'p') { + } else if (in[0] == '\\' && in[1] == 'p' && in[2] != 'o') { in += 2; // Skip text between \pN and \p0 tags. A \p without a number // is the same as \p0, and leading 0s are also allowed. @@ -785,9 +859,6 @@ static void fill_plaintext(struct sd *sd, double pts) bstr dst = {0}; - if (ctx->on_top) - bstr_xappend(NULL, &dst, bstr0("{\\a6}")); - while (*text) { if (*text == '{') bstr_xappend(NULL, &dst, bstr0("\\")); @@ -814,7 +885,7 @@ static void fill_plaintext(struct sd *sd, double pts) static void reset(struct sd *sd) { struct sd_ass_priv *ctx = sd->priv; - if (sd->opts->sub_clear_on_seek || ctx->duration_unknown || ctx->clear_once) { + if (sd->opts->sub_clear_on_seek || ctx->clear_once) { ass_flush_events(ctx->ass_track); ctx->num_seen_packets = 0; sd->preload_ok = false; @@ -852,9 +923,6 @@ static int control(struct sd *sd, enum sd_ctrl cmd, void *arg) case SD_CTRL_SET_VIDEO_PARAMS: ctx->video_params = *(struct mp_image_params *)arg; return CONTROL_OK; - case SD_CTRL_SET_TOP: - ctx->on_top = *(bool *)arg; - return CONTROL_OK; case SD_CTRL_UPDATE_OPTS: { int flags = (uintptr_t)arg; if (flags & UPDATE_SUB_FILT) { @@ -897,27 +965,27 @@ static void mangle_colors(struct sd *sd, struct sub_bitmaps *parts) { struct mp_subtitle_opts *opts = sd->opts; struct sd_ass_priv *ctx = sd->priv; - enum mp_csp csp = 0; - enum mp_csp_levels levels = 0; + enum pl_color_system csp = 0; + enum pl_color_levels levels = 0; if (opts->ass_vsfilter_color_compat == 0) // "no" return; bool force_601 = opts->ass_vsfilter_color_compat == 3; ASS_Track *track = ctx->ass_track; static const int ass_csp[] = { - [YCBCR_BT601_TV] = MP_CSP_BT_601, - [YCBCR_BT601_PC] = MP_CSP_BT_601, - [YCBCR_BT709_TV] = MP_CSP_BT_709, - [YCBCR_BT709_PC] = MP_CSP_BT_709, - [YCBCR_SMPTE240M_TV] = MP_CSP_SMPTE_240M, - [YCBCR_SMPTE240M_PC] = MP_CSP_SMPTE_240M, + [YCBCR_BT601_TV] = PL_COLOR_SYSTEM_BT_601, + [YCBCR_BT601_PC] = PL_COLOR_SYSTEM_BT_601, + [YCBCR_BT709_TV] = PL_COLOR_SYSTEM_BT_709, + [YCBCR_BT709_PC] = PL_COLOR_SYSTEM_BT_709, + [YCBCR_SMPTE240M_TV] = PL_COLOR_SYSTEM_SMPTE_240M, + [YCBCR_SMPTE240M_PC] = PL_COLOR_SYSTEM_SMPTE_240M, }; static const int ass_levels[] = { - [YCBCR_BT601_TV] = MP_CSP_LEVELS_TV, - [YCBCR_BT601_PC] = MP_CSP_LEVELS_PC, - [YCBCR_BT709_TV] = MP_CSP_LEVELS_TV, - [YCBCR_BT709_PC] = MP_CSP_LEVELS_PC, - [YCBCR_SMPTE240M_TV] = MP_CSP_LEVELS_TV, - [YCBCR_SMPTE240M_PC] = MP_CSP_LEVELS_PC, + [YCBCR_BT601_TV] = PL_COLOR_LEVELS_LIMITED, + [YCBCR_BT601_PC] = PL_COLOR_LEVELS_FULL, + [YCBCR_BT709_TV] = PL_COLOR_LEVELS_LIMITED, + [YCBCR_BT709_PC] = PL_COLOR_LEVELS_FULL, + [YCBCR_SMPTE240M_TV] = PL_COLOR_LEVELS_LIMITED, + [YCBCR_SMPTE240M_PC] = PL_COLOR_LEVELS_FULL, }; int trackcsp = track->YCbCrMatrix; if (force_601) @@ -930,8 +998,8 @@ static void mangle_colors(struct sd *sd, struct sub_bitmaps *parts) if (trackcsp < sizeof(ass_levels) / sizeof(ass_levels[0])) levels = ass_levels[trackcsp]; if (trackcsp == YCBCR_DEFAULT) { - csp = MP_CSP_BT_601; - levels = MP_CSP_LEVELS_TV; + csp = PL_COLOR_SYSTEM_BT_601; + levels = PL_COLOR_LEVELS_LIMITED; } // Unknown colorspace (either YCBCR_UNKNOWN, or a valid value unknown to us) if (!csp || !levels) @@ -940,50 +1008,51 @@ static void mangle_colors(struct sd *sd, struct sub_bitmaps *parts) struct mp_image_params params = ctx->video_params; if (force_601) { - params.color = (struct mp_colorspace){ - .space = MP_CSP_BT_709, - .levels = MP_CSP_LEVELS_TV, + params.repr = (struct pl_color_repr){ + .sys = PL_COLOR_SYSTEM_BT_709, + .levels = PL_COLOR_LEVELS_LIMITED, }; } - if ((csp == params.color.space && levels == params.color.levels) || - params.color.space == MP_CSP_RGB) // Even VSFilter doesn't mangle on RGB video + if ((csp == params.repr.sys && levels == params.repr.levels) || + params.repr.sys == PL_COLOR_SYSTEM_RGB) // Even VSFilter doesn't mangle on RGB video return; - bool basic_conv = params.color.space == MP_CSP_BT_709 && - params.color.levels == MP_CSP_LEVELS_TV && - csp == MP_CSP_BT_601 && - levels == MP_CSP_LEVELS_TV; + bool basic_conv = params.repr.sys == PL_COLOR_SYSTEM_BT_709 && + params.repr.levels == PL_COLOR_LEVELS_LIMITED && + csp == PL_COLOR_SYSTEM_BT_601 && + levels == PL_COLOR_LEVELS_LIMITED; // With "basic", only do as much as needed for basic compatibility. if (opts->ass_vsfilter_color_compat == 1 && !basic_conv) return; - if (params.color.space != ctx->last_params.color.space || - params.color.levels != ctx->last_params.color.levels) + if (params.repr.sys != ctx->last_params.repr.sys || + params.repr.levels != ctx->last_params.repr.levels) { int msgl = basic_conv ? MSGL_V : MSGL_WARN; ctx->last_params = params; MP_MSG(sd, msgl, "mangling colors like vsfilter: " "RGB -> %s %s -> %s %s -> RGB\n", - m_opt_choice_str(mp_csp_names, csp), - m_opt_choice_str(mp_csp_levels_names, levels), - m_opt_choice_str(mp_csp_names, params.color.space), - m_opt_choice_str(mp_csp_names, params.color.levels)); + m_opt_choice_str(pl_csp_names, csp), + m_opt_choice_str(pl_csp_levels_names, levels), + m_opt_choice_str(pl_csp_names, params.repr.sys), + m_opt_choice_str(pl_csp_names, params.repr.levels)); } // Conversion that VSFilter would use struct mp_csp_params vs_params = MP_CSP_PARAMS_DEFAULTS; - vs_params.color.space = csp; - vs_params.color.levels = levels; - struct mp_cmat vs_yuv2rgb, vs_rgb2yuv; + vs_params.repr.sys = csp; + vs_params.repr.levels = levels; + struct pl_transform3x3 vs_yuv2rgb; mp_get_csp_matrix(&vs_params, &vs_yuv2rgb); - mp_invert_cmat(&vs_rgb2yuv, &vs_yuv2rgb); + pl_transform3x3_invert(&vs_yuv2rgb); // Proper conversion to RGB struct mp_csp_params rgb_params = MP_CSP_PARAMS_DEFAULTS; + rgb_params.repr = params.repr; rgb_params.color = params.color; - struct mp_cmat vs2rgb; + struct pl_transform3x3 vs2rgb; mp_get_csp_matrix(&rgb_params, &vs2rgb); for (int n = 0; n < parts->num_parts; n++) { @@ -994,7 +1063,7 @@ static void mangle_colors(struct sd *sd, struct sub_bitmaps *parts) int b = (color >> 8u) & 0xff; int a = 0xff - (color & 0xff); int rgb[3] = {r, g, b}, yuv[3]; - mp_map_fixp_color(&vs_rgb2yuv, 8, rgb, 8, yuv); + mp_map_fixp_color(&vs_yuv2rgb, 8, rgb, 8, yuv); mp_map_fixp_color(&vs2rgb, 8, yuv, 8, rgb); sb->libass.color = MP_ASS_RGBA(rgb[0], rgb[1], rgb[2], a); } diff --git a/sub/sd_lavc.c b/sub/sd_lavc.c index 30aa641..cf49f2d 100644 --- a/sub/sd_lavc.c +++ b/sub/sd_lavc.c @@ -96,6 +96,9 @@ static int init(struct sd *sd) ctx = avcodec_alloc_context3(sub_codec); if (!ctx) goto error; + + mp_set_avopts(sd->log, ctx, sd->opts->sub_avopts); + priv->avpkt = av_packet_alloc(); if (!priv->avpkt) goto error; @@ -308,9 +311,15 @@ static void decode(struct sd *sd, struct demux_packet *packet) mp_set_av_packet(priv->avpkt, packet, &priv->pkt_timebase); if (ctx->codec_id == AV_CODEC_ID_DVB_TELETEXT) { - char page[4]; - snprintf(page, sizeof(page), "%d", opts->teletext_page); - av_opt_set(ctx, "txt_page", page, AV_OPT_SEARCH_CHILDREN); + if (!opts->teletext_page) { + av_opt_set(ctx, "txt_page", "subtitle", AV_OPT_SEARCH_CHILDREN); + } else if (opts->teletext_page == -1) { + av_opt_set(ctx, "txt_page", "*", AV_OPT_SEARCH_CHILDREN); + } else { + char page[4]; + snprintf(page, sizeof(page), "%d", opts->teletext_page); + av_opt_set(ctx, "txt_page", page, AV_OPT_SEARCH_CHILDREN); + } } int got_sub; @@ -318,6 +327,8 @@ static void decode(struct sd *sd, struct demux_packet *packet) if (res < 0 || !got_sub) return; + packet->sub_duration = sub.end_display_time; + if (sub.pts != AV_NOPTS_VALUE) pts = sub.pts / (double)AV_TIME_BASE; @@ -403,6 +414,7 @@ static struct sub_bitmaps *get_bitmaps(struct sd *sd, struct mp_osd_res d, { struct sd_lavc_priv *priv = sd->priv; struct mp_subtitle_opts *opts = sd->opts; + struct mp_subtitle_shared_opts *shared_opts = sd->shared_opts; priv->current_pts = pts; @@ -450,8 +462,8 @@ static struct sub_bitmaps *get_bitmaps(struct sd *sd, struct mp_osd_res d, h = MPMAX(priv->video_params.h, current->src_h); } - if (opts->sub_pos != 100.0f && opts->ass_style_override) { - float offset = (100.0f - opts->sub_pos) / 100.0f * h; + if (shared_opts->sub_pos[sd->order] != 100.0f && shared_opts->ass_style_override[sd->order]) { + float offset = (100.0f - shared_opts->sub_pos[sd->order]) / 100.0f * h; for (int n = 0; n < res->num_parts; n++) { struct sub_bitmap *sub = &res->parts[n]; @@ -469,7 +481,7 @@ static struct sub_bitmaps *get_bitmaps(struct sd *sd, struct mp_osd_res d, osd_rescale_bitmaps(res, w, h, d, video_par); - if (opts->sub_scale != 1.0 && opts->ass_style_override) { + if (opts->sub_scale != 1.0 && shared_opts->ass_style_override[sd->order]) { for (int n = 0; n < res->num_parts; n++) { struct sub_bitmap *sub = &res->parts[n]; @@ -144,9 +144,26 @@ void *ta_xrealloc_size(void *ta_parent, void *ptr, size_t size); #define ta_xrealloc_size(...) ta_dbg_set_loc(ta_xrealloc_size(__VA_ARGS__), TA_LOC) #endif -void ta_oom_b(bool b); -char *ta_oom_s(char *s); -void *ta_oom_p(void *p); +static inline void *ta_oom_p(void *p) +{ + if (!p) + abort(); + return p; +} + +static inline void ta_oom_b(bool b) +{ + if (!b) + abort(); +} + +static inline char *ta_oom_s(char *s) +{ + if (!s) + abort(); + return s; +} + // Generic pointer #define ta_oom_g(ptr) (TA_TYPEOF(ptr))ta_oom_p(ptr) diff --git a/ta/ta_utils.c b/ta/ta_utils.c index 6246968..294ad8d 100644 --- a/ta/ta_utils.c +++ b/ta/ta_utils.c @@ -265,27 +265,6 @@ bool ta_vasprintf_append_buffer(char **str, const char *fmt, va_list ap) return ta_vasprintf_append_at(str, size, fmt, ap); } - -void *ta_oom_p(void *p) -{ - if (!p) - abort(); - return p; -} - -void ta_oom_b(bool b) -{ - if (!b) - abort(); -} - -char *ta_oom_s(char *s) -{ - if (!s) - abort(); - return s; -} - void *ta_xmemdup(void *ta_parent, void *ptr, size_t size) { void *new = ta_memdup(ta_parent, ptr, size); diff --git a/test/format.c b/test/format.c new file mode 100644 index 0000000..8033d1b --- /dev/null +++ b/test/format.c @@ -0,0 +1,99 @@ +#include "test_utils.h" +#include "common/common.h" + +int main(void) +{ + void *ta_ctx = talloc_new(NULL); + + assert_string_equal(mp_format_double(ta_ctx, 123.456, 0, false, false, false), "123"); + assert_string_equal(mp_format_double(ta_ctx, 123.456, 0, false, false, true), "123"); + assert_string_equal(mp_format_double(ta_ctx, 123.456, 0, false, true, false), "123%"); + assert_string_equal(mp_format_double(ta_ctx, 123.456, 0, false, true, true), "123%"); + assert_string_equal(mp_format_double(ta_ctx, 123.456, 0, true, false, false), "+123"); + assert_string_equal(mp_format_double(ta_ctx, 123.456, 0, true, false, true), "+123"); + assert_string_equal(mp_format_double(ta_ctx, 123.456, 0, true, true, false), "+123%"); + assert_string_equal(mp_format_double(ta_ctx, 123.456, 0, true, true, true), "+123%"); + + assert_string_equal(mp_format_double(ta_ctx, -123.456, 0, false, false, false), "-123"); + assert_string_equal(mp_format_double(ta_ctx, -123.456, 0, false, false, true), "-123"); + assert_string_equal(mp_format_double(ta_ctx, -123.456, 0, false, true, false), "-123%"); + assert_string_equal(mp_format_double(ta_ctx, -123.456, 0, false, true, true), "-123%"); + assert_string_equal(mp_format_double(ta_ctx, -123.456, 0, true, false, false), "-123"); + assert_string_equal(mp_format_double(ta_ctx, -123.456, 0, true, false, true), "-123"); + assert_string_equal(mp_format_double(ta_ctx, -123.456, 0, true, true, false), "-123%"); + assert_string_equal(mp_format_double(ta_ctx, -123.456, 0, true, true, true), "-123%"); + + assert_string_equal(mp_format_double(ta_ctx, 123.456, 2, false, false, false), "123.46"); + assert_string_equal(mp_format_double(ta_ctx, 123.456, 2, false, false, true), "123.46"); + assert_string_equal(mp_format_double(ta_ctx, 123.456, 2, false, true, false), "123.46%"); + assert_string_equal(mp_format_double(ta_ctx, 123.456, 2, false, true, true), "123.46%"); + assert_string_equal(mp_format_double(ta_ctx, 123.456, 2, true, false, false), "+123.46"); + assert_string_equal(mp_format_double(ta_ctx, 123.456, 2, true, false, true), "+123.46"); + assert_string_equal(mp_format_double(ta_ctx, 123.456, 2, true, true, false), "+123.46%"); + assert_string_equal(mp_format_double(ta_ctx, 123.456, 2, true, true, true), "+123.46%"); + + assert_string_equal(mp_format_double(ta_ctx, -123.456, 2, false, false, false), "-123.46"); + assert_string_equal(mp_format_double(ta_ctx, -123.456, 2, false, false, true), "-123.46"); + assert_string_equal(mp_format_double(ta_ctx, -123.456, 2, false, true, false), "-123.46%"); + assert_string_equal(mp_format_double(ta_ctx, -123.456, 2, false, true, true), "-123.46%"); + assert_string_equal(mp_format_double(ta_ctx, -123.456, 2, true, false, false), "-123.46"); + assert_string_equal(mp_format_double(ta_ctx, -123.456, 2, true, false, true), "-123.46"); + assert_string_equal(mp_format_double(ta_ctx, -123.456, 2, true, true, false), "-123.46%"); + assert_string_equal(mp_format_double(ta_ctx, -123.456, 2, true, true, true), "-123.46%"); + + assert_string_equal(mp_format_double(ta_ctx, 123.456, 6, false, false, false), "123.456000"); + assert_string_equal(mp_format_double(ta_ctx, 123.456, 6, false, false, true), "123.456"); + assert_string_equal(mp_format_double(ta_ctx, 123.456, 6, false, true, false), "123.456000%"); + assert_string_equal(mp_format_double(ta_ctx, 123.456, 6, false, true, true), "123.456%"); + assert_string_equal(mp_format_double(ta_ctx, 123.456, 6, true, false, false), "+123.456000"); + assert_string_equal(mp_format_double(ta_ctx, 123.456, 6, true, false, true), "+123.456"); + assert_string_equal(mp_format_double(ta_ctx, 123.456, 6, true, true, false), "+123.456000%"); + assert_string_equal(mp_format_double(ta_ctx, 123.456, 6, true, true, true), "+123.456%"); + + assert_string_equal(mp_format_double(ta_ctx, -123.456, 6, false, false, false), "-123.456000"); + assert_string_equal(mp_format_double(ta_ctx, -123.456, 6, false, false, true), "-123.456"); + assert_string_equal(mp_format_double(ta_ctx, -123.456, 6, false, true, false), "-123.456000%"); + assert_string_equal(mp_format_double(ta_ctx, -123.456, 6, false, true, true), "-123.456%"); + assert_string_equal(mp_format_double(ta_ctx, -123.456, 6, true, false, false), "-123.456000"); + assert_string_equal(mp_format_double(ta_ctx, -123.456, 6, true, false, true), "-123.456"); + assert_string_equal(mp_format_double(ta_ctx, -123.456, 6, true, true, false), "-123.456000%"); + assert_string_equal(mp_format_double(ta_ctx, -123.456, 6, true, true, true), "-123.456%"); + + assert_string_equal(mp_format_double(ta_ctx, 123, 6, false, false, false), "123.000000"); + assert_string_equal(mp_format_double(ta_ctx, 123, 6, false, false, true), "123"); + assert_string_equal(mp_format_double(ta_ctx, 123, 6, false, true, false), "123.000000%"); + assert_string_equal(mp_format_double(ta_ctx, 123, 6, false, true, true), "123%"); + assert_string_equal(mp_format_double(ta_ctx, 123, 6, true, false, false), "+123.000000"); + assert_string_equal(mp_format_double(ta_ctx, 123, 6, true, false, true), "+123"); + assert_string_equal(mp_format_double(ta_ctx, 123, 6, true, true, false), "+123.000000%"); + assert_string_equal(mp_format_double(ta_ctx, 123, 6, true, true, true), "+123%"); + + assert_string_equal(mp_format_double(ta_ctx, -123, 6, false, false, false), "-123.000000"); + assert_string_equal(mp_format_double(ta_ctx, -123, 6, false, false, true), "-123"); + assert_string_equal(mp_format_double(ta_ctx, -123, 6, false, true, false), "-123.000000%"); + assert_string_equal(mp_format_double(ta_ctx, -123, 6, false, true, true), "-123%"); + assert_string_equal(mp_format_double(ta_ctx, -123, 6, true, false, false), "-123.000000"); + assert_string_equal(mp_format_double(ta_ctx, -123, 6, true, false, true), "-123"); + assert_string_equal(mp_format_double(ta_ctx, -123, 6, true, true, false), "-123.000000%"); + assert_string_equal(mp_format_double(ta_ctx, -123, 6, true, true, true), "-123%"); + + assert_string_equal(mp_format_double(ta_ctx, INFINITY, 6, false, false, false), "inf"); + assert_string_equal(mp_format_double(ta_ctx, INFINITY, 6, false, false, true), "inf"); + assert_string_equal(mp_format_double(ta_ctx, INFINITY, 6, false, true, false), "inf%"); + assert_string_equal(mp_format_double(ta_ctx, INFINITY, 6, false, true, true), "inf%"); + assert_string_equal(mp_format_double(ta_ctx, INFINITY, 6, true, false, false), "+inf"); + assert_string_equal(mp_format_double(ta_ctx, INFINITY, 6, true, false, true), "+inf"); + assert_string_equal(mp_format_double(ta_ctx, INFINITY, 6, true, true, false), "+inf%"); + assert_string_equal(mp_format_double(ta_ctx, INFINITY, 6, true, true, true), "+inf%"); + + assert_string_equal(mp_format_double(ta_ctx, -INFINITY, 6, false, false, false), "-inf"); + assert_string_equal(mp_format_double(ta_ctx, -INFINITY, 6, false, false, true), "-inf"); + assert_string_equal(mp_format_double(ta_ctx, -INFINITY, 6, false, true, false), "-inf%"); + assert_string_equal(mp_format_double(ta_ctx, -INFINITY, 6, false, true, true), "-inf%"); + assert_string_equal(mp_format_double(ta_ctx, -INFINITY, 6, true, false, false), "-inf"); + assert_string_equal(mp_format_double(ta_ctx, -INFINITY, 6, true, false, true), "-inf"); + assert_string_equal(mp_format_double(ta_ctx, -INFINITY, 6, true, true, false), "-inf%"); + assert_string_equal(mp_format_double(ta_ctx, -INFINITY, 6, true, true, true), "-inf%"); + + talloc_free(ta_ctx); +} diff --git a/test/img_format.c b/test/img_format.c index 3cc8ff5..3b553f6 100644 --- a/test/img_format.c +++ b/test/img_format.c @@ -40,7 +40,7 @@ int main(int argc, char *argv[]) int fcsp = mp_imgfmt_get_forced_csp(mpfmt); if (fcsp) - fprintf(f, "fcsp=%s ", m_opt_choice_str(mp_csp_names, fcsp)); + fprintf(f, "fcsp=%s ", m_opt_choice_str(pl_csp_names, fcsp)); fprintf(f, "ctype=%s\n", comp_type(mp_imgfmt_get_component_type(mpfmt))); struct mp_imgfmt_desc d = mp_imgfmt_get_desc(mpfmt); diff --git a/test/libmpv_encode.c b/test/libmpv_encode.c new file mode 100644 index 0000000..49ca166 --- /dev/null +++ b/test/libmpv_encode.c @@ -0,0 +1,138 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <inttypes.h> +#include <libmpv/client.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> + +// Stolen from osdep/compiler.h +#ifdef __GNUC__ +#define PRINTF_ATTRIBUTE(a1, a2) __attribute__ ((format(printf, a1, a2))) +#define MP_NORETURN __attribute__((noreturn)) +#else +#define PRINTF_ATTRIBUTE(a1, a2) +#define MP_NORETURN +#endif + +// Broken crap with __USE_MINGW_ANSI_STDIO +#if defined(__MINGW32__) && defined(__GNUC__) && !defined(__clang__) +#undef PRINTF_ATTRIBUTE +#define PRINTF_ATTRIBUTE(a1, a2) __attribute__ ((format (gnu_printf, a1, a2))) +#endif + +// Global handle +static mpv_handle *ctx; +// Temporary output file +static const char *out_path; + +static void exit_cleanup(void) +{ + if (ctx) + mpv_destroy(ctx); + if (out_path) + unlink(out_path); +} + +MP_NORETURN PRINTF_ATTRIBUTE(1, 2) +static void fail(const char *fmt, ...) +{ + if (fmt) { + va_list va; + va_start(va, fmt); + vfprintf(stderr, fmt, va); + va_end(va); + } + exit(1); +} + +static void check_api_error(int status) +{ + if (status < 0) + fail("libmpv error: %s\n", mpv_error_string(status)); +} + +static void wait_done(void) +{ + while (1) { + mpv_event *ev = mpv_wait_event(ctx, -1.0); + if (ev->event_id == MPV_EVENT_NONE) + continue; + printf("event: %s\n", mpv_event_name(ev->event_id)); + if (ev->event_id == MPV_EVENT_SHUTDOWN) + return; + } +} + +static void check_output(int fd) +{ + off_t size = lseek(fd, 0, SEEK_END); + if (size < 100) + fail("did not encode anything"); + + char magic[4] = {0}; + lseek(fd, 0, SEEK_SET); + read(fd, magic, sizeof(magic)); + static const char ebml_magic[] = {26, 69, 223, 163}; + if (memcmp(magic, ebml_magic, 4) != 0) + fail("output was not Matroska"); + + puts("output file ok"); +} + +int main(int argc, char *argv[]) +{ + atexit(exit_cleanup); + + ctx = mpv_create(); + if (!ctx) + return 1; + + int fd; + { + char path[] = "./testout.XXXXXX"; + fd = mkstemp(path); + if (fd == -1) + fail("mkstemp failed"); + out_path = strdup(path); + } + check_api_error(mpv_set_option_string(ctx, "o", out_path)); + check_api_error(mpv_set_option_string(ctx, "of", "matroska")); + check_api_error(mpv_set_option_string(ctx, "end", "1.5")); + + if (mpv_initialize(ctx) != 0) + return 1; + + check_api_error(mpv_set_option_string(ctx, "terminal", "yes")); + check_api_error(mpv_set_option_string(ctx, "msg-level", "all=v")); + check_api_error(mpv_set_option_string(ctx, "idle", "once")); + + const char *cmd[] = {"loadfile", "av://lavfi:testsrc", NULL}; + check_api_error(mpv_command(ctx, cmd)); + + wait_done(); + mpv_destroy(ctx); + ctx = NULL; + + check_output(fd); + close(fd); + + return 0; +} diff --git a/test/meson.build b/test/meson.build index ebd4395..e34d72c 100644 --- a/test/meson.build +++ b/test/meson.build @@ -1,7 +1,7 @@ # So we don't have to reorganize the entire directory tree. incdir = include_directories('../') outdir = join_paths(build_root, 'test', 'out') -refdir = join_paths(source_root, 'test', 'ref') +refdir = '' # Convenient testing libraries. An adhoc collection of # mpv objects that test_utils.c needs. Paths and subprocesses @@ -17,6 +17,7 @@ test_utils_files = [ 'misc/dispatch.c', 'misc/json.c', 'misc/node.c', + 'misc/path_utils.c', 'misc/random.c', 'misc/thread_tools.c', 'options/m_config_core.c', @@ -50,7 +51,8 @@ if not features['win32-threads'] endif if features['win32-desktop'] - test_utils_deps += cc.find_library('winmm') + test_utils_deps += cc.find_library('imm32') + test_utils_deps += cc.find_library('ntdll') endif test_utils_objects = libmpv.extract_objects(test_utils_files) test_utils = static_library('test-utils', 'test_utils.c', include_directories: incdir, @@ -73,7 +75,7 @@ endif img_utils_objects = libmpv.extract_objects(img_utils_files) img_utils = static_library('img-utils', 'img_utils.c', include_directories: incdir, - dependencies: [libavcodec], objects: img_utils_objects) + dependencies: [libavcodec, libplacebo], objects: img_utils_objects) # The actual tests. chmap_files = [ @@ -91,7 +93,7 @@ test('chmap', chmap) gl_video_objects = libmpv.extract_objects('video/out/gpu/ra.c', 'video/out/gpu/utils.c') gl_video = executable('gl-video', 'gl_video.c', objects: gl_video_objects, - dependencies: [libavutil], include_directories: incdir, + dependencies: [libavutil, libplacebo], include_directories: incdir, link_with: [img_utils, test_utils]) test('gl-video', gl_video) @@ -104,22 +106,34 @@ test('linked-list', linked_list) timer = executable('timer', files('timer.c'), include_directories: incdir, link_with: test_utils) test('timer', timer) +format = executable('format', files('format.c'), include_directories: incdir, link_with: test_utils) +test('format', format) + paths_objects = libmpv.extract_objects('options/path.c', path_source) paths = executable('paths', 'paths.c', include_directories: incdir, objects: paths_objects, link_with: test_utils) test('paths', paths) if get_option('libmpv') - libmpv_test = executable('libmpv-test', 'libmpv_test.c', - include_directories: incdir, link_with: libmpv) + exe = executable('libmpv-test', 'libmpv_test.c', + include_directories: incdir, link_with: libmpv) file = join_paths(source_root, 'etc', 'mpv-icon-8bit-16x16.png') - test('libmpv', libmpv_test, args: file, timeout: 60) + test('libmpv', exe, args: file, timeout: 60) + + exe = executable('libmpv-encode', 'libmpv_encode.c', + include_directories: incdir, link_with: libmpv) + test('libmpv-encode', exe, timeout: 30) endif -# Minimum required libavutil version that works with these tests. +# Supported libavutil versions that work with these tests. # Will need to be manually updated when ffmpeg adds/removes more formats in the future. -if libavutil.version().version_compare('>= 58.27.100') +if libavutil.version().version_compare('>= 59.0.100') + refdir = join_paths(source_root, 'test', 'ref', 'ffmpeg7') +elif libavutil.version().version_compare('>= 58.27.100') + refdir = join_paths(source_root, 'test', 'ref', 'ffmpeg6') +endif +if refdir != '' # The CI can randomly fail if libavutil isn't explicitly linked again here. img_format = executable('img-format', 'img_format.c', include_directories: incdir, dependencies: [libavutil, libplacebo], link_with: [img_utils, test_utils]) diff --git a/test/ref/draw_bmp.txt b/test/ref/ffmpeg6/draw_bmp.txt index 66de4de..66de4de 100644 --- a/test/ref/draw_bmp.txt +++ b/test/ref/ffmpeg6/draw_bmp.txt diff --git a/test/ref/img_formats.txt b/test/ref/ffmpeg6/img_formats.txt index 9a3826b..9a3826b 100644 --- a/test/ref/img_formats.txt +++ b/test/ref/ffmpeg6/img_formats.txt diff --git a/test/ref/repack.txt b/test/ref/ffmpeg6/repack.txt index 89b29be..89b29be 100644 --- a/test/ref/repack.txt +++ b/test/ref/ffmpeg6/repack.txt diff --git a/test/ref/zimg_formats.txt b/test/ref/ffmpeg6/zimg_formats.txt index 6c199b1..6c199b1 100644 --- a/test/ref/zimg_formats.txt +++ b/test/ref/ffmpeg6/zimg_formats.txt diff --git a/test/ref/ffmpeg7/draw_bmp.txt b/test/ref/ffmpeg7/draw_bmp.txt new file mode 100644 index 0000000..a36e168 --- /dev/null +++ b/test/ref/ffmpeg7/draw_bmp.txt @@ -0,0 +1,249 @@ +0bgr = align=1:1 ov=unknown, ov_f=gbrap, v_f=gbrp, a=unknown, ca=unknown, ca_f=unknown +0rgb = align=1:1 ov=unknown, ov_f=gbrap, v_f=gbrp, a=unknown, ca=unknown, ca_f=unknown +abgr = align=1:1 ov=unknown, ov_f=gbrap, v_f=gbrap, a=unknown, ca=unknown, ca_f=unknown +argb = align=1:1 ov=unknown, ov_f=gbrap, v_f=gbrap, a=unknown, ca=unknown, ca_f=unknown +ayuv64 = align=1:1 ov=yuva444p, ov_f=yuva444pf, v_f=yuva444pf, a=unknown, ca=unknown, ca_f=unknown +ayuv64be = align=1:1 ov=yuva444p, ov_f=yuva444pf, v_f=yuva444pf, a=unknown, ca=unknown, ca_f=unknown +bayer_bggr16= no +bayer_bggr16be= no +bayer_bggr8 = no +bayer_gbrg16= no +bayer_gbrg16be= no +bayer_gbrg8 = no +bayer_grbg16= no +bayer_grbg16be= no +bayer_grbg8 = no +bayer_rggb16= no +bayer_rggb16be= no +bayer_rggb8 = no +bgr0 = align=1:1 ov=unknown, ov_f=gbrap, v_f=gbrp, a=unknown, ca=unknown, ca_f=unknown +bgr24 = align=1:1 ov=unknown, ov_f=gbrap, v_f=gbrp, a=unknown, ca=unknown, ca_f=unknown +bgr4 = no +bgr444 = align=1:1 ov=unknown, ov_f=gbrap, v_f=gbrp, a=unknown, ca=unknown, ca_f=unknown +bgr444be = align=1:1 ov=unknown, ov_f=gbrap, v_f=gbrp, a=unknown, ca=unknown, ca_f=unknown +bgr48 = align=1:1 ov=unknown, ov_f=gbrapf32, v_f=gbrpf32, a=unknown, ca=unknown, ca_f=unknown +bgr48be = align=1:1 ov=unknown, ov_f=gbrapf32, v_f=gbrpf32, a=unknown, ca=unknown, ca_f=unknown +bgr4_byte = align=1:1 ov=unknown, ov_f=gbrap, v_f=gbrp, a=unknown, ca=unknown, ca_f=unknown +bgr555 = align=1:1 ov=unknown, ov_f=gbrap, v_f=gbrp, a=unknown, ca=unknown, ca_f=unknown +bgr555be = align=1:1 ov=unknown, ov_f=gbrap, v_f=gbrp, a=unknown, ca=unknown, ca_f=unknown +bgr565 = align=1:1 ov=unknown, ov_f=gbrap, v_f=gbrp, a=unknown, ca=unknown, ca_f=unknown +bgr565be = align=1:1 ov=unknown, ov_f=gbrap, v_f=gbrp, a=unknown, ca=unknown, ca_f=unknown +bgr8 = align=1:1 ov=unknown, ov_f=gbrap, v_f=gbrp, a=unknown, ca=unknown, ca_f=unknown +bgra = align=1:1 ov=unknown, ov_f=gbrap, v_f=gbrap, a=unknown, ca=unknown, ca_f=unknown +bgra64 = align=1:1 ov=unknown, ov_f=gbrapf32, v_f=gbrapf32, a=unknown, ca=unknown, ca_f=unknown +bgra64be = align=1:1 ov=unknown, ov_f=gbrapf32, v_f=gbrapf32, a=unknown, ca=unknown, ca_f=unknown +cuda = no +d3d11 = no +d3d11va_vld = no +d3d12 = no +drm_prime = no +dxva2_vld = no +gbrap = align=1:1 ov=unknown, ov_f=gbrap, v_f=gbrap, a=unknown, ca=unknown, ca_f=unknown +gbrap10 = align=1:1 ov=unknown, ov_f=gbrapf32, v_f=gbrapf32, a=unknown, ca=unknown, ca_f=unknown +gbrap10be = align=1:1 ov=unknown, ov_f=gbrapf32, v_f=gbrapf32, a=unknown, ca=unknown, ca_f=unknown +gbrap12 = align=1:1 ov=unknown, ov_f=gbrapf32, v_f=gbrapf32, a=unknown, ca=unknown, ca_f=unknown +gbrap12be = align=1:1 ov=unknown, ov_f=gbrapf32, v_f=gbrapf32, a=unknown, ca=unknown, ca_f=unknown +gbrap14 = align=1:1 ov=unknown, ov_f=gbrapf32, v_f=gbrapf32, a=unknown, ca=unknown, ca_f=unknown +gbrap14be = align=1:1 ov=unknown, ov_f=gbrapf32, v_f=gbrapf32, a=unknown, ca=unknown, ca_f=unknown +gbrap16 = align=1:1 ov=unknown, ov_f=gbrapf32, v_f=gbrapf32, a=unknown, ca=unknown, ca_f=unknown +gbrap16be = align=1:1 ov=unknown, ov_f=gbrapf32, v_f=gbrapf32, a=unknown, ca=unknown, ca_f=unknown +gbrapf32 = align=1:1 ov=unknown, ov_f=gbrapf32, v_f=gbrapf32, a=unknown, ca=unknown, ca_f=unknown +gbrapf32be = align=1:1 ov=unknown, ov_f=gbrapf32, v_f=gbrapf32, a=unknown, ca=unknown, ca_f=unknown +gbrp = align=1:1 ov=unknown, ov_f=gbrap, v_f=gbrp, a=unknown, ca=unknown, ca_f=unknown +gbrp1 = align=1:1 ov=unknown, ov_f=gbrapf32, v_f=gbrpf32, a=unknown, ca=unknown, ca_f=unknown +gbrp10 = align=1:1 ov=unknown, ov_f=gbrapf32, v_f=gbrpf32, a=unknown, ca=unknown, ca_f=unknown +gbrp10be = align=1:1 ov=unknown, ov_f=gbrapf32, v_f=gbrpf32, a=unknown, ca=unknown, ca_f=unknown +gbrp12 = align=1:1 ov=unknown, ov_f=gbrapf32, v_f=gbrpf32, a=unknown, ca=unknown, ca_f=unknown +gbrp12be = align=1:1 ov=unknown, ov_f=gbrapf32, v_f=gbrpf32, a=unknown, ca=unknown, ca_f=unknown +gbrp14 = align=1:1 ov=unknown, ov_f=gbrapf32, v_f=gbrpf32, a=unknown, ca=unknown, ca_f=unknown +gbrp14be = align=1:1 ov=unknown, ov_f=gbrapf32, v_f=gbrpf32, a=unknown, ca=unknown, ca_f=unknown +gbrp16 = align=1:1 ov=unknown, ov_f=gbrapf32, v_f=gbrpf32, a=unknown, ca=unknown, ca_f=unknown +gbrp16be = align=1:1 ov=unknown, ov_f=gbrapf32, v_f=gbrpf32, a=unknown, ca=unknown, ca_f=unknown +gbrp2 = align=1:1 ov=unknown, ov_f=gbrapf32, v_f=gbrpf32, a=unknown, ca=unknown, ca_f=unknown +gbrp3 = align=1:1 ov=unknown, ov_f=gbrapf32, v_f=gbrpf32, a=unknown, ca=unknown, ca_f=unknown +gbrp4 = align=1:1 ov=unknown, ov_f=gbrapf32, v_f=gbrpf32, a=unknown, ca=unknown, ca_f=unknown +gbrp5 = align=1:1 ov=unknown, ov_f=gbrapf32, v_f=gbrpf32, a=unknown, ca=unknown, ca_f=unknown +gbrp6 = align=1:1 ov=unknown, ov_f=gbrapf32, v_f=gbrpf32, a=unknown, ca=unknown, ca_f=unknown +gbrp9 = align=1:1 ov=unknown, ov_f=gbrapf32, v_f=gbrpf32, a=unknown, ca=unknown, ca_f=unknown +gbrp9be = align=1:1 ov=unknown, ov_f=gbrapf32, v_f=gbrpf32, a=unknown, ca=unknown, ca_f=unknown +gbrpf32 = align=1:1 ov=unknown, ov_f=gbrapf32, v_f=gbrpf32, a=unknown, ca=unknown, ca_f=unknown +gbrpf32be = align=1:1 ov=unknown, ov_f=gbrapf32, v_f=gbrpf32, a=unknown, ca=unknown, ca_f=unknown +gray = align=1:1 ov=yap8 , ov_f=grayaf32, v_f=grayf32, a=unknown, ca=unknown, ca_f=unknown +gray10 = align=1:1 ov=yap8 , ov_f=grayaf32, v_f=grayf32, a=unknown, ca=unknown, ca_f=unknown +gray10be = align=1:1 ov=yap8 , ov_f=grayaf32, v_f=grayf32, a=unknown, ca=unknown, ca_f=unknown +gray12 = align=1:1 ov=yap8 , ov_f=grayaf32, v_f=grayf32, a=unknown, ca=unknown, ca_f=unknown +gray12be = align=1:1 ov=yap8 , ov_f=grayaf32, v_f=grayf32, a=unknown, ca=unknown, ca_f=unknown +gray14 = align=1:1 ov=yap8 , ov_f=grayaf32, v_f=grayf32, a=unknown, ca=unknown, ca_f=unknown +gray14be = align=1:1 ov=yap8 , ov_f=grayaf32, v_f=grayf32, a=unknown, ca=unknown, ca_f=unknown +gray16 = align=1:1 ov=yap8 , ov_f=grayaf32, v_f=grayf32, a=unknown, ca=unknown, ca_f=unknown +gray16be = align=1:1 ov=yap8 , ov_f=grayaf32, v_f=grayf32, a=unknown, ca=unknown, ca_f=unknown +gray9 = align=1:1 ov=yap8 , ov_f=grayaf32, v_f=grayf32, a=unknown, ca=unknown, ca_f=unknown +gray9be = align=1:1 ov=yap8 , ov_f=grayaf32, v_f=grayf32, a=unknown, ca=unknown, ca_f=unknown +grayaf32 = align=1:1 ov=yap8 , ov_f=grayaf32, v_f=grayaf32, a=unknown, ca=unknown, ca_f=unknown +grayf32 = align=1:1 ov=yap8 , ov_f=grayaf32, v_f=grayf32, a=unknown, ca=unknown, ca_f=unknown +grayf32be = align=1:1 ov=yap8 , ov_f=grayaf32, v_f=grayf32, a=unknown, ca=unknown, ca_f=unknown +mediacodec = no +mmal = no +monob = align=8:1 ov=yap8 , ov_f=grayaf32, v_f=grayf32, a=unknown, ca=unknown, ca_f=unknown +monow = align=8:1 ov=yap8 , ov_f=grayaf32, v_f=grayf32, a=unknown, ca=unknown, ca_f=unknown +nv12 = align=2:2 ov=yuva420p, ov_f=yuva420pf, v_f=yuv420pf, a=gray, ca=gray, ca_f=grayf32 +nv16 = align=2:1 ov=yuva422p, ov_f=yuva422pf, v_f=yuv422pf, a=gray, ca=gray, ca_f=grayf32 +nv20 = align=2:1 ov=yuva422p, ov_f=yuva422pf, v_f=yuv422pf, a=gray, ca=gray, ca_f=grayf32 +nv20be = align=2:1 ov=yuva422p, ov_f=yuva422pf, v_f=yuv422pf, a=gray, ca=gray, ca_f=grayf32 +nv21 = align=2:2 ov=yuva420p, ov_f=yuva420pf, v_f=yuv420pf, a=gray, ca=gray, ca_f=grayf32 +nv24 = align=1:1 ov=yuva444p, ov_f=yuva444pf, v_f=yuv444pf, a=unknown, ca=unknown, ca_f=unknown +nv42 = align=1:1 ov=yuva444p, ov_f=yuva444pf, v_f=yuv444pf, a=unknown, ca=unknown, ca_f=unknown +opencl = no +p010 = align=2:2 ov=yuva420p, ov_f=yuva420pf, v_f=yuv420pf, a=gray, ca=gray, ca_f=grayf32 +p010be = align=2:2 ov=yuva420p, ov_f=yuva420pf, v_f=yuv420pf, a=gray, ca=gray, ca_f=grayf32 +p012 = align=2:2 ov=yuva420p, ov_f=yuva420pf, v_f=yuv420pf, a=gray, ca=gray, ca_f=grayf32 +p012be = align=2:2 ov=yuva420p, ov_f=yuva420pf, v_f=yuv420pf, a=gray, ca=gray, ca_f=grayf32 +p016 = align=2:2 ov=yuva420p, ov_f=yuva420pf, v_f=yuv420pf, a=gray, ca=gray, ca_f=grayf32 +p016be = align=2:2 ov=yuva420p, ov_f=yuva420pf, v_f=yuv420pf, a=gray, ca=gray, ca_f=grayf32 +p210 = align=2:1 ov=yuva422p, ov_f=yuva422pf, v_f=yuv422pf, a=gray, ca=gray, ca_f=grayf32 +p210be = align=2:1 ov=yuva422p, ov_f=yuva422pf, v_f=yuv422pf, a=gray, ca=gray, ca_f=grayf32 +p212 = align=2:1 ov=yuva422p, ov_f=yuva422pf, v_f=yuv422pf, a=gray, ca=gray, ca_f=grayf32 +p212be = align=2:1 ov=yuva422p, ov_f=yuva422pf, v_f=yuv422pf, a=gray, ca=gray, ca_f=grayf32 +p216 = align=2:1 ov=yuva422p, ov_f=yuva422pf, v_f=yuv422pf, a=gray, ca=gray, ca_f=grayf32 +p216be = align=2:1 ov=yuva422p, ov_f=yuva422pf, v_f=yuv422pf, a=gray, ca=gray, ca_f=grayf32 +p410 = align=1:1 ov=yuva444p, ov_f=yuva444pf, v_f=yuv444pf, a=unknown, ca=unknown, ca_f=unknown +p410be = align=1:1 ov=yuva444p, ov_f=yuva444pf, v_f=yuv444pf, a=unknown, ca=unknown, ca_f=unknown +p412 = align=1:1 ov=yuva444p, ov_f=yuva444pf, v_f=yuv444pf, a=unknown, ca=unknown, ca_f=unknown +p412be = align=1:1 ov=yuva444p, ov_f=yuva444pf, v_f=yuv444pf, a=unknown, ca=unknown, ca_f=unknown +p416 = align=1:1 ov=yuva444p, ov_f=yuva444pf, v_f=yuv444pf, a=unknown, ca=unknown, ca_f=unknown +p416be = align=1:1 ov=yuva444p, ov_f=yuva444pf, v_f=yuv444pf, a=unknown, ca=unknown, ca_f=unknown +pal8 = no +qsv = no +rgb0 = align=1:1 ov=unknown, ov_f=gbrap, v_f=gbrp, a=unknown, ca=unknown, ca_f=unknown +rgb24 = align=1:1 ov=unknown, ov_f=gbrap, v_f=gbrp, a=unknown, ca=unknown, ca_f=unknown +rgb30 = align=1:1 ov=unknown, ov_f=gbrapf32, v_f=gbrpf32, a=unknown, ca=unknown, ca_f=unknown +rgb4 = no +rgb444 = align=1:1 ov=unknown, ov_f=gbrap, v_f=gbrp, a=unknown, ca=unknown, ca_f=unknown +rgb444be = align=1:1 ov=unknown, ov_f=gbrap, v_f=gbrp, a=unknown, ca=unknown, ca_f=unknown +rgb48 = align=1:1 ov=unknown, ov_f=gbrapf32, v_f=gbrpf32, a=unknown, ca=unknown, ca_f=unknown +rgb48be = align=1:1 ov=unknown, ov_f=gbrapf32, v_f=gbrpf32, a=unknown, ca=unknown, ca_f=unknown +rgb4_byte = align=1:1 ov=unknown, ov_f=gbrap, v_f=gbrp, a=unknown, ca=unknown, ca_f=unknown +rgb555 = align=1:1 ov=unknown, ov_f=gbrap, v_f=gbrp, a=unknown, ca=unknown, ca_f=unknown +rgb555be = align=1:1 ov=unknown, ov_f=gbrap, v_f=gbrp, a=unknown, ca=unknown, ca_f=unknown +rgb565 = align=1:1 ov=unknown, ov_f=gbrap, v_f=gbrp, a=unknown, ca=unknown, ca_f=unknown +rgb565be = align=1:1 ov=unknown, ov_f=gbrap, v_f=gbrp, a=unknown, ca=unknown, ca_f=unknown +rgb8 = align=1:1 ov=unknown, ov_f=gbrap, v_f=gbrp, a=unknown, ca=unknown, ca_f=unknown +rgba = align=1:1 ov=unknown, ov_f=gbrap, v_f=gbrap, a=unknown, ca=unknown, ca_f=unknown +rgba64 = align=1:1 ov=unknown, ov_f=gbrapf32, v_f=gbrapf32, a=unknown, ca=unknown, ca_f=unknown +rgba64be = align=1:1 ov=unknown, ov_f=gbrapf32, v_f=gbrapf32, a=unknown, ca=unknown, ca_f=unknown +rgbaf16 = no +rgbaf16be = no +rgbaf32 = no +rgbaf32be = no +rgbf32 = no +rgbf32be = no +uyvy422 = align=2:1 ov=yuva422p, ov_f=yuva422pf, v_f=yuv422pf, a=gray, ca=gray, ca_f=grayf32 +uyyvyy411 = no +vaapi = no +vdpau = no +vdpau_output= no +videotoolbox= no +vulkan = no +vuya = align=1:1 ov=yuva444p, ov_f=yuva444pf, v_f=yuva444pf, a=unknown, ca=unknown, ca_f=unknown +vuyx = align=1:1 ov=yuva444p, ov_f=yuva444pf, v_f=yuv444pf, a=unknown, ca=unknown, ca_f=unknown +x2bgr10 = align=1:1 ov=unknown, ov_f=gbrapf32, v_f=gbrpf32, a=unknown, ca=unknown, ca_f=unknown +x2bgr10be = align=1:1 ov=unknown, ov_f=gbrapf32, v_f=gbrpf32, a=unknown, ca=unknown, ca_f=unknown +x2rgb10be = align=1:1 ov=unknown, ov_f=gbrapf32, v_f=gbrpf32, a=unknown, ca=unknown, ca_f=unknown +xv30 = align=1:1 ov=yuva444p, ov_f=yuva444pf, v_f=yuv444pf, a=unknown, ca=unknown, ca_f=unknown +xv30be = align=1:1 ov=yuva444p, ov_f=yuva444pf, v_f=yuv444pf, a=unknown, ca=unknown, ca_f=unknown +xv36 = align=1:1 ov=yuva444p, ov_f=yuva444pf, v_f=yuv444pf, a=unknown, ca=unknown, ca_f=unknown +xv36be = align=1:1 ov=yuva444p, ov_f=yuva444pf, v_f=yuv444pf, a=unknown, ca=unknown, ca_f=unknown +xyz12 = align=1:1 ov=gbrap , ov_f=gbrapf32, v_f=gbrpf32, a=unknown, ca=unknown, ca_f=unknown +xyz12be = align=1:1 ov=gbrap , ov_f=gbrapf32, v_f=gbrpf32, a=unknown, ca=unknown, ca_f=unknown +y1 = no +y210 = align=2:1 ov=yuva422p, ov_f=yuva422pf, v_f=yuv422pf, a=gray, ca=gray, ca_f=grayf32 +y210be = align=2:1 ov=yuva422p, ov_f=yuva422pf, v_f=yuv422pf, a=gray, ca=gray, ca_f=grayf32 +y212 = align=2:1 ov=yuva422p, ov_f=yuva422pf, v_f=yuv422pf, a=gray, ca=gray, ca_f=grayf32 +y212be = align=2:1 ov=yuva422p, ov_f=yuva422pf, v_f=yuv422pf, a=gray, ca=gray, ca_f=grayf32 +ya16 = align=1:1 ov=yap8 , ov_f=grayaf32, v_f=grayaf32, a=unknown, ca=unknown, ca_f=unknown +ya16be = align=1:1 ov=yap8 , ov_f=grayaf32, v_f=grayaf32, a=unknown, ca=unknown, ca_f=unknown +ya8 = align=1:1 ov=yap8 , ov_f=grayaf32, v_f=grayaf32, a=unknown, ca=unknown, ca_f=unknown +yap16 = align=1:1 ov=yap8 , ov_f=grayaf32, v_f=grayaf32, a=unknown, ca=unknown, ca_f=unknown +yap8 = align=1:1 ov=yap8 , ov_f=grayaf32, v_f=grayaf32, a=unknown, ca=unknown, ca_f=unknown +yuv410p = no +yuv410pf = no +yuv411p = no +yuv411pf = no +yuv420p = align=2:2 ov=yuva420p, ov_f=yuva420pf, v_f=yuv420pf, a=gray, ca=gray, ca_f=grayf32 +yuv420p10 = align=2:2 ov=yuva420p, ov_f=yuva420pf, v_f=yuv420pf, a=gray, ca=gray, ca_f=grayf32 +yuv420p10be = align=2:2 ov=yuva420p, ov_f=yuva420pf, v_f=yuv420pf, a=gray, ca=gray, ca_f=grayf32 +yuv420p12 = align=2:2 ov=yuva420p, ov_f=yuva420pf, v_f=yuv420pf, a=gray, ca=gray, ca_f=grayf32 +yuv420p12be = align=2:2 ov=yuva420p, ov_f=yuva420pf, v_f=yuv420pf, a=gray, ca=gray, ca_f=grayf32 +yuv420p14 = align=2:2 ov=yuva420p, ov_f=yuva420pf, v_f=yuv420pf, a=gray, ca=gray, ca_f=grayf32 +yuv420p14be = align=2:2 ov=yuva420p, ov_f=yuva420pf, v_f=yuv420pf, a=gray, ca=gray, ca_f=grayf32 +yuv420p16 = align=2:2 ov=yuva420p, ov_f=yuva420pf, v_f=yuv420pf, a=gray, ca=gray, ca_f=grayf32 +yuv420p16be = align=2:2 ov=yuva420p, ov_f=yuva420pf, v_f=yuv420pf, a=gray, ca=gray, ca_f=grayf32 +yuv420p9 = align=2:2 ov=yuva420p, ov_f=yuva420pf, v_f=yuv420pf, a=gray, ca=gray, ca_f=grayf32 +yuv420p9be = align=2:2 ov=yuva420p, ov_f=yuva420pf, v_f=yuv420pf, a=gray, ca=gray, ca_f=grayf32 +yuv420pf = align=2:2 ov=yuva420p, ov_f=yuva420pf, v_f=yuv420pf, a=gray, ca=gray, ca_f=grayf32 +yuv422p = align=2:1 ov=yuva422p, ov_f=yuva422pf, v_f=yuv422pf, a=gray, ca=gray, ca_f=grayf32 +yuv422p10 = align=2:1 ov=yuva422p, ov_f=yuva422pf, v_f=yuv422pf, a=gray, ca=gray, ca_f=grayf32 +yuv422p10be = align=2:1 ov=yuva422p, ov_f=yuva422pf, v_f=yuv422pf, a=gray, ca=gray, ca_f=grayf32 +yuv422p12 = align=2:1 ov=yuva422p, ov_f=yuva422pf, v_f=yuv422pf, a=gray, ca=gray, ca_f=grayf32 +yuv422p12be = align=2:1 ov=yuva422p, ov_f=yuva422pf, v_f=yuv422pf, a=gray, ca=gray, ca_f=grayf32 +yuv422p14 = align=2:1 ov=yuva422p, ov_f=yuva422pf, v_f=yuv422pf, a=gray, ca=gray, ca_f=grayf32 +yuv422p14be = align=2:1 ov=yuva422p, ov_f=yuva422pf, v_f=yuv422pf, a=gray, ca=gray, ca_f=grayf32 +yuv422p16 = align=2:1 ov=yuva422p, ov_f=yuva422pf, v_f=yuv422pf, a=gray, ca=gray, ca_f=grayf32 +yuv422p16be = align=2:1 ov=yuva422p, ov_f=yuva422pf, v_f=yuv422pf, a=gray, ca=gray, ca_f=grayf32 +yuv422p9 = align=2:1 ov=yuva422p, ov_f=yuva422pf, v_f=yuv422pf, a=gray, ca=gray, ca_f=grayf32 +yuv422p9be = align=2:1 ov=yuva422p, ov_f=yuva422pf, v_f=yuv422pf, a=gray, ca=gray, ca_f=grayf32 +yuv422pf = align=2:1 ov=yuva422p, ov_f=yuva422pf, v_f=yuv422pf, a=gray, ca=gray, ca_f=grayf32 +yuv440p = no +yuv440p10 = no +yuv440p10be = no +yuv440p12 = no +yuv440p12be = no +yuv440pf = no +yuv444p = align=1:1 ov=yuva444p, ov_f=yuva444pf, v_f=yuv444pf, a=unknown, ca=unknown, ca_f=unknown +yuv444p10 = align=1:1 ov=yuva444p, ov_f=yuva444pf, v_f=yuv444pf, a=unknown, ca=unknown, ca_f=unknown +yuv444p10be = align=1:1 ov=yuva444p, ov_f=yuva444pf, v_f=yuv444pf, a=unknown, ca=unknown, ca_f=unknown +yuv444p12 = align=1:1 ov=yuva444p, ov_f=yuva444pf, v_f=yuv444pf, a=unknown, ca=unknown, ca_f=unknown +yuv444p12be = align=1:1 ov=yuva444p, ov_f=yuva444pf, v_f=yuv444pf, a=unknown, ca=unknown, ca_f=unknown +yuv444p14 = align=1:1 ov=yuva444p, ov_f=yuva444pf, v_f=yuv444pf, a=unknown, ca=unknown, ca_f=unknown +yuv444p14be = align=1:1 ov=yuva444p, ov_f=yuva444pf, v_f=yuv444pf, a=unknown, ca=unknown, ca_f=unknown +yuv444p16 = align=1:1 ov=yuva444p, ov_f=yuva444pf, v_f=yuv444pf, a=unknown, ca=unknown, ca_f=unknown +yuv444p16be = align=1:1 ov=yuva444p, ov_f=yuva444pf, v_f=yuv444pf, a=unknown, ca=unknown, ca_f=unknown +yuv444p9 = align=1:1 ov=yuva444p, ov_f=yuva444pf, v_f=yuv444pf, a=unknown, ca=unknown, ca_f=unknown +yuv444p9be = align=1:1 ov=yuva444p, ov_f=yuva444pf, v_f=yuv444pf, a=unknown, ca=unknown, ca_f=unknown +yuv444pf = align=1:1 ov=yuva444p, ov_f=yuva444pf, v_f=yuv444pf, a=unknown, ca=unknown, ca_f=unknown +yuva410pf = no +yuva411pf = no +yuva420p = align=2:2 ov=yuva420p, ov_f=yuva420pf, v_f=yuva420pf, a=gray, ca=gray, ca_f=grayf32 +yuva420p10 = align=2:2 ov=yuva420p, ov_f=yuva420pf, v_f=yuva420pf, a=gray, ca=gray, ca_f=grayf32 +yuva420p10be= align=2:2 ov=yuva420p, ov_f=yuva420pf, v_f=yuva420pf, a=gray, ca=gray, ca_f=grayf32 +yuva420p16 = align=2:2 ov=yuva420p, ov_f=yuva420pf, v_f=yuva420pf, a=gray, ca=gray, ca_f=grayf32 +yuva420p16be= align=2:2 ov=yuva420p, ov_f=yuva420pf, v_f=yuva420pf, a=gray, ca=gray, ca_f=grayf32 +yuva420p9 = align=2:2 ov=yuva420p, ov_f=yuva420pf, v_f=yuva420pf, a=gray, ca=gray, ca_f=grayf32 +yuva420p9be = align=2:2 ov=yuva420p, ov_f=yuva420pf, v_f=yuva420pf, a=gray, ca=gray, ca_f=grayf32 +yuva420pf = align=2:2 ov=yuva420p, ov_f=yuva420pf, v_f=yuva420pf, a=gray, ca=gray, ca_f=grayf32 +yuva422p = align=2:1 ov=yuva422p, ov_f=yuva422pf, v_f=yuva422pf, a=gray, ca=gray, ca_f=grayf32 +yuva422p10 = align=2:1 ov=yuva422p, ov_f=yuva422pf, v_f=yuva422pf, a=gray, ca=gray, ca_f=grayf32 +yuva422p10be= align=2:1 ov=yuva422p, ov_f=yuva422pf, v_f=yuva422pf, a=gray, ca=gray, ca_f=grayf32 +yuva422p12 = align=2:1 ov=yuva422p, ov_f=yuva422pf, v_f=yuva422pf, a=gray, ca=gray, ca_f=grayf32 +yuva422p12be= align=2:1 ov=yuva422p, ov_f=yuva422pf, v_f=yuva422pf, a=gray, ca=gray, ca_f=grayf32 +yuva422p16 = align=2:1 ov=yuva422p, ov_f=yuva422pf, v_f=yuva422pf, a=gray, ca=gray, ca_f=grayf32 +yuva422p16be= align=2:1 ov=yuva422p, ov_f=yuva422pf, v_f=yuva422pf, a=gray, ca=gray, ca_f=grayf32 +yuva422p9 = align=2:1 ov=yuva422p, ov_f=yuva422pf, v_f=yuva422pf, a=gray, ca=gray, ca_f=grayf32 +yuva422p9be = align=2:1 ov=yuva422p, ov_f=yuva422pf, v_f=yuva422pf, a=gray, ca=gray, ca_f=grayf32 +yuva422pf = align=2:1 ov=yuva422p, ov_f=yuva422pf, v_f=yuva422pf, a=gray, ca=gray, ca_f=grayf32 +yuva440pf = no +yuva444p = align=1:1 ov=yuva444p, ov_f=yuva444pf, v_f=yuva444pf, a=unknown, ca=unknown, ca_f=unknown +yuva444p10 = align=1:1 ov=yuva444p, ov_f=yuva444pf, v_f=yuva444pf, a=unknown, ca=unknown, ca_f=unknown +yuva444p10be= align=1:1 ov=yuva444p, ov_f=yuva444pf, v_f=yuva444pf, a=unknown, ca=unknown, ca_f=unknown +yuva444p12 = align=1:1 ov=yuva444p, ov_f=yuva444pf, v_f=yuva444pf, a=unknown, ca=unknown, ca_f=unknown +yuva444p12be= align=1:1 ov=yuva444p, ov_f=yuva444pf, v_f=yuva444pf, a=unknown, ca=unknown, ca_f=unknown +yuva444p16 = align=1:1 ov=yuva444p, ov_f=yuva444pf, v_f=yuva444pf, a=unknown, ca=unknown, ca_f=unknown +yuva444p16be= align=1:1 ov=yuva444p, ov_f=yuva444pf, v_f=yuva444pf, a=unknown, ca=unknown, ca_f=unknown +yuva444p9 = align=1:1 ov=yuva444p, ov_f=yuva444pf, v_f=yuva444pf, a=unknown, ca=unknown, ca_f=unknown +yuva444p9be = align=1:1 ov=yuva444p, ov_f=yuva444pf, v_f=yuva444pf, a=unknown, ca=unknown, ca_f=unknown +yuva444pf = align=1:1 ov=yuva444p, ov_f=yuva444pf, v_f=yuva444pf, a=unknown, ca=unknown, ca_f=unknown +yuvj411p = no +yuvj422p = align=2:1 ov=yuva422p, ov_f=yuva422pf, v_f=yuv422pf, a=gray, ca=gray, ca_f=grayf32 +yuvj440p = no +yuyv422 = align=2:1 ov=yuva422p, ov_f=yuva422pf, v_f=yuv422pf, a=gray, ca=gray, ca_f=grayf32 +yvyu422 = align=2:1 ov=yuva422p, ov_f=yuva422pf, v_f=yuv422pf, a=gray, ca=gray, ca_f=grayf32 diff --git a/test/ref/ffmpeg7/img_formats.txt b/test/ref/ffmpeg7/img_formats.txt new file mode 100644 index 0000000..9e67aa8 --- /dev/null +++ b/test/ref/ffmpeg7/img_formats.txt @@ -0,0 +1,2834 @@ +0bgr: fcsp=rgb ctype=uint + Basic desc: [ba][bb][rgb][le][be][uint] + planes=1, chroma=0:0 align=1:1 + {32/[0:0] } + 0: 32bits {24:8} {16:8} {8:8} {} + Regular: planes=1 compbytes=1 bitpad=0 chroma=1x1 ctype=uint + 0: {0, 3, 2, 1} + AVD: name=0bgr chroma=0:0 flags=0x20 [rgb] + 0: p=0 st=4 o=3 sh=0 d=8 + 1: p=0 st=4 o=2 sh=0 d=8 + 2: p=0 st=4 o=1 sh=0 d=8 +0rgb: fcsp=rgb ctype=uint + Basic desc: [ba][bb][rgb][le][be][uint] + planes=1, chroma=0:0 align=1:1 + {32/[0:0] } + 0: 32bits {8:8} {16:8} {24:8} {} + Regular: planes=1 compbytes=1 bitpad=0 chroma=1x1 ctype=uint + 0: {0, 1, 2, 3} + AVD: name=0rgb chroma=0:0 flags=0x20 [rgb] + 0: p=0 st=4 o=1 sh=0 d=8 + 1: p=0 st=4 o=2 sh=0 d=8 + 2: p=0 st=4 o=3 sh=0 d=8 +abgr: fcsp=rgb ctype=uint + Basic desc: [ba][bb][a][rgb][le][be][uint] + planes=1, chroma=0:0 align=1:1 + {32/[0:0] } + 0: 32bits {24:8} {16:8} {8:8} {0:8} + Regular: planes=1 compbytes=1 bitpad=0 chroma=1x1 ctype=uint + 0: {4, 3, 2, 1} + AVD: name=abgr chroma=0:0 flags=0xa0 [rgb][alpha] + 0: p=0 st=4 o=3 sh=0 d=8 + 1: p=0 st=4 o=2 sh=0 d=8 + 2: p=0 st=4 o=1 sh=0 d=8 + 3: p=0 st=4 o=0 sh=0 d=8 +argb: fcsp=rgb ctype=uint + Basic desc: [ba][bb][a][rgb][le][be][uint] + planes=1, chroma=0:0 align=1:1 + {32/[0:0] } + 0: 32bits {8:8} {16:8} {24:8} {0:8} + Regular: planes=1 compbytes=1 bitpad=0 chroma=1x1 ctype=uint + 0: {4, 1, 2, 3} + AVD: name=argb chroma=0:0 flags=0xa0 [rgb][alpha] + 0: p=0 st=4 o=1 sh=0 d=8 + 1: p=0 st=4 o=2 sh=0 d=8 + 2: p=0 st=4 o=3 sh=0 d=8 + 3: p=0 st=4 o=0 sh=0 d=8 +ayuv64: [GENERIC] ctype=uint + Basic desc: [ba][bb][a][yuv][le][uint] + planes=1, chroma=0:0 align=1:1 + {64/[0:0] } + 0: 64bits {16:16} {32:16} {48:16} {0:16} + Regular: planes=1 compbytes=2 bitpad=0 chroma=1x1 ctype=uint + 0: {4, 1, 2, 3} + AVD: name=ayuv64le chroma=0:0 flags=0x80 [alpha] + 0: p=0 st=8 o=2 sh=0 d=16 + 1: p=0 st=8 o=4 sh=0 d=16 + 2: p=0 st=8 o=6 sh=0 d=16 + 3: p=0 st=8 o=0 sh=0 d=16 +ayuv64be: [GENERIC] ctype=uint + Basic desc: [ba][bb][a][yuv][be][uint] + planes=1, chroma=0:0 align=1:1 + {64/[0:0] } + 0: 64bits endian_bytes=2 {16:16} {32:16} {48:16} {0:16} + AVD: name=ayuv64be chroma=0:0 flags=0x81 [be][alpha] + 0: p=0 st=8 o=2 sh=0 d=16 + 1: p=0 st=8 o=4 sh=0 d=16 + 2: p=0 st=8 o=6 sh=0 d=16 + 3: p=0 st=8 o=0 sh=0 d=16 +bayer_bggr16: [GENERIC] fcsp=rgb ctype=unknown + Basic desc: [ba][rgb][le][be] + planes=1, chroma=0:0 align=1:1 + {16/[0:0] } + 0: 16bits {} {} {} {} + AVD: name=bayer_bggr16le chroma=0:0 flags=0x120 [rgb][bayer] + 0: p=0 st=2 o=0 sh=0 d=4 + 1: p=0 st=2 o=0 sh=0 d=8 + 2: p=0 st=2 o=0 sh=0 d=4 +bayer_bggr16be: [GENERIC] fcsp=rgb ctype=unknown + Basic desc: [ba][rgb][le][be] + planes=1, chroma=0:0 align=1:1 + {16/[0:0] } + 0: 16bits endian_bytes=2 {} {} {} {} + AVD: name=bayer_bggr16be chroma=0:0 flags=0x121 [be][rgb][bayer] + 0: p=0 st=2 o=0 sh=0 d=4 + 1: p=0 st=2 o=0 sh=0 d=8 + 2: p=0 st=2 o=0 sh=0 d=4 +bayer_bggr8: [GENERIC] fcsp=rgb ctype=unknown + Basic desc: [ba][rgb][le][be] + planes=1, chroma=0:0 align=1:1 + {8/[0:0] } + 0: 8bits {} {} {} {} + AVD: name=bayer_bggr8 chroma=0:0 flags=0x120 [rgb][bayer] + 0: p=0 st=1 o=0 sh=0 d=2 + 1: p=0 st=1 o=0 sh=0 d=4 + 2: p=0 st=1 o=0 sh=0 d=2 +bayer_gbrg16: [GENERIC] fcsp=rgb ctype=unknown + Basic desc: [ba][rgb][le][be] + planes=1, chroma=0:0 align=1:1 + {16/[0:0] } + 0: 16bits {} {} {} {} + AVD: name=bayer_gbrg16le chroma=0:0 flags=0x120 [rgb][bayer] + 0: p=0 st=2 o=0 sh=0 d=4 + 1: p=0 st=2 o=0 sh=0 d=8 + 2: p=0 st=2 o=0 sh=0 d=4 +bayer_gbrg16be: [GENERIC] fcsp=rgb ctype=unknown + Basic desc: [ba][rgb][le][be] + planes=1, chroma=0:0 align=1:1 + {16/[0:0] } + 0: 16bits endian_bytes=2 {} {} {} {} + AVD: name=bayer_gbrg16be chroma=0:0 flags=0x121 [be][rgb][bayer] + 0: p=0 st=2 o=0 sh=0 d=4 + 1: p=0 st=2 o=0 sh=0 d=8 + 2: p=0 st=2 o=0 sh=0 d=4 +bayer_gbrg8: [GENERIC] fcsp=rgb ctype=unknown + Basic desc: [ba][rgb][le][be] + planes=1, chroma=0:0 align=1:1 + {8/[0:0] } + 0: 8bits {} {} {} {} + AVD: name=bayer_gbrg8 chroma=0:0 flags=0x120 [rgb][bayer] + 0: p=0 st=1 o=0 sh=0 d=2 + 1: p=0 st=1 o=0 sh=0 d=4 + 2: p=0 st=1 o=0 sh=0 d=2 +bayer_grbg16: [GENERIC] fcsp=rgb ctype=unknown + Basic desc: [ba][rgb][le][be] + planes=1, chroma=0:0 align=1:1 + {16/[0:0] } + 0: 16bits {} {} {} {} + AVD: name=bayer_grbg16le chroma=0:0 flags=0x120 [rgb][bayer] + 0: p=0 st=2 o=0 sh=0 d=4 + 1: p=0 st=2 o=0 sh=0 d=8 + 2: p=0 st=2 o=0 sh=0 d=4 +bayer_grbg16be: [GENERIC] fcsp=rgb ctype=unknown + Basic desc: [ba][rgb][le][be] + planes=1, chroma=0:0 align=1:1 + {16/[0:0] } + 0: 16bits endian_bytes=2 {} {} {} {} + AVD: name=bayer_grbg16be chroma=0:0 flags=0x121 [be][rgb][bayer] + 0: p=0 st=2 o=0 sh=0 d=4 + 1: p=0 st=2 o=0 sh=0 d=8 + 2: p=0 st=2 o=0 sh=0 d=4 +bayer_grbg8: [GENERIC] fcsp=rgb ctype=unknown + Basic desc: [ba][rgb][le][be] + planes=1, chroma=0:0 align=1:1 + {8/[0:0] } + 0: 8bits {} {} {} {} + AVD: name=bayer_grbg8 chroma=0:0 flags=0x120 [rgb][bayer] + 0: p=0 st=1 o=0 sh=0 d=2 + 1: p=0 st=1 o=0 sh=0 d=4 + 2: p=0 st=1 o=0 sh=0 d=2 +bayer_rggb16: [GENERIC] fcsp=rgb ctype=unknown + Basic desc: [ba][rgb][le][be] + planes=1, chroma=0:0 align=1:1 + {16/[0:0] } + 0: 16bits {} {} {} {} + AVD: name=bayer_rggb16le chroma=0:0 flags=0x120 [rgb][bayer] + 0: p=0 st=2 o=0 sh=0 d=4 + 1: p=0 st=2 o=0 sh=0 d=8 + 2: p=0 st=2 o=0 sh=0 d=4 +bayer_rggb16be: [GENERIC] fcsp=rgb ctype=unknown + Basic desc: [ba][rgb][le][be] + planes=1, chroma=0:0 align=1:1 + {16/[0:0] } + 0: 16bits endian_bytes=2 {} {} {} {} + AVD: name=bayer_rggb16be chroma=0:0 flags=0x121 [be][rgb][bayer] + 0: p=0 st=2 o=0 sh=0 d=4 + 1: p=0 st=2 o=0 sh=0 d=8 + 2: p=0 st=2 o=0 sh=0 d=4 +bayer_rggb8: [GENERIC] fcsp=rgb ctype=unknown + Basic desc: [ba][rgb][le][be] + planes=1, chroma=0:0 align=1:1 + {8/[0:0] } + 0: 8bits {} {} {} {} + AVD: name=bayer_rggb8 chroma=0:0 flags=0x120 [rgb][bayer] + 0: p=0 st=1 o=0 sh=0 d=2 + 1: p=0 st=1 o=0 sh=0 d=4 + 2: p=0 st=1 o=0 sh=0 d=2 +bgr0: fcsp=rgb ctype=uint + Basic desc: [ba][bb][rgb][le][be][uint] + planes=1, chroma=0:0 align=1:1 + {32/[0:0] } + 0: 32bits {16:8} {8:8} {0:8} {} + Regular: planes=1 compbytes=1 bitpad=0 chroma=1x1 ctype=uint + 0: {3, 2, 1, 0} + AVD: name=bgr0 chroma=0:0 flags=0x20 [rgb] + 0: p=0 st=4 o=2 sh=0 d=8 + 1: p=0 st=4 o=1 sh=0 d=8 + 2: p=0 st=4 o=0 sh=0 d=8 +bgr24: fcsp=rgb ctype=uint + Basic desc: [ba][bb][rgb][le][be][uint] + planes=1, chroma=0:0 align=1:1 + {24/[0:0] } + 0: 24bits {16:8} {8:8} {0:8} {} + Regular: planes=1 compbytes=1 bitpad=0 chroma=1x1 ctype=uint + 0: {3, 2, 1} + AVD: name=bgr24 chroma=0:0 flags=0x20 [rgb] + 0: p=0 st=3 o=2 sh=0 d=8 + 1: p=0 st=3 o=1 sh=0 d=8 + 2: p=0 st=3 o=0 sh=0 d=8 +bgr4: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [rgb][le][be][uint] + planes=1, chroma=0:0 align=2:1 + {4/[0:0] } + 0: 4bits {3:1} {1:2} {0:1} {} + AVD: name=bgr4 chroma=0:0 flags=0x24 [bs][rgb] + 0: p=0 st=4 o=3 sh=0 d=1 + 1: p=0 st=4 o=1 sh=0 d=2 + 2: p=0 st=4 o=0 sh=0 d=1 +bgr444: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [ba][rgb][le][uint] + planes=1, chroma=0:0 align=1:1 + {16/[0:0] } + 0: 16bits {0:4} {4:4} {8:4} {} + AVD: name=bgr444le chroma=0:0 flags=0x20 [rgb] + 0: p=0 st=2 o=0 sh=0 d=4 + 1: p=0 st=2 o=0 sh=4 d=4 + 2: p=0 st=2 o=1 sh=0 d=4 +bgr444be: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [ba][rgb][be][uint] + planes=1, chroma=0:0 align=1:1 + {16/[0:0] } + 0: 16bits endian_bytes=2 {0:4} {4:4} {8:4} {} + AVD: name=bgr444be chroma=0:0 flags=0x21 [be][rgb] + 0: p=0 st=2 o=0 sh=0 d=4 + 1: p=0 st=2 o=0 sh=4 d=4 + 2: p=0 st=2 o=-1 sh=0 d=4 +bgr48: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [ba][bb][rgb][le][uint] + planes=1, chroma=0:0 align=1:1 + {48/[0:0] } + 0: 48bits {32:16} {16:16} {0:16} {} + Regular: planes=1 compbytes=2 bitpad=0 chroma=1x1 ctype=uint + 0: {3, 2, 1} + AVD: name=bgr48le chroma=0:0 flags=0x20 [rgb] + 0: p=0 st=6 o=4 sh=0 d=16 + 1: p=0 st=6 o=2 sh=0 d=16 + 2: p=0 st=6 o=0 sh=0 d=16 +bgr48be: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [ba][bb][rgb][be][uint] + planes=1, chroma=0:0 align=1:1 + {48/[0:0] } + 0: 48bits endian_bytes=2 {32:16} {16:16} {0:16} {} + AVD: name=bgr48be chroma=0:0 flags=0x21 [be][rgb] + 0: p=0 st=6 o=4 sh=0 d=16 + 1: p=0 st=6 o=2 sh=0 d=16 + 2: p=0 st=6 o=0 sh=0 d=16 +bgr4_byte: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [ba][rgb][le][be][uint] + planes=1, chroma=0:0 align=1:1 + {8/[0:0] } + 0: 8bits {0:1} {1:2} {3:1} {} + AVD: name=bgr4_byte chroma=0:0 flags=0x20 [rgb] + 0: p=0 st=1 o=0 sh=0 d=1 + 1: p=0 st=1 o=0 sh=1 d=2 + 2: p=0 st=1 o=0 sh=3 d=1 +bgr555: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [ba][rgb][le][uint] + planes=1, chroma=0:0 align=1:1 + {16/[0:0] } + 0: 16bits {0:5} {5:5} {10:5} {} + AVD: name=bgr555le chroma=0:0 flags=0x20 [rgb] + 0: p=0 st=2 o=0 sh=0 d=5 + 1: p=0 st=2 o=0 sh=5 d=5 + 2: p=0 st=2 o=1 sh=2 d=5 +bgr555be: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [ba][rgb][be][uint] + planes=1, chroma=0:0 align=1:1 + {16/[0:0] } + 0: 16bits endian_bytes=2 {0:5} {5:5} {10:5} {} + AVD: name=bgr555be chroma=0:0 flags=0x21 [be][rgb] + 0: p=0 st=2 o=0 sh=0 d=5 + 1: p=0 st=2 o=0 sh=5 d=5 + 2: p=0 st=2 o=-1 sh=2 d=5 +bgr565: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [ba][rgb][le][uint] + planes=1, chroma=0:0 align=1:1 + {16/[0:0] } + 0: 16bits {0:5} {5:6} {11:5} {} + AVD: name=bgr565le chroma=0:0 flags=0x20 [rgb] + 0: p=0 st=2 o=0 sh=0 d=5 + 1: p=0 st=2 o=0 sh=5 d=6 + 2: p=0 st=2 o=1 sh=3 d=5 +bgr565be: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [ba][rgb][be][uint] + planes=1, chroma=0:0 align=1:1 + {16/[0:0] } + 0: 16bits endian_bytes=2 {0:5} {5:6} {11:5} {} + AVD: name=bgr565be chroma=0:0 flags=0x21 [be][rgb] + 0: p=0 st=2 o=0 sh=0 d=5 + 1: p=0 st=2 o=0 sh=5 d=6 + 2: p=0 st=2 o=-1 sh=3 d=5 +bgr8: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [ba][rgb][le][be][uint] + planes=1, chroma=0:0 align=1:1 + {8/[0:0] } + 0: 8bits {0:3} {3:3} {6:2} {} + AVD: name=bgr8 chroma=0:0 flags=0x20 [rgb] + 0: p=0 st=1 o=0 sh=0 d=3 + 1: p=0 st=1 o=0 sh=3 d=3 + 2: p=0 st=1 o=0 sh=6 d=2 +bgra: fcsp=rgb ctype=uint + Basic desc: [ba][bb][a][rgb][le][be][uint] + planes=1, chroma=0:0 align=1:1 + {32/[0:0] } + 0: 32bits {16:8} {8:8} {0:8} {24:8} + Regular: planes=1 compbytes=1 bitpad=0 chroma=1x1 ctype=uint + 0: {3, 2, 1, 4} + AVD: name=bgra chroma=0:0 flags=0xa0 [rgb][alpha] + 0: p=0 st=4 o=2 sh=0 d=8 + 1: p=0 st=4 o=1 sh=0 d=8 + 2: p=0 st=4 o=0 sh=0 d=8 + 3: p=0 st=4 o=3 sh=0 d=8 +bgra64: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [ba][bb][a][rgb][le][uint] + planes=1, chroma=0:0 align=1:1 + {64/[0:0] } + 0: 64bits {32:16} {16:16} {0:16} {48:16} + Regular: planes=1 compbytes=2 bitpad=0 chroma=1x1 ctype=uint + 0: {3, 2, 1, 4} + AVD: name=bgra64le chroma=0:0 flags=0xa0 [rgb][alpha] + 0: p=0 st=8 o=4 sh=0 d=16 + 1: p=0 st=8 o=2 sh=0 d=16 + 2: p=0 st=8 o=0 sh=0 d=16 + 3: p=0 st=8 o=6 sh=0 d=16 +bgra64be: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [ba][bb][a][rgb][be][uint] + planes=1, chroma=0:0 align=1:1 + {64/[0:0] } + 0: 64bits endian_bytes=2 {32:16} {16:16} {0:16} {48:16} + AVD: name=bgra64be chroma=0:0 flags=0xa1 [be][rgb][alpha] + 0: p=0 st=8 o=4 sh=0 d=16 + 1: p=0 st=8 o=2 sh=0 d=16 + 2: p=0 st=8 o=0 sh=0 d=16 + 3: p=0 st=8 o=6 sh=0 d=16 +cuda: ctype=unknown + Basic desc: [le][be][hw] + planes=0, chroma=0:0 align=1:1 + {} + AVD: name=cuda chroma=0:0 flags=0x8 [hw] +d3d11: ctype=unknown + Basic desc: [le][be][hw] + planes=0, chroma=0:0 align=1:1 + {} + AVD: name=d3d11 chroma=0:0 flags=0x8 [hw] +d3d11va_vld: [GENERIC] ctype=unknown + Basic desc: [le][be][hw] + planes=0, chroma=1:1 align=2:2 + {} + AVD: name=d3d11va_vld chroma=1:1 flags=0x8 [hw] +d3d12: [GENERIC] ctype=unknown + Basic desc: [le][be][hw] + planes=0, chroma=0:0 align=1:1 + {} + AVD: name=d3d12 chroma=0:0 flags=0x8 [hw] +drm_prime: ctype=unknown + Basic desc: [le][be][hw] + planes=0, chroma=0:0 align=1:1 + {} + AVD: name=drm_prime chroma=0:0 flags=0x8 [hw] +dxva2_vld: ctype=unknown + Basic desc: [le][be][hw] + planes=0, chroma=1:1 align=2:2 + {} + AVD: name=dxva2_vld chroma=1:1 flags=0x8 [hw] +gbrap: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [ba][bb][a][rgb][le][be][uint] + planes=4, chroma=0:0 align=1:1 + {8/[0:0] 8/[0:0] 8/[0:0] 8/[0:0] } + 0: 8bits {} {0:8} {} {} + 1: 8bits {} {} {0:8} {} + 2: 8bits {0:8} {} {} {} + 3: 8bits {} {} {} {0:8} + Regular: planes=4 compbytes=1 bitpad=0 chroma=1x1 ctype=uint + 0: {2} + 1: {3} + 2: {1} + 3: {4} + AVD: name=gbrap chroma=0:0 flags=0xb0 [planar][rgb][alpha] + 0: p=2 st=1 o=0 sh=0 d=8 + 1: p=0 st=1 o=0 sh=0 d=8 + 2: p=1 st=1 o=0 sh=0 d=8 + 3: p=3 st=1 o=0 sh=0 d=8 +gbrap10: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [ba][bb][a][rgb][le][uint] + planes=4, chroma=0:0 align=1:1 + {16/[0:0] 16/[0:0] 16/[0:0] 16/[0:0] } + 0: 16bits {} {0:16/-6} {} {} + 1: 16bits {} {} {0:16/-6} {} + 2: 16bits {0:16/-6} {} {} {} + 3: 16bits {} {} {} {0:16/-6} + Regular: planes=4 compbytes=2 bitpad=-6 chroma=1x1 ctype=uint + 0: {2} + 1: {3} + 2: {1} + 3: {4} + AVD: name=gbrap10le chroma=0:0 flags=0xb0 [planar][rgb][alpha] + 0: p=2 st=2 o=0 sh=0 d=10 + 1: p=0 st=2 o=0 sh=0 d=10 + 2: p=1 st=2 o=0 sh=0 d=10 + 3: p=3 st=2 o=0 sh=0 d=10 +gbrap10be: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [ba][bb][a][rgb][be][uint] + planes=4, chroma=0:0 align=1:1 + {16/[0:0] 16/[0:0] 16/[0:0] 16/[0:0] } + 0: 16bits endian_bytes=2 {} {0:16/-6} {} {} + 1: 16bits endian_bytes=2 {} {} {0:16/-6} {} + 2: 16bits endian_bytes=2 {0:16/-6} {} {} {} + 3: 16bits endian_bytes=2 {} {} {} {0:16/-6} + AVD: name=gbrap10be chroma=0:0 flags=0xb1 [be][planar][rgb][alpha] + 0: p=2 st=2 o=0 sh=0 d=10 + 1: p=0 st=2 o=0 sh=0 d=10 + 2: p=1 st=2 o=0 sh=0 d=10 + 3: p=3 st=2 o=0 sh=0 d=10 +gbrap12: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [ba][bb][a][rgb][le][uint] + planes=4, chroma=0:0 align=1:1 + {16/[0:0] 16/[0:0] 16/[0:0] 16/[0:0] } + 0: 16bits {} {0:16/-4} {} {} + 1: 16bits {} {} {0:16/-4} {} + 2: 16bits {0:16/-4} {} {} {} + 3: 16bits {} {} {} {0:16/-4} + Regular: planes=4 compbytes=2 bitpad=-4 chroma=1x1 ctype=uint + 0: {2} + 1: {3} + 2: {1} + 3: {4} + AVD: name=gbrap12le chroma=0:0 flags=0xb0 [planar][rgb][alpha] + 0: p=2 st=2 o=0 sh=0 d=12 + 1: p=0 st=2 o=0 sh=0 d=12 + 2: p=1 st=2 o=0 sh=0 d=12 + 3: p=3 st=2 o=0 sh=0 d=12 +gbrap12be: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [ba][bb][a][rgb][be][uint] + planes=4, chroma=0:0 align=1:1 + {16/[0:0] 16/[0:0] 16/[0:0] 16/[0:0] } + 0: 16bits endian_bytes=2 {} {0:16/-4} {} {} + 1: 16bits endian_bytes=2 {} {} {0:16/-4} {} + 2: 16bits endian_bytes=2 {0:16/-4} {} {} {} + 3: 16bits endian_bytes=2 {} {} {} {0:16/-4} + AVD: name=gbrap12be chroma=0:0 flags=0xb1 [be][planar][rgb][alpha] + 0: p=2 st=2 o=0 sh=0 d=12 + 1: p=0 st=2 o=0 sh=0 d=12 + 2: p=1 st=2 o=0 sh=0 d=12 + 3: p=3 st=2 o=0 sh=0 d=12 +gbrap14: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [ba][bb][a][rgb][le][uint] + planes=4, chroma=0:0 align=1:1 + {16/[0:0] 16/[0:0] 16/[0:0] 16/[0:0] } + 0: 16bits {} {0:16/-2} {} {} + 1: 16bits {} {} {0:16/-2} {} + 2: 16bits {0:16/-2} {} {} {} + 3: 16bits {} {} {} {0:16/-2} + Regular: planes=4 compbytes=2 bitpad=-2 chroma=1x1 ctype=uint + 0: {2} + 1: {3} + 2: {1} + 3: {4} + AVD: name=gbrap14le chroma=0:0 flags=0xb0 [planar][rgb][alpha] + 0: p=2 st=2 o=0 sh=0 d=14 + 1: p=0 st=2 o=0 sh=0 d=14 + 2: p=1 st=2 o=0 sh=0 d=14 + 3: p=3 st=2 o=0 sh=0 d=14 +gbrap14be: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [ba][bb][a][rgb][be][uint] + planes=4, chroma=0:0 align=1:1 + {16/[0:0] 16/[0:0] 16/[0:0] 16/[0:0] } + 0: 16bits endian_bytes=2 {} {0:16/-2} {} {} + 1: 16bits endian_bytes=2 {} {} {0:16/-2} {} + 2: 16bits endian_bytes=2 {0:16/-2} {} {} {} + 3: 16bits endian_bytes=2 {} {} {} {0:16/-2} + AVD: name=gbrap14be chroma=0:0 flags=0xb1 [be][planar][rgb][alpha] + 0: p=2 st=2 o=0 sh=0 d=14 + 1: p=0 st=2 o=0 sh=0 d=14 + 2: p=1 st=2 o=0 sh=0 d=14 + 3: p=3 st=2 o=0 sh=0 d=14 +gbrap16: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [ba][bb][a][rgb][le][uint] + planes=4, chroma=0:0 align=1:1 + {16/[0:0] 16/[0:0] 16/[0:0] 16/[0:0] } + 0: 16bits {} {0:16} {} {} + 1: 16bits {} {} {0:16} {} + 2: 16bits {0:16} {} {} {} + 3: 16bits {} {} {} {0:16} + Regular: planes=4 compbytes=2 bitpad=0 chroma=1x1 ctype=uint + 0: {2} + 1: {3} + 2: {1} + 3: {4} + AVD: name=gbrap16le chroma=0:0 flags=0xb0 [planar][rgb][alpha] + 0: p=2 st=2 o=0 sh=0 d=16 + 1: p=0 st=2 o=0 sh=0 d=16 + 2: p=1 st=2 o=0 sh=0 d=16 + 3: p=3 st=2 o=0 sh=0 d=16 +gbrap16be: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [ba][bb][a][rgb][be][uint] + planes=4, chroma=0:0 align=1:1 + {16/[0:0] 16/[0:0] 16/[0:0] 16/[0:0] } + 0: 16bits endian_bytes=2 {} {0:16} {} {} + 1: 16bits endian_bytes=2 {} {} {0:16} {} + 2: 16bits endian_bytes=2 {0:16} {} {} {} + 3: 16bits endian_bytes=2 {} {} {} {0:16} + AVD: name=gbrap16be chroma=0:0 flags=0xb1 [be][planar][rgb][alpha] + 0: p=2 st=2 o=0 sh=0 d=16 + 1: p=0 st=2 o=0 sh=0 d=16 + 2: p=1 st=2 o=0 sh=0 d=16 + 3: p=3 st=2 o=0 sh=0 d=16 +gbrapf32: [GENERIC] fcsp=rgb ctype=float + Basic desc: [ba][bb][a][rgb][le][float] + planes=4, chroma=0:0 align=1:1 + {32/[0:0] 32/[0:0] 32/[0:0] 32/[0:0] } + 0: 32bits {} {0:32} {} {} + 1: 32bits {} {} {0:32} {} + 2: 32bits {0:32} {} {} {} + 3: 32bits {} {} {} {0:32} + Regular: planes=4 compbytes=4 bitpad=0 chroma=1x1 ctype=float + 0: {2} + 1: {3} + 2: {1} + 3: {4} + AVD: name=gbrapf32le chroma=0:0 flags=0x2b0 [planar][rgb][alpha][float] + 0: p=2 st=4 o=0 sh=0 d=32 + 1: p=0 st=4 o=0 sh=0 d=32 + 2: p=1 st=4 o=0 sh=0 d=32 + 3: p=3 st=4 o=0 sh=0 d=32 +gbrapf32be: [GENERIC] fcsp=rgb ctype=float + Basic desc: [ba][bb][a][rgb][be][float] + planes=4, chroma=0:0 align=1:1 + {32/[0:0] 32/[0:0] 32/[0:0] 32/[0:0] } + 0: 32bits endian_bytes=4 {} {0:32} {} {} + 1: 32bits endian_bytes=4 {} {} {0:32} {} + 2: 32bits endian_bytes=4 {0:32} {} {} {} + 3: 32bits endian_bytes=4 {} {} {} {0:32} + AVD: name=gbrapf32be chroma=0:0 flags=0x2b1 [be][planar][rgb][alpha][float] + 0: p=2 st=4 o=0 sh=0 d=32 + 1: p=0 st=4 o=0 sh=0 d=32 + 2: p=1 st=4 o=0 sh=0 d=32 + 3: p=3 st=4 o=0 sh=0 d=32 +gbrp: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [ba][bb][rgb][le][be][uint] + planes=3, chroma=0:0 align=1:1 + {8/[0:0] 8/[0:0] 8/[0:0] } + 0: 8bits {} {0:8} {} {} + 1: 8bits {} {} {0:8} {} + 2: 8bits {0:8} {} {} {} + Regular: planes=3 compbytes=1 bitpad=0 chroma=1x1 ctype=uint + 0: {2} + 1: {3} + 2: {1} + AVD: name=gbrp chroma=0:0 flags=0x30 [planar][rgb] + 0: p=2 st=1 o=0 sh=0 d=8 + 1: p=0 st=1 o=0 sh=0 d=8 + 2: p=1 st=1 o=0 sh=0 d=8 +gbrp1: fcsp=rgb ctype=uint + Basic desc: [ba][bb][rgb][le][uint] + planes=3, chroma=0:0 align=1:1 + {8/[0:0] 8/[0:0] 8/[0:0] } + 0: 8bits {} {0:8/-7} {} {} + 1: 8bits {} {} {0:8/-7} {} + 2: 8bits {0:8/-7} {} {} {} + Regular: planes=3 compbytes=1 bitpad=-7 chroma=1x1 ctype=uint + 0: {2} + 1: {3} + 2: {1} +gbrp10: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [ba][bb][rgb][le][uint] + planes=3, chroma=0:0 align=1:1 + {16/[0:0] 16/[0:0] 16/[0:0] } + 0: 16bits {} {0:16/-6} {} {} + 1: 16bits {} {} {0:16/-6} {} + 2: 16bits {0:16/-6} {} {} {} + Regular: planes=3 compbytes=2 bitpad=-6 chroma=1x1 ctype=uint + 0: {2} + 1: {3} + 2: {1} + AVD: name=gbrp10le chroma=0:0 flags=0x30 [planar][rgb] + 0: p=2 st=2 o=0 sh=0 d=10 + 1: p=0 st=2 o=0 sh=0 d=10 + 2: p=1 st=2 o=0 sh=0 d=10 +gbrp10be: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [ba][bb][rgb][be][uint] + planes=3, chroma=0:0 align=1:1 + {16/[0:0] 16/[0:0] 16/[0:0] } + 0: 16bits endian_bytes=2 {} {0:16/-6} {} {} + 1: 16bits endian_bytes=2 {} {} {0:16/-6} {} + 2: 16bits endian_bytes=2 {0:16/-6} {} {} {} + AVD: name=gbrp10be chroma=0:0 flags=0x31 [be][planar][rgb] + 0: p=2 st=2 o=0 sh=0 d=10 + 1: p=0 st=2 o=0 sh=0 d=10 + 2: p=1 st=2 o=0 sh=0 d=10 +gbrp12: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [ba][bb][rgb][le][uint] + planes=3, chroma=0:0 align=1:1 + {16/[0:0] 16/[0:0] 16/[0:0] } + 0: 16bits {} {0:16/-4} {} {} + 1: 16bits {} {} {0:16/-4} {} + 2: 16bits {0:16/-4} {} {} {} + Regular: planes=3 compbytes=2 bitpad=-4 chroma=1x1 ctype=uint + 0: {2} + 1: {3} + 2: {1} + AVD: name=gbrp12le chroma=0:0 flags=0x30 [planar][rgb] + 0: p=2 st=2 o=0 sh=0 d=12 + 1: p=0 st=2 o=0 sh=0 d=12 + 2: p=1 st=2 o=0 sh=0 d=12 +gbrp12be: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [ba][bb][rgb][be][uint] + planes=3, chroma=0:0 align=1:1 + {16/[0:0] 16/[0:0] 16/[0:0] } + 0: 16bits endian_bytes=2 {} {0:16/-4} {} {} + 1: 16bits endian_bytes=2 {} {} {0:16/-4} {} + 2: 16bits endian_bytes=2 {0:16/-4} {} {} {} + AVD: name=gbrp12be chroma=0:0 flags=0x31 [be][planar][rgb] + 0: p=2 st=2 o=0 sh=0 d=12 + 1: p=0 st=2 o=0 sh=0 d=12 + 2: p=1 st=2 o=0 sh=0 d=12 +gbrp14: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [ba][bb][rgb][le][uint] + planes=3, chroma=0:0 align=1:1 + {16/[0:0] 16/[0:0] 16/[0:0] } + 0: 16bits {} {0:16/-2} {} {} + 1: 16bits {} {} {0:16/-2} {} + 2: 16bits {0:16/-2} {} {} {} + Regular: planes=3 compbytes=2 bitpad=-2 chroma=1x1 ctype=uint + 0: {2} + 1: {3} + 2: {1} + AVD: name=gbrp14le chroma=0:0 flags=0x30 [planar][rgb] + 0: p=2 st=2 o=0 sh=0 d=14 + 1: p=0 st=2 o=0 sh=0 d=14 + 2: p=1 st=2 o=0 sh=0 d=14 +gbrp14be: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [ba][bb][rgb][be][uint] + planes=3, chroma=0:0 align=1:1 + {16/[0:0] 16/[0:0] 16/[0:0] } + 0: 16bits endian_bytes=2 {} {0:16/-2} {} {} + 1: 16bits endian_bytes=2 {} {} {0:16/-2} {} + 2: 16bits endian_bytes=2 {0:16/-2} {} {} {} + AVD: name=gbrp14be chroma=0:0 flags=0x31 [be][planar][rgb] + 0: p=2 st=2 o=0 sh=0 d=14 + 1: p=0 st=2 o=0 sh=0 d=14 + 2: p=1 st=2 o=0 sh=0 d=14 +gbrp16: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [ba][bb][rgb][le][uint] + planes=3, chroma=0:0 align=1:1 + {16/[0:0] 16/[0:0] 16/[0:0] } + 0: 16bits {} {0:16} {} {} + 1: 16bits {} {} {0:16} {} + 2: 16bits {0:16} {} {} {} + Regular: planes=3 compbytes=2 bitpad=0 chroma=1x1 ctype=uint + 0: {2} + 1: {3} + 2: {1} + AVD: name=gbrp16le chroma=0:0 flags=0x30 [planar][rgb] + 0: p=2 st=2 o=0 sh=0 d=16 + 1: p=0 st=2 o=0 sh=0 d=16 + 2: p=1 st=2 o=0 sh=0 d=16 +gbrp16be: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [ba][bb][rgb][be][uint] + planes=3, chroma=0:0 align=1:1 + {16/[0:0] 16/[0:0] 16/[0:0] } + 0: 16bits endian_bytes=2 {} {0:16} {} {} + 1: 16bits endian_bytes=2 {} {} {0:16} {} + 2: 16bits endian_bytes=2 {0:16} {} {} {} + AVD: name=gbrp16be chroma=0:0 flags=0x31 [be][planar][rgb] + 0: p=2 st=2 o=0 sh=0 d=16 + 1: p=0 st=2 o=0 sh=0 d=16 + 2: p=1 st=2 o=0 sh=0 d=16 +gbrp2: fcsp=rgb ctype=uint + Basic desc: [ba][bb][rgb][le][uint] + planes=3, chroma=0:0 align=1:1 + {8/[0:0] 8/[0:0] 8/[0:0] } + 0: 8bits {} {0:8/-6} {} {} + 1: 8bits {} {} {0:8/-6} {} + 2: 8bits {0:8/-6} {} {} {} + Regular: planes=3 compbytes=1 bitpad=-6 chroma=1x1 ctype=uint + 0: {2} + 1: {3} + 2: {1} +gbrp3: fcsp=rgb ctype=uint + Basic desc: [ba][bb][rgb][le][uint] + planes=3, chroma=0:0 align=1:1 + {8/[0:0] 8/[0:0] 8/[0:0] } + 0: 8bits {} {0:8/-5} {} {} + 1: 8bits {} {} {0:8/-5} {} + 2: 8bits {0:8/-5} {} {} {} + Regular: planes=3 compbytes=1 bitpad=-5 chroma=1x1 ctype=uint + 0: {2} + 1: {3} + 2: {1} +gbrp4: fcsp=rgb ctype=uint + Basic desc: [ba][bb][rgb][le][uint] + planes=3, chroma=0:0 align=1:1 + {8/[0:0] 8/[0:0] 8/[0:0] } + 0: 8bits {} {0:8/-4} {} {} + 1: 8bits {} {} {0:8/-4} {} + 2: 8bits {0:8/-4} {} {} {} + Regular: planes=3 compbytes=1 bitpad=-4 chroma=1x1 ctype=uint + 0: {2} + 1: {3} + 2: {1} +gbrp5: fcsp=rgb ctype=uint + Basic desc: [ba][bb][rgb][le][uint] + planes=3, chroma=0:0 align=1:1 + {8/[0:0] 8/[0:0] 8/[0:0] } + 0: 8bits {} {0:8/-3} {} {} + 1: 8bits {} {} {0:8/-3} {} + 2: 8bits {0:8/-3} {} {} {} + Regular: planes=3 compbytes=1 bitpad=-3 chroma=1x1 ctype=uint + 0: {2} + 1: {3} + 2: {1} +gbrp6: fcsp=rgb ctype=uint + Basic desc: [ba][bb][rgb][le][uint] + planes=3, chroma=0:0 align=1:1 + {8/[0:0] 8/[0:0] 8/[0:0] } + 0: 8bits {} {0:8/-2} {} {} + 1: 8bits {} {} {0:8/-2} {} + 2: 8bits {0:8/-2} {} {} {} + Regular: planes=3 compbytes=1 bitpad=-2 chroma=1x1 ctype=uint + 0: {2} + 1: {3} + 2: {1} +gbrp9: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [ba][bb][rgb][le][uint] + planes=3, chroma=0:0 align=1:1 + {16/[0:0] 16/[0:0] 16/[0:0] } + 0: 16bits {} {0:16/-7} {} {} + 1: 16bits {} {} {0:16/-7} {} + 2: 16bits {0:16/-7} {} {} {} + Regular: planes=3 compbytes=2 bitpad=-7 chroma=1x1 ctype=uint + 0: {2} + 1: {3} + 2: {1} + AVD: name=gbrp9le chroma=0:0 flags=0x30 [planar][rgb] + 0: p=2 st=2 o=0 sh=0 d=9 + 1: p=0 st=2 o=0 sh=0 d=9 + 2: p=1 st=2 o=0 sh=0 d=9 +gbrp9be: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [ba][bb][rgb][be][uint] + planes=3, chroma=0:0 align=1:1 + {16/[0:0] 16/[0:0] 16/[0:0] } + 0: 16bits endian_bytes=2 {} {0:16/-7} {} {} + 1: 16bits endian_bytes=2 {} {} {0:16/-7} {} + 2: 16bits endian_bytes=2 {0:16/-7} {} {} {} + AVD: name=gbrp9be chroma=0:0 flags=0x31 [be][planar][rgb] + 0: p=2 st=2 o=0 sh=0 d=9 + 1: p=0 st=2 o=0 sh=0 d=9 + 2: p=1 st=2 o=0 sh=0 d=9 +gbrpf32: [GENERIC] fcsp=rgb ctype=float + Basic desc: [ba][bb][rgb][le][float] + planes=3, chroma=0:0 align=1:1 + {32/[0:0] 32/[0:0] 32/[0:0] } + 0: 32bits {} {0:32} {} {} + 1: 32bits {} {} {0:32} {} + 2: 32bits {0:32} {} {} {} + Regular: planes=3 compbytes=4 bitpad=0 chroma=1x1 ctype=float + 0: {2} + 1: {3} + 2: {1} + AVD: name=gbrpf32le chroma=0:0 flags=0x230 [planar][rgb][float] + 0: p=2 st=4 o=0 sh=0 d=32 + 1: p=0 st=4 o=0 sh=0 d=32 + 2: p=1 st=4 o=0 sh=0 d=32 +gbrpf32be: [GENERIC] fcsp=rgb ctype=float + Basic desc: [ba][bb][rgb][be][float] + planes=3, chroma=0:0 align=1:1 + {32/[0:0] 32/[0:0] 32/[0:0] } + 0: 32bits endian_bytes=4 {} {0:32} {} {} + 1: 32bits endian_bytes=4 {} {} {0:32} {} + 2: 32bits endian_bytes=4 {0:32} {} {} {} + AVD: name=gbrpf32be chroma=0:0 flags=0x231 [be][planar][rgb][float] + 0: p=2 st=4 o=0 sh=0 d=32 + 1: p=0 st=4 o=0 sh=0 d=32 + 2: p=1 st=4 o=0 sh=0 d=32 +gray: ctype=uint + Basic desc: [ba][bb][yuvp][yuv][gray][le][be][uint] + planes=1, chroma=0:0 align=1:1 + {8/[0:0] } + 0: 8bits {0:8} {} {} {} + Regular: planes=1 compbytes=1 bitpad=0 chroma=1x1 ctype=uint + 0: {1} + AVD: name=gray chroma=0:0 flags=0x0 + 0: p=0 st=1 o=0 sh=0 d=8 +gray10: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][gray][le][uint] + planes=1, chroma=0:0 align=1:1 + {16/[0:0] } + 0: 16bits {0:16/-6} {} {} {} + Regular: planes=1 compbytes=2 bitpad=-6 chroma=1x1 ctype=uint + 0: {1} + AVD: name=gray10le chroma=0:0 flags=0x0 + 0: p=0 st=2 o=0 sh=0 d=10 +gray10be: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][gray][be][uint] + planes=1, chroma=0:0 align=1:1 + {16/[0:0] } + 0: 16bits endian_bytes=2 {0:16/-6} {} {} {} + AVD: name=gray10be chroma=0:0 flags=0x1 [be] + 0: p=0 st=2 o=0 sh=0 d=10 +gray12: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][gray][le][uint] + planes=1, chroma=0:0 align=1:1 + {16/[0:0] } + 0: 16bits {0:16/-4} {} {} {} + Regular: planes=1 compbytes=2 bitpad=-4 chroma=1x1 ctype=uint + 0: {1} + AVD: name=gray12le chroma=0:0 flags=0x0 + 0: p=0 st=2 o=0 sh=0 d=12 +gray12be: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][gray][be][uint] + planes=1, chroma=0:0 align=1:1 + {16/[0:0] } + 0: 16bits endian_bytes=2 {0:16/-4} {} {} {} + AVD: name=gray12be chroma=0:0 flags=0x1 [be] + 0: p=0 st=2 o=0 sh=0 d=12 +gray14: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][gray][le][uint] + planes=1, chroma=0:0 align=1:1 + {16/[0:0] } + 0: 16bits {0:16/-2} {} {} {} + Regular: planes=1 compbytes=2 bitpad=-2 chroma=1x1 ctype=uint + 0: {1} + AVD: name=gray14le chroma=0:0 flags=0x0 + 0: p=0 st=2 o=0 sh=0 d=14 +gray14be: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][gray][be][uint] + planes=1, chroma=0:0 align=1:1 + {16/[0:0] } + 0: 16bits endian_bytes=2 {0:16/-2} {} {} {} + AVD: name=gray14be chroma=0:0 flags=0x1 [be] + 0: p=0 st=2 o=0 sh=0 d=14 +gray16: ctype=uint + Basic desc: [ba][bb][yuvp][yuv][gray][le][uint] + planes=1, chroma=0:0 align=1:1 + {16/[0:0] } + 0: 16bits {0:16} {} {} {} + Regular: planes=1 compbytes=2 bitpad=0 chroma=1x1 ctype=uint + 0: {1} + AVD: name=gray16le chroma=0:0 flags=0x0 + 0: p=0 st=2 o=0 sh=0 d=16 +gray16be: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][gray][be][uint] + planes=1, chroma=0:0 align=1:1 + {16/[0:0] } + 0: 16bits endian_bytes=2 {0:16} {} {} {} + AVD: name=gray16be chroma=0:0 flags=0x1 [be] + 0: p=0 st=2 o=0 sh=0 d=16 +gray9: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][gray][le][uint] + planes=1, chroma=0:0 align=1:1 + {16/[0:0] } + 0: 16bits {0:16/-7} {} {} {} + Regular: planes=1 compbytes=2 bitpad=-7 chroma=1x1 ctype=uint + 0: {1} + AVD: name=gray9le chroma=0:0 flags=0x0 + 0: p=0 st=2 o=0 sh=0 d=9 +gray9be: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][gray][be][uint] + planes=1, chroma=0:0 align=1:1 + {16/[0:0] } + 0: 16bits endian_bytes=2 {0:16/-7} {} {} {} + AVD: name=gray9be chroma=0:0 flags=0x1 [be] + 0: p=0 st=2 o=0 sh=0 d=9 +grayaf32: ctype=float + Basic desc: [ba][bb][a][yuv][gray][le][float] + planes=2, chroma=0:0 align=1:1 + {32/[0:0] 32/[0:0] } + 0: 32bits {0:32} {} {} {} + 1: 32bits {} {} {} {0:32} + Regular: planes=2 compbytes=4 bitpad=0 chroma=1x1 ctype=float + 0: {1} + 1: {4} +grayf32: [GENERIC] ctype=float + Basic desc: [ba][bb][yuv][gray][le][float] + planes=1, chroma=0:0 align=1:1 + {32/[0:0] } + 0: 32bits {0:32} {} {} {} + Regular: planes=1 compbytes=4 bitpad=0 chroma=1x1 ctype=float + 0: {1} + AVD: name=grayf32le chroma=0:0 flags=0x200 [float] + 0: p=0 st=4 o=0 sh=0 d=32 +grayf32be: [GENERIC] ctype=float + Basic desc: [ba][bb][yuv][gray][be][float] + planes=1, chroma=0:0 align=1:1 + {32/[0:0] } + 0: 32bits endian_bytes=4 {0:32} {} {} {} + AVD: name=grayf32be chroma=0:0 flags=0x201 [be][float] + 0: p=0 st=4 o=0 sh=0 d=32 +mediacodec: ctype=unknown + Basic desc: [le][be][hw] + planes=0, chroma=0:0 align=1:1 + {} + AVD: name=mediacodec chroma=0:0 flags=0x8 [hw] +mmal: ctype=unknown + Basic desc: [le][be][hw] + planes=0, chroma=0:0 align=1:1 + {} + AVD: name=mmal chroma=0:0 flags=0x8 [hw] +monob: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [rgb][gray][le][be][uint] + planes=1, chroma=0:0 align=8:1 + {1/[0:0] } + 0: 1bits {0:1} {} {} {} + AVD: name=monob chroma=0:0 flags=0x4 [bs] + 0: p=0 st=1 o=0 sh=7 d=1 +monow: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [rgb][gray][le][be][uint] + planes=1, chroma=0:0 align=8:1 + {1/[0:0] } + 0: 1bits {0:1} {} {} {} + AVD: name=monow chroma=0:0 flags=0x4 [bs] + 0: p=0 st=1 o=0 sh=0 d=1 +nv12: ctype=uint + Basic desc: [ba][bb][nv][yuv][le][be][uint] + planes=2, chroma=1:1 align=2:2 + {8/[0:0] 16/[1:1] } + 0: 8bits {0:8} {} {} {} + 1: 16bits {} {0:8} {8:8} {} + Regular: planes=2 compbytes=1 bitpad=0 chroma=2x2 ctype=uint + 0: {1} + 1: {2, 3} + AVD: name=nv12 chroma=1:1 flags=0x10 [planar] + 0: p=0 st=1 o=0 sh=0 d=8 + 1: p=1 st=2 o=0 sh=0 d=8 + 2: p=1 st=2 o=1 sh=0 d=8 +nv16: [GENERIC] ctype=uint + Basic desc: [ba][bb][nv][yuv][le][be][uint] + planes=2, chroma=1:0 align=2:1 + {8/[0:0] 16/[1:0] } + 0: 8bits {0:8} {} {} {} + 1: 16bits {} {0:8} {8:8} {} + Regular: planes=2 compbytes=1 bitpad=0 chroma=2x1 ctype=uint + 0: {1} + 1: {2, 3} + AVD: name=nv16 chroma=1:0 flags=0x10 [planar] + 0: p=0 st=1 o=0 sh=0 d=8 + 1: p=1 st=2 o=0 sh=0 d=8 + 2: p=1 st=2 o=1 sh=0 d=8 +nv20: [GENERIC] ctype=uint + Basic desc: [ba][bb][nv][yuv][le][uint] + planes=2, chroma=1:0 align=2:1 + {16/[0:0] 32/[1:0] } + 0: 16bits {0:16/-6} {} {} {} + 1: 32bits {} {0:16/-6} {16:16/-6} {} + Regular: planes=2 compbytes=2 bitpad=-6 chroma=2x1 ctype=uint + 0: {1} + 1: {2, 3} + AVD: name=nv20le chroma=1:0 flags=0x10 [planar] + 0: p=0 st=2 o=0 sh=0 d=10 + 1: p=1 st=4 o=0 sh=0 d=10 + 2: p=1 st=4 o=2 sh=0 d=10 +nv20be: [GENERIC] ctype=uint + Basic desc: [ba][bb][nv][yuv][be][uint] + planes=2, chroma=1:0 align=2:1 + {16/[0:0] 32/[1:0] } + 0: 16bits endian_bytes=2 {0:16/-6} {} {} {} + 1: 32bits endian_bytes=2 {} {0:16/-6} {16:16/-6} {} + AVD: name=nv20be chroma=1:0 flags=0x11 [be][planar] + 0: p=0 st=2 o=0 sh=0 d=10 + 1: p=1 st=4 o=0 sh=0 d=10 + 2: p=1 st=4 o=2 sh=0 d=10 +nv21: [GENERIC] ctype=uint + Basic desc: [ba][bb][nv][yuv][le][be][uint] + planes=2, chroma=1:1 align=2:2 + {8/[0:0] 16/[1:1] } + 0: 8bits {0:8} {} {} {} + 1: 16bits {} {8:8} {0:8} {} + Regular: planes=2 compbytes=1 bitpad=0 chroma=2x2 ctype=uint + 0: {1} + 1: {3, 2} + AVD: name=nv21 chroma=1:1 flags=0x10 [planar] + 0: p=0 st=1 o=0 sh=0 d=8 + 1: p=1 st=2 o=1 sh=0 d=8 + 2: p=1 st=2 o=0 sh=0 d=8 +nv24: [GENERIC] ctype=uint + Basic desc: [ba][bb][nv][yuv][le][be][uint] + planes=2, chroma=0:0 align=1:1 + {8/[0:0] 16/[0:0] } + 0: 8bits {0:8} {} {} {} + 1: 16bits {} {0:8} {8:8} {} + Regular: planes=2 compbytes=1 bitpad=0 chroma=1x1 ctype=uint + 0: {1} + 1: {2, 3} + AVD: name=nv24 chroma=0:0 flags=0x10 [planar] + 0: p=0 st=1 o=0 sh=0 d=8 + 1: p=1 st=2 o=0 sh=0 d=8 + 2: p=1 st=2 o=1 sh=0 d=8 +nv42: [GENERIC] ctype=uint + Basic desc: [ba][bb][nv][yuv][le][be][uint] + planes=2, chroma=0:0 align=1:1 + {8/[0:0] 16/[0:0] } + 0: 8bits {0:8} {} {} {} + 1: 16bits {} {8:8} {0:8} {} + Regular: planes=2 compbytes=1 bitpad=0 chroma=1x1 ctype=uint + 0: {1} + 1: {3, 2} + AVD: name=nv42 chroma=0:0 flags=0x10 [planar] + 0: p=0 st=1 o=0 sh=0 d=8 + 1: p=1 st=2 o=1 sh=0 d=8 + 2: p=1 st=2 o=0 sh=0 d=8 +opencl: [GENERIC] ctype=unknown + Basic desc: [le][be][hw] + planes=0, chroma=0:0 align=1:1 + {} + AVD: name=opencl chroma=0:0 flags=0x8 [hw] +p010: ctype=uint + Basic desc: [ba][bb][nv][yuv][le][uint] + planes=2, chroma=1:1 align=2:2 + {16/[0:0] 32/[1:1] } + 0: 16bits {0:16/6} {} {} {} + 1: 32bits {} {0:16/6} {16:16/6} {} + Regular: planes=2 compbytes=2 bitpad=6 chroma=2x2 ctype=uint + 0: {1} + 1: {2, 3} + AVD: name=p010le chroma=1:1 flags=0x10 [planar] + 0: p=0 st=2 o=0 sh=6 d=10 + 1: p=1 st=4 o=0 sh=6 d=10 + 2: p=1 st=4 o=2 sh=6 d=10 +p010be: [GENERIC] ctype=uint + Basic desc: [ba][bb][nv][yuv][be][uint] + planes=2, chroma=1:1 align=2:2 + {16/[0:0] 32/[1:1] } + 0: 16bits endian_bytes=2 {0:16/6} {} {} {} + 1: 32bits endian_bytes=2 {} {0:16/6} {16:16/6} {} + AVD: name=p010be chroma=1:1 flags=0x11 [be][planar] + 0: p=0 st=2 o=0 sh=6 d=10 + 1: p=1 st=4 o=0 sh=6 d=10 + 2: p=1 st=4 o=2 sh=6 d=10 +p012: [GENERIC] ctype=uint + Basic desc: [ba][bb][nv][yuv][le][uint] + planes=2, chroma=1:1 align=2:2 + {16/[0:0] 32/[1:1] } + 0: 16bits {0:16/4} {} {} {} + 1: 32bits {} {0:16/4} {16:16/4} {} + Regular: planes=2 compbytes=2 bitpad=4 chroma=2x2 ctype=uint + 0: {1} + 1: {2, 3} + AVD: name=p012le chroma=1:1 flags=0x10 [planar] + 0: p=0 st=2 o=0 sh=4 d=12 + 1: p=1 st=4 o=0 sh=4 d=12 + 2: p=1 st=4 o=2 sh=4 d=12 +p012be: [GENERIC] ctype=uint + Basic desc: [ba][bb][nv][yuv][be][uint] + planes=2, chroma=1:1 align=2:2 + {16/[0:0] 32/[1:1] } + 0: 16bits endian_bytes=2 {0:16/4} {} {} {} + 1: 32bits endian_bytes=2 {} {0:16/4} {16:16/4} {} + AVD: name=p012be chroma=1:1 flags=0x11 [be][planar] + 0: p=0 st=2 o=0 sh=4 d=12 + 1: p=1 st=4 o=0 sh=4 d=12 + 2: p=1 st=4 o=2 sh=4 d=12 +p016: [GENERIC] ctype=uint + Basic desc: [ba][bb][nv][yuv][le][uint] + planes=2, chroma=1:1 align=2:2 + {16/[0:0] 32/[1:1] } + 0: 16bits {0:16} {} {} {} + 1: 32bits {} {0:16} {16:16} {} + Regular: planes=2 compbytes=2 bitpad=0 chroma=2x2 ctype=uint + 0: {1} + 1: {2, 3} + AVD: name=p016le chroma=1:1 flags=0x10 [planar] + 0: p=0 st=2 o=0 sh=0 d=16 + 1: p=1 st=4 o=0 sh=0 d=16 + 2: p=1 st=4 o=2 sh=0 d=16 +p016be: [GENERIC] ctype=uint + Basic desc: [ba][bb][nv][yuv][be][uint] + planes=2, chroma=1:1 align=2:2 + {16/[0:0] 32/[1:1] } + 0: 16bits endian_bytes=2 {0:16} {} {} {} + 1: 32bits endian_bytes=2 {} {0:16} {16:16} {} + AVD: name=p016be chroma=1:1 flags=0x11 [be][planar] + 0: p=0 st=2 o=0 sh=0 d=16 + 1: p=1 st=4 o=0 sh=0 d=16 + 2: p=1 st=4 o=2 sh=0 d=16 +p210: [GENERIC] ctype=uint + Basic desc: [ba][bb][nv][yuv][le][uint] + planes=2, chroma=1:0 align=2:1 + {16/[0:0] 32/[1:0] } + 0: 16bits {0:16/6} {} {} {} + 1: 32bits {} {0:16/6} {16:16/6} {} + Regular: planes=2 compbytes=2 bitpad=6 chroma=2x1 ctype=uint + 0: {1} + 1: {2, 3} + AVD: name=p210le chroma=1:0 flags=0x10 [planar] + 0: p=0 st=2 o=0 sh=6 d=10 + 1: p=1 st=4 o=0 sh=6 d=10 + 2: p=1 st=4 o=2 sh=6 d=10 +p210be: [GENERIC] ctype=uint + Basic desc: [ba][bb][nv][yuv][be][uint] + planes=2, chroma=1:0 align=2:1 + {16/[0:0] 32/[1:0] } + 0: 16bits endian_bytes=2 {0:16/6} {} {} {} + 1: 32bits endian_bytes=2 {} {0:16/6} {16:16/6} {} + AVD: name=p210be chroma=1:0 flags=0x11 [be][planar] + 0: p=0 st=2 o=0 sh=6 d=10 + 1: p=1 st=4 o=0 sh=6 d=10 + 2: p=1 st=4 o=2 sh=6 d=10 +p212: [GENERIC] ctype=uint + Basic desc: [ba][bb][nv][yuv][le][uint] + planes=2, chroma=1:0 align=2:1 + {16/[0:0] 32/[1:0] } + 0: 16bits {0:16/4} {} {} {} + 1: 32bits {} {0:16/4} {16:16/4} {} + Regular: planes=2 compbytes=2 bitpad=4 chroma=2x1 ctype=uint + 0: {1} + 1: {2, 3} + AVD: name=p212le chroma=1:0 flags=0x10 [planar] + 0: p=0 st=2 o=0 sh=4 d=12 + 1: p=1 st=4 o=0 sh=4 d=12 + 2: p=1 st=4 o=2 sh=4 d=12 +p212be: [GENERIC] ctype=uint + Basic desc: [ba][bb][nv][yuv][be][uint] + planes=2, chroma=1:0 align=2:1 + {16/[0:0] 32/[1:0] } + 0: 16bits endian_bytes=2 {0:16/4} {} {} {} + 1: 32bits endian_bytes=2 {} {0:16/4} {16:16/4} {} + AVD: name=p212be chroma=1:0 flags=0x11 [be][planar] + 0: p=0 st=2 o=0 sh=4 d=12 + 1: p=1 st=4 o=0 sh=4 d=12 + 2: p=1 st=4 o=2 sh=4 d=12 +p216: [GENERIC] ctype=uint + Basic desc: [ba][bb][nv][yuv][le][uint] + planes=2, chroma=1:0 align=2:1 + {16/[0:0] 32/[1:0] } + 0: 16bits {0:16} {} {} {} + 1: 32bits {} {0:16} {16:16} {} + Regular: planes=2 compbytes=2 bitpad=0 chroma=2x1 ctype=uint + 0: {1} + 1: {2, 3} + AVD: name=p216le chroma=1:0 flags=0x10 [planar] + 0: p=0 st=2 o=0 sh=0 d=16 + 1: p=1 st=4 o=0 sh=0 d=16 + 2: p=1 st=4 o=2 sh=0 d=16 +p216be: [GENERIC] ctype=uint + Basic desc: [ba][bb][nv][yuv][be][uint] + planes=2, chroma=1:0 align=2:1 + {16/[0:0] 32/[1:0] } + 0: 16bits endian_bytes=2 {0:16} {} {} {} + 1: 32bits endian_bytes=2 {} {0:16} {16:16} {} + AVD: name=p216be chroma=1:0 flags=0x11 [be][planar] + 0: p=0 st=2 o=0 sh=0 d=16 + 1: p=1 st=4 o=0 sh=0 d=16 + 2: p=1 st=4 o=2 sh=0 d=16 +p410: [GENERIC] ctype=uint + Basic desc: [ba][bb][nv][yuv][le][uint] + planes=2, chroma=0:0 align=1:1 + {16/[0:0] 32/[0:0] } + 0: 16bits {0:16/6} {} {} {} + 1: 32bits {} {0:16/6} {16:16/6} {} + Regular: planes=2 compbytes=2 bitpad=6 chroma=1x1 ctype=uint + 0: {1} + 1: {2, 3} + AVD: name=p410le chroma=0:0 flags=0x10 [planar] + 0: p=0 st=2 o=0 sh=6 d=10 + 1: p=1 st=4 o=0 sh=6 d=10 + 2: p=1 st=4 o=2 sh=6 d=10 +p410be: [GENERIC] ctype=uint + Basic desc: [ba][bb][nv][yuv][be][uint] + planes=2, chroma=0:0 align=1:1 + {16/[0:0] 32/[0:0] } + 0: 16bits endian_bytes=2 {0:16/6} {} {} {} + 1: 32bits endian_bytes=2 {} {0:16/6} {16:16/6} {} + AVD: name=p410be chroma=0:0 flags=0x11 [be][planar] + 0: p=0 st=2 o=0 sh=6 d=10 + 1: p=1 st=4 o=0 sh=6 d=10 + 2: p=1 st=4 o=2 sh=6 d=10 +p412: [GENERIC] ctype=uint + Basic desc: [ba][bb][nv][yuv][le][uint] + planes=2, chroma=0:0 align=1:1 + {16/[0:0] 32/[0:0] } + 0: 16bits {0:16/4} {} {} {} + 1: 32bits {} {0:16/4} {16:16/4} {} + Regular: planes=2 compbytes=2 bitpad=4 chroma=1x1 ctype=uint + 0: {1} + 1: {2, 3} + AVD: name=p412le chroma=0:0 flags=0x10 [planar] + 0: p=0 st=2 o=0 sh=4 d=12 + 1: p=1 st=4 o=0 sh=4 d=12 + 2: p=1 st=4 o=2 sh=4 d=12 +p412be: [GENERIC] ctype=uint + Basic desc: [ba][bb][nv][yuv][be][uint] + planes=2, chroma=0:0 align=1:1 + {16/[0:0] 32/[0:0] } + 0: 16bits endian_bytes=2 {0:16/4} {} {} {} + 1: 32bits endian_bytes=2 {} {0:16/4} {16:16/4} {} + AVD: name=p412be chroma=0:0 flags=0x11 [be][planar] + 0: p=0 st=2 o=0 sh=4 d=12 + 1: p=1 st=4 o=0 sh=4 d=12 + 2: p=1 st=4 o=2 sh=4 d=12 +p416: [GENERIC] ctype=uint + Basic desc: [ba][bb][nv][yuv][le][uint] + planes=2, chroma=0:0 align=1:1 + {16/[0:0] 32/[0:0] } + 0: 16bits {0:16} {} {} {} + 1: 32bits {} {0:16} {16:16} {} + Regular: planes=2 compbytes=2 bitpad=0 chroma=1x1 ctype=uint + 0: {1} + 1: {2, 3} + AVD: name=p416le chroma=0:0 flags=0x10 [planar] + 0: p=0 st=2 o=0 sh=0 d=16 + 1: p=1 st=4 o=0 sh=0 d=16 + 2: p=1 st=4 o=2 sh=0 d=16 +p416be: [GENERIC] ctype=uint + Basic desc: [ba][bb][nv][yuv][be][uint] + planes=2, chroma=0:0 align=1:1 + {16/[0:0] 32/[0:0] } + 0: 16bits endian_bytes=2 {0:16} {} {} {} + 1: 32bits endian_bytes=2 {} {0:16} {16:16} {} + AVD: name=p416be chroma=0:0 flags=0x11 [be][planar] + 0: p=0 st=2 o=0 sh=0 d=16 + 1: p=1 st=4 o=0 sh=0 d=16 + 2: p=1 st=4 o=2 sh=0 d=16 +pal8: fcsp=rgb ctype=unknown + Basic desc: [ba][a][rgb][le][be][pal] + planes=1, chroma=0:0 align=1:1 + {8/[0:0] } + 0: 8bits {} {} {} {} + AVD: name=pal8 chroma=0:0 flags=0x82 [pal][alpha] + 0: p=0 st=1 o=0 sh=0 d=8 +qsv: [GENERIC] ctype=unknown + Basic desc: [le][be][hw] + planes=0, chroma=0:0 align=1:1 + {} + AVD: name=qsv chroma=0:0 flags=0x8 [hw] +rgb0: fcsp=rgb ctype=uint + Basic desc: [ba][bb][rgb][le][be][uint] + planes=1, chroma=0:0 align=1:1 + {32/[0:0] } + 0: 32bits {0:8} {8:8} {16:8} {} + Regular: planes=1 compbytes=1 bitpad=0 chroma=1x1 ctype=uint + 0: {1, 2, 3, 0} + AVD: name=rgb0 chroma=0:0 flags=0x20 [rgb] + 0: p=0 st=4 o=0 sh=0 d=8 + 1: p=0 st=4 o=1 sh=0 d=8 + 2: p=0 st=4 o=2 sh=0 d=8 +rgb24: fcsp=rgb ctype=uint + Basic desc: [ba][bb][rgb][le][be][uint] + planes=1, chroma=0:0 align=1:1 + {24/[0:0] } + 0: 24bits {0:8} {8:8} {16:8} {} + Regular: planes=1 compbytes=1 bitpad=0 chroma=1x1 ctype=uint + 0: {1, 2, 3} + AVD: name=rgb24 chroma=0:0 flags=0x20 [rgb] + 0: p=0 st=3 o=0 sh=0 d=8 + 1: p=0 st=3 o=1 sh=0 d=8 + 2: p=0 st=3 o=2 sh=0 d=8 +rgb30: fcsp=rgb ctype=uint + Basic desc: [ba][rgb][le][uint] + planes=1, chroma=0:0 align=1:1 + {32/[0:0] } + 0: 32bits {20:10} {10:10} {0:10} {} + AVD: name=x2rgb10le chroma=0:0 flags=0x20 [rgb] + 0: p=0 st=4 o=2 sh=4 d=10 + 1: p=0 st=4 o=1 sh=2 d=10 + 2: p=0 st=4 o=0 sh=0 d=10 +rgb4: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [rgb][le][be][uint] + planes=1, chroma=0:0 align=2:1 + {4/[0:0] } + 0: 4bits {0:1} {1:2} {3:1} {} + AVD: name=rgb4 chroma=0:0 flags=0x24 [bs][rgb] + 0: p=0 st=4 o=0 sh=0 d=1 + 1: p=0 st=4 o=1 sh=0 d=2 + 2: p=0 st=4 o=3 sh=0 d=1 +rgb444: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [ba][rgb][le][uint] + planes=1, chroma=0:0 align=1:1 + {16/[0:0] } + 0: 16bits {8:4} {4:4} {0:4} {} + AVD: name=rgb444le chroma=0:0 flags=0x20 [rgb] + 0: p=0 st=2 o=1 sh=0 d=4 + 1: p=0 st=2 o=0 sh=4 d=4 + 2: p=0 st=2 o=0 sh=0 d=4 +rgb444be: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [ba][rgb][be][uint] + planes=1, chroma=0:0 align=1:1 + {16/[0:0] } + 0: 16bits endian_bytes=2 {8:4} {4:4} {0:4} {} + AVD: name=rgb444be chroma=0:0 flags=0x21 [be][rgb] + 0: p=0 st=2 o=-1 sh=0 d=4 + 1: p=0 st=2 o=0 sh=4 d=4 + 2: p=0 st=2 o=0 sh=0 d=4 +rgb48: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [ba][bb][rgb][le][uint] + planes=1, chroma=0:0 align=1:1 + {48/[0:0] } + 0: 48bits {0:16} {16:16} {32:16} {} + Regular: planes=1 compbytes=2 bitpad=0 chroma=1x1 ctype=uint + 0: {1, 2, 3} + AVD: name=rgb48le chroma=0:0 flags=0x20 [rgb] + 0: p=0 st=6 o=0 sh=0 d=16 + 1: p=0 st=6 o=2 sh=0 d=16 + 2: p=0 st=6 o=4 sh=0 d=16 +rgb48be: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [ba][bb][rgb][be][uint] + planes=1, chroma=0:0 align=1:1 + {48/[0:0] } + 0: 48bits endian_bytes=2 {0:16} {16:16} {32:16} {} + AVD: name=rgb48be chroma=0:0 flags=0x21 [be][rgb] + 0: p=0 st=6 o=0 sh=0 d=16 + 1: p=0 st=6 o=2 sh=0 d=16 + 2: p=0 st=6 o=4 sh=0 d=16 +rgb4_byte: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [ba][rgb][le][be][uint] + planes=1, chroma=0:0 align=1:1 + {8/[0:0] } + 0: 8bits {3:1} {1:2} {0:1} {} + AVD: name=rgb4_byte chroma=0:0 flags=0x20 [rgb] + 0: p=0 st=1 o=0 sh=3 d=1 + 1: p=0 st=1 o=0 sh=1 d=2 + 2: p=0 st=1 o=0 sh=0 d=1 +rgb555: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [ba][rgb][le][uint] + planes=1, chroma=0:0 align=1:1 + {16/[0:0] } + 0: 16bits {10:5} {5:5} {0:5} {} + AVD: name=rgb555le chroma=0:0 flags=0x20 [rgb] + 0: p=0 st=2 o=1 sh=2 d=5 + 1: p=0 st=2 o=0 sh=5 d=5 + 2: p=0 st=2 o=0 sh=0 d=5 +rgb555be: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [ba][rgb][be][uint] + planes=1, chroma=0:0 align=1:1 + {16/[0:0] } + 0: 16bits endian_bytes=2 {10:5} {5:5} {0:5} {} + AVD: name=rgb555be chroma=0:0 flags=0x21 [be][rgb] + 0: p=0 st=2 o=-1 sh=2 d=5 + 1: p=0 st=2 o=0 sh=5 d=5 + 2: p=0 st=2 o=0 sh=0 d=5 +rgb565: fcsp=rgb ctype=uint + Basic desc: [ba][rgb][le][uint] + planes=1, chroma=0:0 align=1:1 + {16/[0:0] } + 0: 16bits {11:5} {5:6} {0:5} {} + AVD: name=rgb565le chroma=0:0 flags=0x20 [rgb] + 0: p=0 st=2 o=1 sh=3 d=5 + 1: p=0 st=2 o=0 sh=5 d=6 + 2: p=0 st=2 o=0 sh=0 d=5 +rgb565be: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [ba][rgb][be][uint] + planes=1, chroma=0:0 align=1:1 + {16/[0:0] } + 0: 16bits endian_bytes=2 {11:5} {5:6} {0:5} {} + AVD: name=rgb565be chroma=0:0 flags=0x21 [be][rgb] + 0: p=0 st=2 o=-1 sh=3 d=5 + 1: p=0 st=2 o=0 sh=5 d=6 + 2: p=0 st=2 o=0 sh=0 d=5 +rgb8: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [ba][rgb][le][be][uint] + planes=1, chroma=0:0 align=1:1 + {8/[0:0] } + 0: 8bits {5:3} {2:3} {0:2} {} + AVD: name=rgb8 chroma=0:0 flags=0x20 [rgb] + 0: p=0 st=1 o=0 sh=5 d=3 + 1: p=0 st=1 o=0 sh=2 d=3 + 2: p=0 st=1 o=0 sh=0 d=2 +rgba: fcsp=rgb ctype=uint + Basic desc: [ba][bb][a][rgb][le][be][uint] + planes=1, chroma=0:0 align=1:1 + {32/[0:0] } + 0: 32bits {0:8} {8:8} {16:8} {24:8} + Regular: planes=1 compbytes=1 bitpad=0 chroma=1x1 ctype=uint + 0: {1, 2, 3, 4} + AVD: name=rgba chroma=0:0 flags=0xa0 [rgb][alpha] + 0: p=0 st=4 o=0 sh=0 d=8 + 1: p=0 st=4 o=1 sh=0 d=8 + 2: p=0 st=4 o=2 sh=0 d=8 + 3: p=0 st=4 o=3 sh=0 d=8 +rgba64: fcsp=rgb ctype=uint + Basic desc: [ba][bb][a][rgb][le][uint] + planes=1, chroma=0:0 align=1:1 + {64/[0:0] } + 0: 64bits {0:16} {16:16} {32:16} {48:16} + Regular: planes=1 compbytes=2 bitpad=0 chroma=1x1 ctype=uint + 0: {1, 2, 3, 4} + AVD: name=rgba64le chroma=0:0 flags=0xa0 [rgb][alpha] + 0: p=0 st=8 o=0 sh=0 d=16 + 1: p=0 st=8 o=2 sh=0 d=16 + 2: p=0 st=8 o=4 sh=0 d=16 + 3: p=0 st=8 o=6 sh=0 d=16 +rgba64be: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [ba][bb][a][rgb][be][uint] + planes=1, chroma=0:0 align=1:1 + {64/[0:0] } + 0: 64bits endian_bytes=2 {0:16} {16:16} {32:16} {48:16} + AVD: name=rgba64be chroma=0:0 flags=0xa1 [be][rgb][alpha] + 0: p=0 st=8 o=0 sh=0 d=16 + 1: p=0 st=8 o=2 sh=0 d=16 + 2: p=0 st=8 o=4 sh=0 d=16 + 3: p=0 st=8 o=6 sh=0 d=16 +rgbaf16: [GENERIC] fcsp=rgb ctype=float + Basic desc: [ba][bb][a][rgb][le][float] + planes=1, chroma=0:0 align=1:1 + {64/[0:0] } + 0: 64bits {0:16} {16:16} {32:16} {48:16} + Regular: planes=1 compbytes=2 bitpad=0 chroma=1x1 ctype=float + 0: {1, 2, 3, 4} + AVD: name=rgbaf16le chroma=0:0 flags=0x2a0 [rgb][alpha][float] + 0: p=0 st=8 o=0 sh=0 d=16 + 1: p=0 st=8 o=2 sh=0 d=16 + 2: p=0 st=8 o=4 sh=0 d=16 + 3: p=0 st=8 o=6 sh=0 d=16 +rgbaf16be: [GENERIC] fcsp=rgb ctype=float + Basic desc: [ba][bb][a][rgb][be][float] + planes=1, chroma=0:0 align=1:1 + {64/[0:0] } + 0: 64bits endian_bytes=2 {0:16} {16:16} {32:16} {48:16} + AVD: name=rgbaf16be chroma=0:0 flags=0x2a1 [be][rgb][alpha][float] + 0: p=0 st=8 o=0 sh=0 d=16 + 1: p=0 st=8 o=2 sh=0 d=16 + 2: p=0 st=8 o=4 sh=0 d=16 + 3: p=0 st=8 o=6 sh=0 d=16 +rgbaf32: [GENERIC] fcsp=rgb ctype=float + Basic desc: [ba][a][rgb][le][be][float] + planes=1, chroma=0:0 align=1:1 + {-128/[0:0] } + 0: -128bits {} {} {} {} + [NOALLOC] + AVD: name=rgbaf32le chroma=0:0 flags=0x2a0 [rgb][alpha][float] + 0: p=0 st=16 o=0 sh=0 d=32 + 1: p=0 st=16 o=4 sh=0 d=32 + 2: p=0 st=16 o=8 sh=0 d=32 + 3: p=0 st=16 o=12 sh=0 d=32 +rgbaf32be: [GENERIC] fcsp=rgb ctype=float + Basic desc: [ba][a][rgb][le][be][float] + planes=1, chroma=0:0 align=1:1 + {-128/[0:0] } + 0: -128bits endian_bytes=4 {} {} {} {} + [NOALLOC] + AVD: name=rgbaf32be chroma=0:0 flags=0x2a1 [be][rgb][alpha][float] + 0: p=0 st=16 o=0 sh=0 d=32 + 1: p=0 st=16 o=4 sh=0 d=32 + 2: p=0 st=16 o=8 sh=0 d=32 + 3: p=0 st=16 o=12 sh=0 d=32 +rgbf32: [GENERIC] fcsp=rgb ctype=float + Basic desc: [ba][rgb][le][be][float] + planes=1, chroma=0:0 align=1:1 + {96/[0:0] } + 0: 96bits {} {} {} {} + AVD: name=rgbf32le chroma=0:0 flags=0x220 [rgb][float] + 0: p=0 st=12 o=0 sh=0 d=32 + 1: p=0 st=12 o=4 sh=0 d=32 + 2: p=0 st=12 o=8 sh=0 d=32 +rgbf32be: [GENERIC] fcsp=rgb ctype=float + Basic desc: [ba][rgb][le][be][float] + planes=1, chroma=0:0 align=1:1 + {96/[0:0] } + 0: 96bits endian_bytes=4 {} {} {} {} + AVD: name=rgbf32be chroma=0:0 flags=0x221 [be][rgb][float] + 0: p=0 st=12 o=0 sh=0 d=32 + 1: p=0 st=12 o=4 sh=0 d=32 + 2: p=0 st=12 o=8 sh=0 d=32 +uyvy422: ctype=uint + Basic desc: [ba][yuv][le][be][uint] + planes=1, chroma=1:0 align=2:1 + {16/[0:0] } + 0: 16bits {8:8} {0:8} {16:8} {} + luma_offsets=[ 8 24] + AVD: name=uyvy422 chroma=1:0 flags=0x0 + 0: p=0 st=2 o=1 sh=0 d=8 + 1: p=0 st=4 o=0 sh=0 d=8 + 2: p=0 st=4 o=2 sh=0 d=8 +uyyvyy411: [GENERIC] ctype=uint + Basic desc: [yuv][le][be][uint] + planes=1, chroma=2:0 align=4:1 + {12/[0:0] } + 0: 12bits {8:8} {0:8} {24:8} {} + luma_offsets=[ 8 16 32 40] + AVD: name=uyyvyy411 chroma=2:0 flags=0x0 + 0: p=0 st=4 o=1 sh=0 d=8 + 1: p=0 st=6 o=0 sh=0 d=8 + 2: p=0 st=6 o=3 sh=0 d=8 +vaapi: ctype=unknown + Basic desc: [le][be][hw] + planes=0, chroma=1:1 align=2:2 + {} + AVD: name=vaapi chroma=1:1 flags=0x8 [hw] +vdpau: ctype=unknown + Basic desc: [le][be][hw] + planes=0, chroma=1:1 align=2:2 + {} + AVD: name=vdpau chroma=1:1 flags=0x8 [hw] +vdpau_output: fcsp=rgb ctype=unknown + Basic desc: [rgb][le][hw] + planes=0, chroma=0:0 align=1:1 + {} +videotoolbox: ctype=unknown + Basic desc: [le][be][hw] + planes=0, chroma=0:0 align=1:1 + {} + AVD: name=videotoolbox_vld chroma=0:0 flags=0x8 [hw] +vulkan: ctype=unknown + Basic desc: [le][be][hw] + planes=0, chroma=0:0 align=1:1 + {} + AVD: name=vulkan chroma=0:0 flags=0x8 [hw] +vuya: [GENERIC] ctype=uint + Basic desc: [ba][bb][a][yuv][le][be][uint] + planes=1, chroma=0:0 align=1:1 + {32/[0:0] } + 0: 32bits {16:8} {8:8} {0:8} {24:8} + Regular: planes=1 compbytes=1 bitpad=0 chroma=1x1 ctype=uint + 0: {3, 2, 1, 4} + AVD: name=vuya chroma=0:0 flags=0x80 [alpha] + 0: p=0 st=4 o=2 sh=0 d=8 + 1: p=0 st=4 o=1 sh=0 d=8 + 2: p=0 st=4 o=0 sh=0 d=8 + 3: p=0 st=4 o=3 sh=0 d=8 +vuyx: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuv][le][be][uint] + planes=1, chroma=0:0 align=1:1 + {32/[0:0] } + 0: 32bits {16:8} {8:8} {0:8} {} + Regular: planes=1 compbytes=1 bitpad=0 chroma=1x1 ctype=uint + 0: {3, 2, 1, 0} + AVD: name=vuyx chroma=0:0 flags=0x0 + 0: p=0 st=4 o=2 sh=0 d=8 + 1: p=0 st=4 o=1 sh=0 d=8 + 2: p=0 st=4 o=0 sh=0 d=8 +x2bgr10: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [ba][rgb][le][uint] + planes=1, chroma=0:0 align=1:1 + {32/[0:0] } + 0: 32bits {0:10} {10:10} {20:10} {} + AVD: name=x2bgr10le chroma=0:0 flags=0x20 [rgb] + 0: p=0 st=4 o=0 sh=0 d=10 + 1: p=0 st=4 o=1 sh=2 d=10 + 2: p=0 st=4 o=2 sh=4 d=10 +x2bgr10be: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [ba][rgb][be][uint] + planes=1, chroma=0:0 align=1:1 + {32/[0:0] } + 0: 32bits endian_bytes=4 {0:10} {10:10} {20:10} {} + AVD: name=x2bgr10be chroma=0:0 flags=0x21 [be][rgb] + 0: p=0 st=4 o=2 sh=0 d=10 + 1: p=0 st=4 o=1 sh=2 d=10 + 2: p=0 st=4 o=0 sh=4 d=10 +x2rgb10be: [GENERIC] fcsp=rgb ctype=uint + Basic desc: [ba][rgb][be][uint] + planes=1, chroma=0:0 align=1:1 + {32/[0:0] } + 0: 32bits endian_bytes=4 {20:10} {10:10} {0:10} {} + AVD: name=x2rgb10be chroma=0:0 flags=0x21 [be][rgb] + 0: p=0 st=4 o=0 sh=4 d=10 + 1: p=0 st=4 o=1 sh=2 d=10 + 2: p=0 st=4 o=2 sh=0 d=10 +xv30: [GENERIC] ctype=uint + Basic desc: [ba][yuv][le][uint] + planes=1, chroma=0:0 align=1:1 + {32/[0:0] } + 0: 32bits {10:10} {0:10} {20:10} {} + AVD: name=xv30le chroma=0:0 flags=0x0 + 0: p=0 st=4 o=1 sh=2 d=10 + 1: p=0 st=4 o=0 sh=0 d=10 + 2: p=0 st=4 o=2 sh=4 d=10 +xv30be: [GENERIC] ctype=unknown + Basic desc: [ba][yuv][le][be] + planes=1, chroma=0:0 align=1:1 + {32/[0:0] } + 0: 32bits endian_bytes=4 {} {} {} {} + AVD: name=xv30be chroma=0:0 flags=0x5 [be][bs] + 0: p=0 st=32 o=10 sh=0 d=10 + 1: p=0 st=32 o=0 sh=0 d=10 + 2: p=0 st=32 o=20 sh=0 d=10 +xv36: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuv][le][uint] + planes=1, chroma=0:0 align=1:1 + {64/[0:0] } + 0: 64bits {16:16/4} {0:16/4} {32:16/4} {} + Regular: planes=1 compbytes=2 bitpad=4 chroma=1x1 ctype=uint + 0: {2, 1, 3, 0} + AVD: name=xv36le chroma=0:0 flags=0x0 + 0: p=0 st=8 o=2 sh=4 d=12 + 1: p=0 st=8 o=0 sh=4 d=12 + 2: p=0 st=8 o=4 sh=4 d=12 +xv36be: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuv][be][uint] + planes=1, chroma=0:0 align=1:1 + {64/[0:0] } + 0: 64bits endian_bytes=2 {16:16/4} {0:16/4} {32:16/4} {} + AVD: name=xv36be chroma=0:0 flags=0x1 [be] + 0: p=0 st=8 o=2 sh=4 d=12 + 1: p=0 st=8 o=0 sh=4 d=12 + 2: p=0 st=8 o=4 sh=4 d=12 +xyz12: [GENERIC] fcsp=xyz ctype=uint + Basic desc: [ba][bb][xyz][le][uint] + planes=1, chroma=0:0 align=1:1 + {48/[0:0] } + 0: 48bits {0:16/4} {16:16/4} {32:16/4} {} + Regular: planes=1 compbytes=2 bitpad=4 chroma=1x1 ctype=uint + 0: {1, 2, 3} + AVD: name=xyz12le chroma=0:0 flags=0x400 + 0: p=0 st=6 o=0 sh=4 d=12 + 1: p=0 st=6 o=2 sh=4 d=12 + 2: p=0 st=6 o=4 sh=4 d=12 +xyz12be: [GENERIC] fcsp=xyz ctype=uint + Basic desc: [ba][bb][xyz][be][uint] + planes=1, chroma=0:0 align=1:1 + {48/[0:0] } + 0: 48bits endian_bytes=2 {0:16/4} {16:16/4} {32:16/4} {} + AVD: name=xyz12be chroma=0:0 flags=0x401 [be] + 0: p=0 st=6 o=0 sh=4 d=12 + 1: p=0 st=6 o=2 sh=4 d=12 + 2: p=0 st=6 o=4 sh=4 d=12 +y1: fcsp=rgb ctype=uint + Basic desc: [ba][bb][rgb][gray][le][uint] + planes=1, chroma=0:0 align=1:1 + {8/[0:0] } + 0: 8bits {0:8/-7} {} {} {} + Regular: planes=1 compbytes=1 bitpad=-7 chroma=1x1 ctype=uint + 0: {1} +y210: [GENERIC] ctype=uint + Basic desc: [ba][yuv][le][uint] + planes=1, chroma=1:0 align=2:1 + {32/[0:0] } + 0: 32bits {0:16/6} {16:16/6} {48:16/6} {} + luma_offsets=[ 0 32] + AVD: name=y210le chroma=1:0 flags=0x0 + 0: p=0 st=4 o=0 sh=6 d=10 + 1: p=0 st=8 o=2 sh=6 d=10 + 2: p=0 st=8 o=6 sh=6 d=10 +y210be: [GENERIC] ctype=uint + Basic desc: [ba][yuv][be][uint] + planes=1, chroma=1:0 align=2:1 + {32/[0:0] } + 0: 32bits endian_bytes=2 {0:16/6} {16:16/6} {48:16/6} {} + luma_offsets=[ 0 32] + AVD: name=y210be chroma=1:0 flags=0x1 [be] + 0: p=0 st=4 o=0 sh=6 d=10 + 1: p=0 st=8 o=2 sh=6 d=10 + 2: p=0 st=8 o=6 sh=6 d=10 +y212: [GENERIC] ctype=uint + Basic desc: [ba][yuv][le][uint] + planes=1, chroma=1:0 align=2:1 + {32/[0:0] } + 0: 32bits {0:16/4} {16:16/4} {48:16/4} {} + luma_offsets=[ 0 32] + AVD: name=y212le chroma=1:0 flags=0x0 + 0: p=0 st=4 o=0 sh=4 d=12 + 1: p=0 st=8 o=2 sh=4 d=12 + 2: p=0 st=8 o=6 sh=4 d=12 +y212be: [GENERIC] ctype=uint + Basic desc: [ba][yuv][be][uint] + planes=1, chroma=1:0 align=2:1 + {32/[0:0] } + 0: 32bits endian_bytes=2 {0:16/4} {16:16/4} {48:16/4} {} + luma_offsets=[ 0 32] + AVD: name=y212be chroma=1:0 flags=0x1 [be] + 0: p=0 st=4 o=0 sh=4 d=12 + 1: p=0 st=8 o=2 sh=4 d=12 + 2: p=0 st=8 o=6 sh=4 d=12 +ya16: [GENERIC] ctype=uint + Basic desc: [ba][bb][a][yuv][gray][le][uint] + planes=1, chroma=0:0 align=1:1 + {32/[0:0] } + 0: 32bits {0:16} {} {} {16:16} + Regular: planes=1 compbytes=2 bitpad=0 chroma=1x1 ctype=uint + 0: {1, 4} + AVD: name=ya16le chroma=0:0 flags=0x80 [alpha] + 0: p=0 st=4 o=0 sh=0 d=16 + 1: p=0 st=4 o=2 sh=0 d=16 +ya16be: [GENERIC] ctype=uint + Basic desc: [ba][bb][a][yuv][gray][be][uint] + planes=1, chroma=0:0 align=1:1 + {32/[0:0] } + 0: 32bits endian_bytes=2 {0:16} {} {} {16:16} + AVD: name=ya16be chroma=0:0 flags=0x81 [be][alpha] + 0: p=0 st=4 o=0 sh=0 d=16 + 1: p=0 st=4 o=2 sh=0 d=16 +ya8: [GENERIC] ctype=uint + Basic desc: [ba][bb][a][yuv][gray][le][be][uint] + planes=1, chroma=0:0 align=1:1 + {16/[0:0] } + 0: 16bits {0:8} {} {} {8:8} + Regular: planes=1 compbytes=1 bitpad=0 chroma=1x1 ctype=uint + 0: {1, 4} + AVD: name=ya8 chroma=0:0 flags=0x80 [alpha] + 0: p=0 st=2 o=0 sh=0 d=8 + 1: p=0 st=2 o=1 sh=0 d=8 +yap16: ctype=uint + Basic desc: [ba][bb][a][yuvp][yuv][gray][le][uint] + planes=2, chroma=0:0 align=1:1 + {16/[0:0] 16/[0:0] } + 0: 16bits {0:16} {} {} {} + 1: 16bits {} {} {} {0:16} + Regular: planes=2 compbytes=2 bitpad=0 chroma=1x1 ctype=uint + 0: {1} + 1: {4} +yap8: ctype=uint + Basic desc: [ba][bb][a][yuvp][yuv][gray][le][uint] + planes=2, chroma=0:0 align=1:1 + {8/[0:0] 8/[0:0] } + 0: 8bits {0:8} {} {} {} + 1: 8bits {} {} {} {0:8} + Regular: planes=2 compbytes=1 bitpad=0 chroma=1x1 ctype=uint + 0: {1} + 1: {4} +yuv410p: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][le][be][uint] + planes=3, chroma=2:2 align=4:4 + {8/[0:0] 8/[2:2] 8/[2:2] } + 0: 8bits {0:8} {} {} {} + 1: 8bits {} {0:8} {} {} + 2: 8bits {} {} {0:8} {} + Regular: planes=3 compbytes=1 bitpad=0 chroma=4x4 ctype=uint + 0: {1} + 1: {2} + 2: {3} + AVD: name=yuv410p chroma=2:2 flags=0x10 [planar] + 0: p=0 st=1 o=0 sh=0 d=8 + 1: p=1 st=1 o=0 sh=0 d=8 + 2: p=2 st=1 o=0 sh=0 d=8 +yuv410pf: ctype=float + Basic desc: [ba][bb][yuv][le][float] + planes=3, chroma=2:2 align=4:4 + {32/[0:0] 32/[2:2] 32/[2:2] } + 0: 32bits {0:32} {} {} {} + 1: 32bits {} {0:32} {} {} + 2: 32bits {} {} {0:32} {} + Regular: planes=3 compbytes=4 bitpad=0 chroma=4x4 ctype=float + 0: {1} + 1: {2} + 2: {3} +yuv411p: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][le][be][uint] + planes=3, chroma=2:0 align=4:1 + {8/[0:0] 8/[2:0] 8/[2:0] } + 0: 8bits {0:8} {} {} {} + 1: 8bits {} {0:8} {} {} + 2: 8bits {} {} {0:8} {} + Regular: planes=3 compbytes=1 bitpad=0 chroma=4x1 ctype=uint + 0: {1} + 1: {2} + 2: {3} + AVD: name=yuv411p chroma=2:0 flags=0x10 [planar] + 0: p=0 st=1 o=0 sh=0 d=8 + 1: p=1 st=1 o=0 sh=0 d=8 + 2: p=2 st=1 o=0 sh=0 d=8 +yuv411pf: ctype=float + Basic desc: [ba][bb][yuv][le][float] + planes=3, chroma=2:0 align=4:1 + {32/[0:0] 32/[2:0] 32/[2:0] } + 0: 32bits {0:32} {} {} {} + 1: 32bits {} {0:32} {} {} + 2: 32bits {} {} {0:32} {} + Regular: planes=3 compbytes=4 bitpad=0 chroma=4x1 ctype=float + 0: {1} + 1: {2} + 2: {3} +yuv420p: ctype=uint + Basic desc: [ba][bb][yuvp][yuv][le][be][uint] + planes=3, chroma=1:1 align=2:2 + {8/[0:0] 8/[1:1] 8/[1:1] } + 0: 8bits {0:8} {} {} {} + 1: 8bits {} {0:8} {} {} + 2: 8bits {} {} {0:8} {} + Regular: planes=3 compbytes=1 bitpad=0 chroma=2x2 ctype=uint + 0: {1} + 1: {2} + 2: {3} + AVD: name=yuv420p chroma=1:1 flags=0x10 [planar] + 0: p=0 st=1 o=0 sh=0 d=8 + 1: p=1 st=1 o=0 sh=0 d=8 + 2: p=2 st=1 o=0 sh=0 d=8 + Ambiguous alias: yuvj420p +yuv420p10: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][le][uint] + planes=3, chroma=1:1 align=2:2 + {16/[0:0] 16/[1:1] 16/[1:1] } + 0: 16bits {0:16/-6} {} {} {} + 1: 16bits {} {0:16/-6} {} {} + 2: 16bits {} {} {0:16/-6} {} + Regular: planes=3 compbytes=2 bitpad=-6 chroma=2x2 ctype=uint + 0: {1} + 1: {2} + 2: {3} + AVD: name=yuv420p10le chroma=1:1 flags=0x10 [planar] + 0: p=0 st=2 o=0 sh=0 d=10 + 1: p=1 st=2 o=0 sh=0 d=10 + 2: p=2 st=2 o=0 sh=0 d=10 +yuv420p10be: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][be][uint] + planes=3, chroma=1:1 align=2:2 + {16/[0:0] 16/[1:1] 16/[1:1] } + 0: 16bits endian_bytes=2 {0:16/-6} {} {} {} + 1: 16bits endian_bytes=2 {} {0:16/-6} {} {} + 2: 16bits endian_bytes=2 {} {} {0:16/-6} {} + AVD: name=yuv420p10be chroma=1:1 flags=0x11 [be][planar] + 0: p=0 st=2 o=0 sh=0 d=10 + 1: p=1 st=2 o=0 sh=0 d=10 + 2: p=2 st=2 o=0 sh=0 d=10 +yuv420p12: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][le][uint] + planes=3, chroma=1:1 align=2:2 + {16/[0:0] 16/[1:1] 16/[1:1] } + 0: 16bits {0:16/-4} {} {} {} + 1: 16bits {} {0:16/-4} {} {} + 2: 16bits {} {} {0:16/-4} {} + Regular: planes=3 compbytes=2 bitpad=-4 chroma=2x2 ctype=uint + 0: {1} + 1: {2} + 2: {3} + AVD: name=yuv420p12le chroma=1:1 flags=0x10 [planar] + 0: p=0 st=2 o=0 sh=0 d=12 + 1: p=1 st=2 o=0 sh=0 d=12 + 2: p=2 st=2 o=0 sh=0 d=12 +yuv420p12be: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][be][uint] + planes=3, chroma=1:1 align=2:2 + {16/[0:0] 16/[1:1] 16/[1:1] } + 0: 16bits endian_bytes=2 {0:16/-4} {} {} {} + 1: 16bits endian_bytes=2 {} {0:16/-4} {} {} + 2: 16bits endian_bytes=2 {} {} {0:16/-4} {} + AVD: name=yuv420p12be chroma=1:1 flags=0x11 [be][planar] + 0: p=0 st=2 o=0 sh=0 d=12 + 1: p=1 st=2 o=0 sh=0 d=12 + 2: p=2 st=2 o=0 sh=0 d=12 +yuv420p14: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][le][uint] + planes=3, chroma=1:1 align=2:2 + {16/[0:0] 16/[1:1] 16/[1:1] } + 0: 16bits {0:16/-2} {} {} {} + 1: 16bits {} {0:16/-2} {} {} + 2: 16bits {} {} {0:16/-2} {} + Regular: planes=3 compbytes=2 bitpad=-2 chroma=2x2 ctype=uint + 0: {1} + 1: {2} + 2: {3} + AVD: name=yuv420p14le chroma=1:1 flags=0x10 [planar] + 0: p=0 st=2 o=0 sh=0 d=14 + 1: p=1 st=2 o=0 sh=0 d=14 + 2: p=2 st=2 o=0 sh=0 d=14 +yuv420p14be: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][be][uint] + planes=3, chroma=1:1 align=2:2 + {16/[0:0] 16/[1:1] 16/[1:1] } + 0: 16bits endian_bytes=2 {0:16/-2} {} {} {} + 1: 16bits endian_bytes=2 {} {0:16/-2} {} {} + 2: 16bits endian_bytes=2 {} {} {0:16/-2} {} + AVD: name=yuv420p14be chroma=1:1 flags=0x11 [be][planar] + 0: p=0 st=2 o=0 sh=0 d=14 + 1: p=1 st=2 o=0 sh=0 d=14 + 2: p=2 st=2 o=0 sh=0 d=14 +yuv420p16: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][le][uint] + planes=3, chroma=1:1 align=2:2 + {16/[0:0] 16/[1:1] 16/[1:1] } + 0: 16bits {0:16} {} {} {} + 1: 16bits {} {0:16} {} {} + 2: 16bits {} {} {0:16} {} + Regular: planes=3 compbytes=2 bitpad=0 chroma=2x2 ctype=uint + 0: {1} + 1: {2} + 2: {3} + AVD: name=yuv420p16le chroma=1:1 flags=0x10 [planar] + 0: p=0 st=2 o=0 sh=0 d=16 + 1: p=1 st=2 o=0 sh=0 d=16 + 2: p=2 st=2 o=0 sh=0 d=16 +yuv420p16be: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][be][uint] + planes=3, chroma=1:1 align=2:2 + {16/[0:0] 16/[1:1] 16/[1:1] } + 0: 16bits endian_bytes=2 {0:16} {} {} {} + 1: 16bits endian_bytes=2 {} {0:16} {} {} + 2: 16bits endian_bytes=2 {} {} {0:16} {} + AVD: name=yuv420p16be chroma=1:1 flags=0x11 [be][planar] + 0: p=0 st=2 o=0 sh=0 d=16 + 1: p=1 st=2 o=0 sh=0 d=16 + 2: p=2 st=2 o=0 sh=0 d=16 +yuv420p9: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][le][uint] + planes=3, chroma=1:1 align=2:2 + {16/[0:0] 16/[1:1] 16/[1:1] } + 0: 16bits {0:16/-7} {} {} {} + 1: 16bits {} {0:16/-7} {} {} + 2: 16bits {} {} {0:16/-7} {} + Regular: planes=3 compbytes=2 bitpad=-7 chroma=2x2 ctype=uint + 0: {1} + 1: {2} + 2: {3} + AVD: name=yuv420p9le chroma=1:1 flags=0x10 [planar] + 0: p=0 st=2 o=0 sh=0 d=9 + 1: p=1 st=2 o=0 sh=0 d=9 + 2: p=2 st=2 o=0 sh=0 d=9 +yuv420p9be: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][be][uint] + planes=3, chroma=1:1 align=2:2 + {16/[0:0] 16/[1:1] 16/[1:1] } + 0: 16bits endian_bytes=2 {0:16/-7} {} {} {} + 1: 16bits endian_bytes=2 {} {0:16/-7} {} {} + 2: 16bits endian_bytes=2 {} {} {0:16/-7} {} + AVD: name=yuv420p9be chroma=1:1 flags=0x11 [be][planar] + 0: p=0 st=2 o=0 sh=0 d=9 + 1: p=1 st=2 o=0 sh=0 d=9 + 2: p=2 st=2 o=0 sh=0 d=9 +yuv420pf: ctype=float + Basic desc: [ba][bb][yuv][le][float] + planes=3, chroma=1:1 align=2:2 + {32/[0:0] 32/[1:1] 32/[1:1] } + 0: 32bits {0:32} {} {} {} + 1: 32bits {} {0:32} {} {} + 2: 32bits {} {} {0:32} {} + Regular: planes=3 compbytes=4 bitpad=0 chroma=2x2 ctype=float + 0: {1} + 1: {2} + 2: {3} +yuv422p: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][le][be][uint] + planes=3, chroma=1:0 align=2:1 + {8/[0:0] 8/[1:0] 8/[1:0] } + 0: 8bits {0:8} {} {} {} + 1: 8bits {} {0:8} {} {} + 2: 8bits {} {} {0:8} {} + Regular: planes=3 compbytes=1 bitpad=0 chroma=2x1 ctype=uint + 0: {1} + 1: {2} + 2: {3} + AVD: name=yuv422p chroma=1:0 flags=0x10 [planar] + 0: p=0 st=1 o=0 sh=0 d=8 + 1: p=1 st=1 o=0 sh=0 d=8 + 2: p=2 st=1 o=0 sh=0 d=8 +yuv422p10: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][le][uint] + planes=3, chroma=1:0 align=2:1 + {16/[0:0] 16/[1:0] 16/[1:0] } + 0: 16bits {0:16/-6} {} {} {} + 1: 16bits {} {0:16/-6} {} {} + 2: 16bits {} {} {0:16/-6} {} + Regular: planes=3 compbytes=2 bitpad=-6 chroma=2x1 ctype=uint + 0: {1} + 1: {2} + 2: {3} + AVD: name=yuv422p10le chroma=1:0 flags=0x10 [planar] + 0: p=0 st=2 o=0 sh=0 d=10 + 1: p=1 st=2 o=0 sh=0 d=10 + 2: p=2 st=2 o=0 sh=0 d=10 +yuv422p10be: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][be][uint] + planes=3, chroma=1:0 align=2:1 + {16/[0:0] 16/[1:0] 16/[1:0] } + 0: 16bits endian_bytes=2 {0:16/-6} {} {} {} + 1: 16bits endian_bytes=2 {} {0:16/-6} {} {} + 2: 16bits endian_bytes=2 {} {} {0:16/-6} {} + AVD: name=yuv422p10be chroma=1:0 flags=0x11 [be][planar] + 0: p=0 st=2 o=0 sh=0 d=10 + 1: p=1 st=2 o=0 sh=0 d=10 + 2: p=2 st=2 o=0 sh=0 d=10 +yuv422p12: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][le][uint] + planes=3, chroma=1:0 align=2:1 + {16/[0:0] 16/[1:0] 16/[1:0] } + 0: 16bits {0:16/-4} {} {} {} + 1: 16bits {} {0:16/-4} {} {} + 2: 16bits {} {} {0:16/-4} {} + Regular: planes=3 compbytes=2 bitpad=-4 chroma=2x1 ctype=uint + 0: {1} + 1: {2} + 2: {3} + AVD: name=yuv422p12le chroma=1:0 flags=0x10 [planar] + 0: p=0 st=2 o=0 sh=0 d=12 + 1: p=1 st=2 o=0 sh=0 d=12 + 2: p=2 st=2 o=0 sh=0 d=12 +yuv422p12be: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][be][uint] + planes=3, chroma=1:0 align=2:1 + {16/[0:0] 16/[1:0] 16/[1:0] } + 0: 16bits endian_bytes=2 {0:16/-4} {} {} {} + 1: 16bits endian_bytes=2 {} {0:16/-4} {} {} + 2: 16bits endian_bytes=2 {} {} {0:16/-4} {} + AVD: name=yuv422p12be chroma=1:0 flags=0x11 [be][planar] + 0: p=0 st=2 o=0 sh=0 d=12 + 1: p=1 st=2 o=0 sh=0 d=12 + 2: p=2 st=2 o=0 sh=0 d=12 +yuv422p14: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][le][uint] + planes=3, chroma=1:0 align=2:1 + {16/[0:0] 16/[1:0] 16/[1:0] } + 0: 16bits {0:16/-2} {} {} {} + 1: 16bits {} {0:16/-2} {} {} + 2: 16bits {} {} {0:16/-2} {} + Regular: planes=3 compbytes=2 bitpad=-2 chroma=2x1 ctype=uint + 0: {1} + 1: {2} + 2: {3} + AVD: name=yuv422p14le chroma=1:0 flags=0x10 [planar] + 0: p=0 st=2 o=0 sh=0 d=14 + 1: p=1 st=2 o=0 sh=0 d=14 + 2: p=2 st=2 o=0 sh=0 d=14 +yuv422p14be: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][be][uint] + planes=3, chroma=1:0 align=2:1 + {16/[0:0] 16/[1:0] 16/[1:0] } + 0: 16bits endian_bytes=2 {0:16/-2} {} {} {} + 1: 16bits endian_bytes=2 {} {0:16/-2} {} {} + 2: 16bits endian_bytes=2 {} {} {0:16/-2} {} + AVD: name=yuv422p14be chroma=1:0 flags=0x11 [be][planar] + 0: p=0 st=2 o=0 sh=0 d=14 + 1: p=1 st=2 o=0 sh=0 d=14 + 2: p=2 st=2 o=0 sh=0 d=14 +yuv422p16: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][le][uint] + planes=3, chroma=1:0 align=2:1 + {16/[0:0] 16/[1:0] 16/[1:0] } + 0: 16bits {0:16} {} {} {} + 1: 16bits {} {0:16} {} {} + 2: 16bits {} {} {0:16} {} + Regular: planes=3 compbytes=2 bitpad=0 chroma=2x1 ctype=uint + 0: {1} + 1: {2} + 2: {3} + AVD: name=yuv422p16le chroma=1:0 flags=0x10 [planar] + 0: p=0 st=2 o=0 sh=0 d=16 + 1: p=1 st=2 o=0 sh=0 d=16 + 2: p=2 st=2 o=0 sh=0 d=16 +yuv422p16be: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][be][uint] + planes=3, chroma=1:0 align=2:1 + {16/[0:0] 16/[1:0] 16/[1:0] } + 0: 16bits endian_bytes=2 {0:16} {} {} {} + 1: 16bits endian_bytes=2 {} {0:16} {} {} + 2: 16bits endian_bytes=2 {} {} {0:16} {} + AVD: name=yuv422p16be chroma=1:0 flags=0x11 [be][planar] + 0: p=0 st=2 o=0 sh=0 d=16 + 1: p=1 st=2 o=0 sh=0 d=16 + 2: p=2 st=2 o=0 sh=0 d=16 +yuv422p9: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][le][uint] + planes=3, chroma=1:0 align=2:1 + {16/[0:0] 16/[1:0] 16/[1:0] } + 0: 16bits {0:16/-7} {} {} {} + 1: 16bits {} {0:16/-7} {} {} + 2: 16bits {} {} {0:16/-7} {} + Regular: planes=3 compbytes=2 bitpad=-7 chroma=2x1 ctype=uint + 0: {1} + 1: {2} + 2: {3} + AVD: name=yuv422p9le chroma=1:0 flags=0x10 [planar] + 0: p=0 st=2 o=0 sh=0 d=9 + 1: p=1 st=2 o=0 sh=0 d=9 + 2: p=2 st=2 o=0 sh=0 d=9 +yuv422p9be: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][be][uint] + planes=3, chroma=1:0 align=2:1 + {16/[0:0] 16/[1:0] 16/[1:0] } + 0: 16bits endian_bytes=2 {0:16/-7} {} {} {} + 1: 16bits endian_bytes=2 {} {0:16/-7} {} {} + 2: 16bits endian_bytes=2 {} {} {0:16/-7} {} + AVD: name=yuv422p9be chroma=1:0 flags=0x11 [be][planar] + 0: p=0 st=2 o=0 sh=0 d=9 + 1: p=1 st=2 o=0 sh=0 d=9 + 2: p=2 st=2 o=0 sh=0 d=9 +yuv422pf: ctype=float + Basic desc: [ba][bb][yuv][le][float] + planes=3, chroma=1:0 align=2:1 + {32/[0:0] 32/[1:0] 32/[1:0] } + 0: 32bits {0:32} {} {} {} + 1: 32bits {} {0:32} {} {} + 2: 32bits {} {} {0:32} {} + Regular: planes=3 compbytes=4 bitpad=0 chroma=2x1 ctype=float + 0: {1} + 1: {2} + 2: {3} +yuv440p: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][le][be][uint] + planes=3, chroma=0:1 align=1:2 + {8/[0:0] 8/[0:1] 8/[0:1] } + 0: 8bits {0:8} {} {} {} + 1: 8bits {} {0:8} {} {} + 2: 8bits {} {} {0:8} {} + Regular: planes=3 compbytes=1 bitpad=0 chroma=1x2 ctype=uint + 0: {1} + 1: {2} + 2: {3} + AVD: name=yuv440p chroma=0:1 flags=0x10 [planar] + 0: p=0 st=1 o=0 sh=0 d=8 + 1: p=1 st=1 o=0 sh=0 d=8 + 2: p=2 st=1 o=0 sh=0 d=8 +yuv440p10: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][le][uint] + planes=3, chroma=0:1 align=1:2 + {16/[0:0] 16/[0:1] 16/[0:1] } + 0: 16bits {0:16/-6} {} {} {} + 1: 16bits {} {0:16/-6} {} {} + 2: 16bits {} {} {0:16/-6} {} + Regular: planes=3 compbytes=2 bitpad=-6 chroma=1x2 ctype=uint + 0: {1} + 1: {2} + 2: {3} + AVD: name=yuv440p10le chroma=0:1 flags=0x10 [planar] + 0: p=0 st=2 o=0 sh=0 d=10 + 1: p=1 st=2 o=0 sh=0 d=10 + 2: p=2 st=2 o=0 sh=0 d=10 +yuv440p10be: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][be][uint] + planes=3, chroma=0:1 align=1:2 + {16/[0:0] 16/[0:1] 16/[0:1] } + 0: 16bits endian_bytes=2 {0:16/-6} {} {} {} + 1: 16bits endian_bytes=2 {} {0:16/-6} {} {} + 2: 16bits endian_bytes=2 {} {} {0:16/-6} {} + AVD: name=yuv440p10be chroma=0:1 flags=0x11 [be][planar] + 0: p=0 st=2 o=0 sh=0 d=10 + 1: p=1 st=2 o=0 sh=0 d=10 + 2: p=2 st=2 o=0 sh=0 d=10 +yuv440p12: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][le][uint] + planes=3, chroma=0:1 align=1:2 + {16/[0:0] 16/[0:1] 16/[0:1] } + 0: 16bits {0:16/-4} {} {} {} + 1: 16bits {} {0:16/-4} {} {} + 2: 16bits {} {} {0:16/-4} {} + Regular: planes=3 compbytes=2 bitpad=-4 chroma=1x2 ctype=uint + 0: {1} + 1: {2} + 2: {3} + AVD: name=yuv440p12le chroma=0:1 flags=0x10 [planar] + 0: p=0 st=2 o=0 sh=0 d=12 + 1: p=1 st=2 o=0 sh=0 d=12 + 2: p=2 st=2 o=0 sh=0 d=12 +yuv440p12be: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][be][uint] + planes=3, chroma=0:1 align=1:2 + {16/[0:0] 16/[0:1] 16/[0:1] } + 0: 16bits endian_bytes=2 {0:16/-4} {} {} {} + 1: 16bits endian_bytes=2 {} {0:16/-4} {} {} + 2: 16bits endian_bytes=2 {} {} {0:16/-4} {} + AVD: name=yuv440p12be chroma=0:1 flags=0x11 [be][planar] + 0: p=0 st=2 o=0 sh=0 d=12 + 1: p=1 st=2 o=0 sh=0 d=12 + 2: p=2 st=2 o=0 sh=0 d=12 +yuv440pf: ctype=float + Basic desc: [ba][bb][yuv][le][float] + planes=3, chroma=0:1 align=1:2 + {32/[0:0] 32/[0:1] 32/[0:1] } + 0: 32bits {0:32} {} {} {} + 1: 32bits {} {0:32} {} {} + 2: 32bits {} {} {0:32} {} + Regular: planes=3 compbytes=4 bitpad=0 chroma=1x2 ctype=float + 0: {1} + 1: {2} + 2: {3} +yuv444p: ctype=uint + Basic desc: [ba][bb][yuvp][yuv][le][be][uint] + planes=3, chroma=0:0 align=1:1 + {8/[0:0] 8/[0:0] 8/[0:0] } + 0: 8bits {0:8} {} {} {} + 1: 8bits {} {0:8} {} {} + 2: 8bits {} {} {0:8} {} + Regular: planes=3 compbytes=1 bitpad=0 chroma=1x1 ctype=uint + 0: {1} + 1: {2} + 2: {3} + AVD: name=yuv444p chroma=0:0 flags=0x10 [planar] + 0: p=0 st=1 o=0 sh=0 d=8 + 1: p=1 st=1 o=0 sh=0 d=8 + 2: p=2 st=1 o=0 sh=0 d=8 + Ambiguous alias: yuvj444p +yuv444p10: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][le][uint] + planes=3, chroma=0:0 align=1:1 + {16/[0:0] 16/[0:0] 16/[0:0] } + 0: 16bits {0:16/-6} {} {} {} + 1: 16bits {} {0:16/-6} {} {} + 2: 16bits {} {} {0:16/-6} {} + Regular: planes=3 compbytes=2 bitpad=-6 chroma=1x1 ctype=uint + 0: {1} + 1: {2} + 2: {3} + AVD: name=yuv444p10le chroma=0:0 flags=0x10 [planar] + 0: p=0 st=2 o=0 sh=0 d=10 + 1: p=1 st=2 o=0 sh=0 d=10 + 2: p=2 st=2 o=0 sh=0 d=10 +yuv444p10be: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][be][uint] + planes=3, chroma=0:0 align=1:1 + {16/[0:0] 16/[0:0] 16/[0:0] } + 0: 16bits endian_bytes=2 {0:16/-6} {} {} {} + 1: 16bits endian_bytes=2 {} {0:16/-6} {} {} + 2: 16bits endian_bytes=2 {} {} {0:16/-6} {} + AVD: name=yuv444p10be chroma=0:0 flags=0x11 [be][planar] + 0: p=0 st=2 o=0 sh=0 d=10 + 1: p=1 st=2 o=0 sh=0 d=10 + 2: p=2 st=2 o=0 sh=0 d=10 +yuv444p12: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][le][uint] + planes=3, chroma=0:0 align=1:1 + {16/[0:0] 16/[0:0] 16/[0:0] } + 0: 16bits {0:16/-4} {} {} {} + 1: 16bits {} {0:16/-4} {} {} + 2: 16bits {} {} {0:16/-4} {} + Regular: planes=3 compbytes=2 bitpad=-4 chroma=1x1 ctype=uint + 0: {1} + 1: {2} + 2: {3} + AVD: name=yuv444p12le chroma=0:0 flags=0x10 [planar] + 0: p=0 st=2 o=0 sh=0 d=12 + 1: p=1 st=2 o=0 sh=0 d=12 + 2: p=2 st=2 o=0 sh=0 d=12 +yuv444p12be: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][be][uint] + planes=3, chroma=0:0 align=1:1 + {16/[0:0] 16/[0:0] 16/[0:0] } + 0: 16bits endian_bytes=2 {0:16/-4} {} {} {} + 1: 16bits endian_bytes=2 {} {0:16/-4} {} {} + 2: 16bits endian_bytes=2 {} {} {0:16/-4} {} + AVD: name=yuv444p12be chroma=0:0 flags=0x11 [be][planar] + 0: p=0 st=2 o=0 sh=0 d=12 + 1: p=1 st=2 o=0 sh=0 d=12 + 2: p=2 st=2 o=0 sh=0 d=12 +yuv444p14: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][le][uint] + planes=3, chroma=0:0 align=1:1 + {16/[0:0] 16/[0:0] 16/[0:0] } + 0: 16bits {0:16/-2} {} {} {} + 1: 16bits {} {0:16/-2} {} {} + 2: 16bits {} {} {0:16/-2} {} + Regular: planes=3 compbytes=2 bitpad=-2 chroma=1x1 ctype=uint + 0: {1} + 1: {2} + 2: {3} + AVD: name=yuv444p14le chroma=0:0 flags=0x10 [planar] + 0: p=0 st=2 o=0 sh=0 d=14 + 1: p=1 st=2 o=0 sh=0 d=14 + 2: p=2 st=2 o=0 sh=0 d=14 +yuv444p14be: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][be][uint] + planes=3, chroma=0:0 align=1:1 + {16/[0:0] 16/[0:0] 16/[0:0] } + 0: 16bits endian_bytes=2 {0:16/-2} {} {} {} + 1: 16bits endian_bytes=2 {} {0:16/-2} {} {} + 2: 16bits endian_bytes=2 {} {} {0:16/-2} {} + AVD: name=yuv444p14be chroma=0:0 flags=0x11 [be][planar] + 0: p=0 st=2 o=0 sh=0 d=14 + 1: p=1 st=2 o=0 sh=0 d=14 + 2: p=2 st=2 o=0 sh=0 d=14 +yuv444p16: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][le][uint] + planes=3, chroma=0:0 align=1:1 + {16/[0:0] 16/[0:0] 16/[0:0] } + 0: 16bits {0:16} {} {} {} + 1: 16bits {} {0:16} {} {} + 2: 16bits {} {} {0:16} {} + Regular: planes=3 compbytes=2 bitpad=0 chroma=1x1 ctype=uint + 0: {1} + 1: {2} + 2: {3} + AVD: name=yuv444p16le chroma=0:0 flags=0x10 [planar] + 0: p=0 st=2 o=0 sh=0 d=16 + 1: p=1 st=2 o=0 sh=0 d=16 + 2: p=2 st=2 o=0 sh=0 d=16 +yuv444p16be: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][be][uint] + planes=3, chroma=0:0 align=1:1 + {16/[0:0] 16/[0:0] 16/[0:0] } + 0: 16bits endian_bytes=2 {0:16} {} {} {} + 1: 16bits endian_bytes=2 {} {0:16} {} {} + 2: 16bits endian_bytes=2 {} {} {0:16} {} + AVD: name=yuv444p16be chroma=0:0 flags=0x11 [be][planar] + 0: p=0 st=2 o=0 sh=0 d=16 + 1: p=1 st=2 o=0 sh=0 d=16 + 2: p=2 st=2 o=0 sh=0 d=16 +yuv444p9: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][le][uint] + planes=3, chroma=0:0 align=1:1 + {16/[0:0] 16/[0:0] 16/[0:0] } + 0: 16bits {0:16/-7} {} {} {} + 1: 16bits {} {0:16/-7} {} {} + 2: 16bits {} {} {0:16/-7} {} + Regular: planes=3 compbytes=2 bitpad=-7 chroma=1x1 ctype=uint + 0: {1} + 1: {2} + 2: {3} + AVD: name=yuv444p9le chroma=0:0 flags=0x10 [planar] + 0: p=0 st=2 o=0 sh=0 d=9 + 1: p=1 st=2 o=0 sh=0 d=9 + 2: p=2 st=2 o=0 sh=0 d=9 +yuv444p9be: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][be][uint] + planes=3, chroma=0:0 align=1:1 + {16/[0:0] 16/[0:0] 16/[0:0] } + 0: 16bits endian_bytes=2 {0:16/-7} {} {} {} + 1: 16bits endian_bytes=2 {} {0:16/-7} {} {} + 2: 16bits endian_bytes=2 {} {} {0:16/-7} {} + AVD: name=yuv444p9be chroma=0:0 flags=0x11 [be][planar] + 0: p=0 st=2 o=0 sh=0 d=9 + 1: p=1 st=2 o=0 sh=0 d=9 + 2: p=2 st=2 o=0 sh=0 d=9 +yuv444pf: ctype=float + Basic desc: [ba][bb][yuv][le][float] + planes=3, chroma=0:0 align=1:1 + {32/[0:0] 32/[0:0] 32/[0:0] } + 0: 32bits {0:32} {} {} {} + 1: 32bits {} {0:32} {} {} + 2: 32bits {} {} {0:32} {} + Regular: planes=3 compbytes=4 bitpad=0 chroma=1x1 ctype=float + 0: {1} + 1: {2} + 2: {3} +yuva410pf: ctype=float + Basic desc: [ba][bb][a][yuv][le][float] + planes=4, chroma=2:2 align=4:4 + {32/[0:0] 32/[2:2] 32/[2:2] 32/[0:0] } + 0: 32bits {0:32} {} {} {} + 1: 32bits {} {0:32} {} {} + 2: 32bits {} {} {0:32} {} + 3: 32bits {} {} {} {0:32} + Regular: planes=4 compbytes=4 bitpad=0 chroma=4x4 ctype=float + 0: {1} + 1: {2} + 2: {3} + 3: {4} +yuva411pf: ctype=float + Basic desc: [ba][bb][a][yuv][le][float] + planes=4, chroma=2:0 align=4:1 + {32/[0:0] 32/[2:0] 32/[2:0] 32/[0:0] } + 0: 32bits {0:32} {} {} {} + 1: 32bits {} {0:32} {} {} + 2: 32bits {} {} {0:32} {} + 3: 32bits {} {} {} {0:32} + Regular: planes=4 compbytes=4 bitpad=0 chroma=4x1 ctype=float + 0: {1} + 1: {2} + 2: {3} + 3: {4} +yuva420p: [GENERIC] ctype=uint + Basic desc: [ba][bb][a][yuvp][yuv][le][be][uint] + planes=4, chroma=1:1 align=2:2 + {8/[0:0] 8/[1:1] 8/[1:1] 8/[0:0] } + 0: 8bits {0:8} {} {} {} + 1: 8bits {} {0:8} {} {} + 2: 8bits {} {} {0:8} {} + 3: 8bits {} {} {} {0:8} + Regular: planes=4 compbytes=1 bitpad=0 chroma=2x2 ctype=uint + 0: {1} + 1: {2} + 2: {3} + 3: {4} + AVD: name=yuva420p chroma=1:1 flags=0x90 [planar][alpha] + 0: p=0 st=1 o=0 sh=0 d=8 + 1: p=1 st=1 o=0 sh=0 d=8 + 2: p=2 st=1 o=0 sh=0 d=8 + 3: p=3 st=1 o=0 sh=0 d=8 +yuva420p10: [GENERIC] ctype=uint + Basic desc: [ba][bb][a][yuvp][yuv][le][uint] + planes=4, chroma=1:1 align=2:2 + {16/[0:0] 16/[1:1] 16/[1:1] 16/[0:0] } + 0: 16bits {0:16/-6} {} {} {} + 1: 16bits {} {0:16/-6} {} {} + 2: 16bits {} {} {0:16/-6} {} + 3: 16bits {} {} {} {0:16/-6} + Regular: planes=4 compbytes=2 bitpad=-6 chroma=2x2 ctype=uint + 0: {1} + 1: {2} + 2: {3} + 3: {4} + AVD: name=yuva420p10le chroma=1:1 flags=0x90 [planar][alpha] + 0: p=0 st=2 o=0 sh=0 d=10 + 1: p=1 st=2 o=0 sh=0 d=10 + 2: p=2 st=2 o=0 sh=0 d=10 + 3: p=3 st=2 o=0 sh=0 d=10 +yuva420p10be: [GENERIC] ctype=uint + Basic desc: [ba][bb][a][yuvp][yuv][be][uint] + planes=4, chroma=1:1 align=2:2 + {16/[0:0] 16/[1:1] 16/[1:1] 16/[0:0] } + 0: 16bits endian_bytes=2 {0:16/-6} {} {} {} + 1: 16bits endian_bytes=2 {} {0:16/-6} {} {} + 2: 16bits endian_bytes=2 {} {} {0:16/-6} {} + 3: 16bits endian_bytes=2 {} {} {} {0:16/-6} + AVD: name=yuva420p10be chroma=1:1 flags=0x91 [be][planar][alpha] + 0: p=0 st=2 o=0 sh=0 d=10 + 1: p=1 st=2 o=0 sh=0 d=10 + 2: p=2 st=2 o=0 sh=0 d=10 + 3: p=3 st=2 o=0 sh=0 d=10 +yuva420p16: [GENERIC] ctype=uint + Basic desc: [ba][bb][a][yuvp][yuv][le][uint] + planes=4, chroma=1:1 align=2:2 + {16/[0:0] 16/[1:1] 16/[1:1] 16/[0:0] } + 0: 16bits {0:16} {} {} {} + 1: 16bits {} {0:16} {} {} + 2: 16bits {} {} {0:16} {} + 3: 16bits {} {} {} {0:16} + Regular: planes=4 compbytes=2 bitpad=0 chroma=2x2 ctype=uint + 0: {1} + 1: {2} + 2: {3} + 3: {4} + AVD: name=yuva420p16le chroma=1:1 flags=0x90 [planar][alpha] + 0: p=0 st=2 o=0 sh=0 d=16 + 1: p=1 st=2 o=0 sh=0 d=16 + 2: p=2 st=2 o=0 sh=0 d=16 + 3: p=3 st=2 o=0 sh=0 d=16 +yuva420p16be: [GENERIC] ctype=uint + Basic desc: [ba][bb][a][yuvp][yuv][be][uint] + planes=4, chroma=1:1 align=2:2 + {16/[0:0] 16/[1:1] 16/[1:1] 16/[0:0] } + 0: 16bits endian_bytes=2 {0:16} {} {} {} + 1: 16bits endian_bytes=2 {} {0:16} {} {} + 2: 16bits endian_bytes=2 {} {} {0:16} {} + 3: 16bits endian_bytes=2 {} {} {} {0:16} + AVD: name=yuva420p16be chroma=1:1 flags=0x91 [be][planar][alpha] + 0: p=0 st=2 o=0 sh=0 d=16 + 1: p=1 st=2 o=0 sh=0 d=16 + 2: p=2 st=2 o=0 sh=0 d=16 + 3: p=3 st=2 o=0 sh=0 d=16 +yuva420p9: [GENERIC] ctype=uint + Basic desc: [ba][bb][a][yuvp][yuv][le][uint] + planes=4, chroma=1:1 align=2:2 + {16/[0:0] 16/[1:1] 16/[1:1] 16/[0:0] } + 0: 16bits {0:16/-7} {} {} {} + 1: 16bits {} {0:16/-7} {} {} + 2: 16bits {} {} {0:16/-7} {} + 3: 16bits {} {} {} {0:16/-7} + Regular: planes=4 compbytes=2 bitpad=-7 chroma=2x2 ctype=uint + 0: {1} + 1: {2} + 2: {3} + 3: {4} + AVD: name=yuva420p9le chroma=1:1 flags=0x90 [planar][alpha] + 0: p=0 st=2 o=0 sh=0 d=9 + 1: p=1 st=2 o=0 sh=0 d=9 + 2: p=2 st=2 o=0 sh=0 d=9 + 3: p=3 st=2 o=0 sh=0 d=9 +yuva420p9be: [GENERIC] ctype=uint + Basic desc: [ba][bb][a][yuvp][yuv][be][uint] + planes=4, chroma=1:1 align=2:2 + {16/[0:0] 16/[1:1] 16/[1:1] 16/[0:0] } + 0: 16bits endian_bytes=2 {0:16/-7} {} {} {} + 1: 16bits endian_bytes=2 {} {0:16/-7} {} {} + 2: 16bits endian_bytes=2 {} {} {0:16/-7} {} + 3: 16bits endian_bytes=2 {} {} {} {0:16/-7} + AVD: name=yuva420p9be chroma=1:1 flags=0x91 [be][planar][alpha] + 0: p=0 st=2 o=0 sh=0 d=9 + 1: p=1 st=2 o=0 sh=0 d=9 + 2: p=2 st=2 o=0 sh=0 d=9 + 3: p=3 st=2 o=0 sh=0 d=9 +yuva420pf: ctype=float + Basic desc: [ba][bb][a][yuv][le][float] + planes=4, chroma=1:1 align=2:2 + {32/[0:0] 32/[1:1] 32/[1:1] 32/[0:0] } + 0: 32bits {0:32} {} {} {} + 1: 32bits {} {0:32} {} {} + 2: 32bits {} {} {0:32} {} + 3: 32bits {} {} {} {0:32} + Regular: planes=4 compbytes=4 bitpad=0 chroma=2x2 ctype=float + 0: {1} + 1: {2} + 2: {3} + 3: {4} +yuva422p: [GENERIC] ctype=uint + Basic desc: [ba][bb][a][yuvp][yuv][le][be][uint] + planes=4, chroma=1:0 align=2:1 + {8/[0:0] 8/[1:0] 8/[1:0] 8/[0:0] } + 0: 8bits {0:8} {} {} {} + 1: 8bits {} {0:8} {} {} + 2: 8bits {} {} {0:8} {} + 3: 8bits {} {} {} {0:8} + Regular: planes=4 compbytes=1 bitpad=0 chroma=2x1 ctype=uint + 0: {1} + 1: {2} + 2: {3} + 3: {4} + AVD: name=yuva422p chroma=1:0 flags=0x90 [planar][alpha] + 0: p=0 st=1 o=0 sh=0 d=8 + 1: p=1 st=1 o=0 sh=0 d=8 + 2: p=2 st=1 o=0 sh=0 d=8 + 3: p=3 st=1 o=0 sh=0 d=8 +yuva422p10: [GENERIC] ctype=uint + Basic desc: [ba][bb][a][yuvp][yuv][le][uint] + planes=4, chroma=1:0 align=2:1 + {16/[0:0] 16/[1:0] 16/[1:0] 16/[0:0] } + 0: 16bits {0:16/-6} {} {} {} + 1: 16bits {} {0:16/-6} {} {} + 2: 16bits {} {} {0:16/-6} {} + 3: 16bits {} {} {} {0:16/-6} + Regular: planes=4 compbytes=2 bitpad=-6 chroma=2x1 ctype=uint + 0: {1} + 1: {2} + 2: {3} + 3: {4} + AVD: name=yuva422p10le chroma=1:0 flags=0x90 [planar][alpha] + 0: p=0 st=2 o=0 sh=0 d=10 + 1: p=1 st=2 o=0 sh=0 d=10 + 2: p=2 st=2 o=0 sh=0 d=10 + 3: p=3 st=2 o=0 sh=0 d=10 +yuva422p10be: [GENERIC] ctype=uint + Basic desc: [ba][bb][a][yuvp][yuv][be][uint] + planes=4, chroma=1:0 align=2:1 + {16/[0:0] 16/[1:0] 16/[1:0] 16/[0:0] } + 0: 16bits endian_bytes=2 {0:16/-6} {} {} {} + 1: 16bits endian_bytes=2 {} {0:16/-6} {} {} + 2: 16bits endian_bytes=2 {} {} {0:16/-6} {} + 3: 16bits endian_bytes=2 {} {} {} {0:16/-6} + AVD: name=yuva422p10be chroma=1:0 flags=0x91 [be][planar][alpha] + 0: p=0 st=2 o=0 sh=0 d=10 + 1: p=1 st=2 o=0 sh=0 d=10 + 2: p=2 st=2 o=0 sh=0 d=10 + 3: p=3 st=2 o=0 sh=0 d=10 +yuva422p12: [GENERIC] ctype=uint + Basic desc: [ba][bb][a][yuvp][yuv][le][uint] + planes=4, chroma=1:0 align=2:1 + {16/[0:0] 16/[1:0] 16/[1:0] 16/[0:0] } + 0: 16bits {0:16/-4} {} {} {} + 1: 16bits {} {0:16/-4} {} {} + 2: 16bits {} {} {0:16/-4} {} + 3: 16bits {} {} {} {0:16/-4} + Regular: planes=4 compbytes=2 bitpad=-4 chroma=2x1 ctype=uint + 0: {1} + 1: {2} + 2: {3} + 3: {4} + AVD: name=yuva422p12le chroma=1:0 flags=0x90 [planar][alpha] + 0: p=0 st=2 o=0 sh=0 d=12 + 1: p=1 st=2 o=0 sh=0 d=12 + 2: p=2 st=2 o=0 sh=0 d=12 + 3: p=3 st=2 o=0 sh=0 d=12 +yuva422p12be: [GENERIC] ctype=uint + Basic desc: [ba][bb][a][yuvp][yuv][be][uint] + planes=4, chroma=1:0 align=2:1 + {16/[0:0] 16/[1:0] 16/[1:0] 16/[0:0] } + 0: 16bits endian_bytes=2 {0:16/-4} {} {} {} + 1: 16bits endian_bytes=2 {} {0:16/-4} {} {} + 2: 16bits endian_bytes=2 {} {} {0:16/-4} {} + 3: 16bits endian_bytes=2 {} {} {} {0:16/-4} + AVD: name=yuva422p12be chroma=1:0 flags=0x91 [be][planar][alpha] + 0: p=0 st=2 o=0 sh=0 d=12 + 1: p=1 st=2 o=0 sh=0 d=12 + 2: p=2 st=2 o=0 sh=0 d=12 + 3: p=3 st=2 o=0 sh=0 d=12 +yuva422p16: [GENERIC] ctype=uint + Basic desc: [ba][bb][a][yuvp][yuv][le][uint] + planes=4, chroma=1:0 align=2:1 + {16/[0:0] 16/[1:0] 16/[1:0] 16/[0:0] } + 0: 16bits {0:16} {} {} {} + 1: 16bits {} {0:16} {} {} + 2: 16bits {} {} {0:16} {} + 3: 16bits {} {} {} {0:16} + Regular: planes=4 compbytes=2 bitpad=0 chroma=2x1 ctype=uint + 0: {1} + 1: {2} + 2: {3} + 3: {4} + AVD: name=yuva422p16le chroma=1:0 flags=0x90 [planar][alpha] + 0: p=0 st=2 o=0 sh=0 d=16 + 1: p=1 st=2 o=0 sh=0 d=16 + 2: p=2 st=2 o=0 sh=0 d=16 + 3: p=3 st=2 o=0 sh=0 d=16 +yuva422p16be: [GENERIC] ctype=uint + Basic desc: [ba][bb][a][yuvp][yuv][be][uint] + planes=4, chroma=1:0 align=2:1 + {16/[0:0] 16/[1:0] 16/[1:0] 16/[0:0] } + 0: 16bits endian_bytes=2 {0:16} {} {} {} + 1: 16bits endian_bytes=2 {} {0:16} {} {} + 2: 16bits endian_bytes=2 {} {} {0:16} {} + 3: 16bits endian_bytes=2 {} {} {} {0:16} + AVD: name=yuva422p16be chroma=1:0 flags=0x91 [be][planar][alpha] + 0: p=0 st=2 o=0 sh=0 d=16 + 1: p=1 st=2 o=0 sh=0 d=16 + 2: p=2 st=2 o=0 sh=0 d=16 + 3: p=3 st=2 o=0 sh=0 d=16 +yuva422p9: [GENERIC] ctype=uint + Basic desc: [ba][bb][a][yuvp][yuv][le][uint] + planes=4, chroma=1:0 align=2:1 + {16/[0:0] 16/[1:0] 16/[1:0] 16/[0:0] } + 0: 16bits {0:16/-7} {} {} {} + 1: 16bits {} {0:16/-7} {} {} + 2: 16bits {} {} {0:16/-7} {} + 3: 16bits {} {} {} {0:16/-7} + Regular: planes=4 compbytes=2 bitpad=-7 chroma=2x1 ctype=uint + 0: {1} + 1: {2} + 2: {3} + 3: {4} + AVD: name=yuva422p9le chroma=1:0 flags=0x90 [planar][alpha] + 0: p=0 st=2 o=0 sh=0 d=9 + 1: p=1 st=2 o=0 sh=0 d=9 + 2: p=2 st=2 o=0 sh=0 d=9 + 3: p=3 st=2 o=0 sh=0 d=9 +yuva422p9be: [GENERIC] ctype=uint + Basic desc: [ba][bb][a][yuvp][yuv][be][uint] + planes=4, chroma=1:0 align=2:1 + {16/[0:0] 16/[1:0] 16/[1:0] 16/[0:0] } + 0: 16bits endian_bytes=2 {0:16/-7} {} {} {} + 1: 16bits endian_bytes=2 {} {0:16/-7} {} {} + 2: 16bits endian_bytes=2 {} {} {0:16/-7} {} + 3: 16bits endian_bytes=2 {} {} {} {0:16/-7} + AVD: name=yuva422p9be chroma=1:0 flags=0x91 [be][planar][alpha] + 0: p=0 st=2 o=0 sh=0 d=9 + 1: p=1 st=2 o=0 sh=0 d=9 + 2: p=2 st=2 o=0 sh=0 d=9 + 3: p=3 st=2 o=0 sh=0 d=9 +yuva422pf: ctype=float + Basic desc: [ba][bb][a][yuv][le][float] + planes=4, chroma=1:0 align=2:1 + {32/[0:0] 32/[1:0] 32/[1:0] 32/[0:0] } + 0: 32bits {0:32} {} {} {} + 1: 32bits {} {0:32} {} {} + 2: 32bits {} {} {0:32} {} + 3: 32bits {} {} {} {0:32} + Regular: planes=4 compbytes=4 bitpad=0 chroma=2x1 ctype=float + 0: {1} + 1: {2} + 2: {3} + 3: {4} +yuva440pf: ctype=float + Basic desc: [ba][bb][a][yuv][le][float] + planes=4, chroma=0:1 align=1:2 + {32/[0:0] 32/[0:1] 32/[0:1] 32/[0:0] } + 0: 32bits {0:32} {} {} {} + 1: 32bits {} {0:32} {} {} + 2: 32bits {} {} {0:32} {} + 3: 32bits {} {} {} {0:32} + Regular: planes=4 compbytes=4 bitpad=0 chroma=1x2 ctype=float + 0: {1} + 1: {2} + 2: {3} + 3: {4} +yuva444p: [GENERIC] ctype=uint + Basic desc: [ba][bb][a][yuvp][yuv][le][be][uint] + planes=4, chroma=0:0 align=1:1 + {8/[0:0] 8/[0:0] 8/[0:0] 8/[0:0] } + 0: 8bits {0:8} {} {} {} + 1: 8bits {} {0:8} {} {} + 2: 8bits {} {} {0:8} {} + 3: 8bits {} {} {} {0:8} + Regular: planes=4 compbytes=1 bitpad=0 chroma=1x1 ctype=uint + 0: {1} + 1: {2} + 2: {3} + 3: {4} + AVD: name=yuva444p chroma=0:0 flags=0x90 [planar][alpha] + 0: p=0 st=1 o=0 sh=0 d=8 + 1: p=1 st=1 o=0 sh=0 d=8 + 2: p=2 st=1 o=0 sh=0 d=8 + 3: p=3 st=1 o=0 sh=0 d=8 +yuva444p10: [GENERIC] ctype=uint + Basic desc: [ba][bb][a][yuvp][yuv][le][uint] + planes=4, chroma=0:0 align=1:1 + {16/[0:0] 16/[0:0] 16/[0:0] 16/[0:0] } + 0: 16bits {0:16/-6} {} {} {} + 1: 16bits {} {0:16/-6} {} {} + 2: 16bits {} {} {0:16/-6} {} + 3: 16bits {} {} {} {0:16/-6} + Regular: planes=4 compbytes=2 bitpad=-6 chroma=1x1 ctype=uint + 0: {1} + 1: {2} + 2: {3} + 3: {4} + AVD: name=yuva444p10le chroma=0:0 flags=0x90 [planar][alpha] + 0: p=0 st=2 o=0 sh=0 d=10 + 1: p=1 st=2 o=0 sh=0 d=10 + 2: p=2 st=2 o=0 sh=0 d=10 + 3: p=3 st=2 o=0 sh=0 d=10 +yuva444p10be: [GENERIC] ctype=uint + Basic desc: [ba][bb][a][yuvp][yuv][be][uint] + planes=4, chroma=0:0 align=1:1 + {16/[0:0] 16/[0:0] 16/[0:0] 16/[0:0] } + 0: 16bits endian_bytes=2 {0:16/-6} {} {} {} + 1: 16bits endian_bytes=2 {} {0:16/-6} {} {} + 2: 16bits endian_bytes=2 {} {} {0:16/-6} {} + 3: 16bits endian_bytes=2 {} {} {} {0:16/-6} + AVD: name=yuva444p10be chroma=0:0 flags=0x91 [be][planar][alpha] + 0: p=0 st=2 o=0 sh=0 d=10 + 1: p=1 st=2 o=0 sh=0 d=10 + 2: p=2 st=2 o=0 sh=0 d=10 + 3: p=3 st=2 o=0 sh=0 d=10 +yuva444p12: [GENERIC] ctype=uint + Basic desc: [ba][bb][a][yuvp][yuv][le][uint] + planes=4, chroma=0:0 align=1:1 + {16/[0:0] 16/[0:0] 16/[0:0] 16/[0:0] } + 0: 16bits {0:16/-4} {} {} {} + 1: 16bits {} {0:16/-4} {} {} + 2: 16bits {} {} {0:16/-4} {} + 3: 16bits {} {} {} {0:16/-4} + Regular: planes=4 compbytes=2 bitpad=-4 chroma=1x1 ctype=uint + 0: {1} + 1: {2} + 2: {3} + 3: {4} + AVD: name=yuva444p12le chroma=0:0 flags=0x90 [planar][alpha] + 0: p=0 st=2 o=0 sh=0 d=12 + 1: p=1 st=2 o=0 sh=0 d=12 + 2: p=2 st=2 o=0 sh=0 d=12 + 3: p=3 st=2 o=0 sh=0 d=12 +yuva444p12be: [GENERIC] ctype=uint + Basic desc: [ba][bb][a][yuvp][yuv][be][uint] + planes=4, chroma=0:0 align=1:1 + {16/[0:0] 16/[0:0] 16/[0:0] 16/[0:0] } + 0: 16bits endian_bytes=2 {0:16/-4} {} {} {} + 1: 16bits endian_bytes=2 {} {0:16/-4} {} {} + 2: 16bits endian_bytes=2 {} {} {0:16/-4} {} + 3: 16bits endian_bytes=2 {} {} {} {0:16/-4} + AVD: name=yuva444p12be chroma=0:0 flags=0x91 [be][planar][alpha] + 0: p=0 st=2 o=0 sh=0 d=12 + 1: p=1 st=2 o=0 sh=0 d=12 + 2: p=2 st=2 o=0 sh=0 d=12 + 3: p=3 st=2 o=0 sh=0 d=12 +yuva444p16: [GENERIC] ctype=uint + Basic desc: [ba][bb][a][yuvp][yuv][le][uint] + planes=4, chroma=0:0 align=1:1 + {16/[0:0] 16/[0:0] 16/[0:0] 16/[0:0] } + 0: 16bits {0:16} {} {} {} + 1: 16bits {} {0:16} {} {} + 2: 16bits {} {} {0:16} {} + 3: 16bits {} {} {} {0:16} + Regular: planes=4 compbytes=2 bitpad=0 chroma=1x1 ctype=uint + 0: {1} + 1: {2} + 2: {3} + 3: {4} + AVD: name=yuva444p16le chroma=0:0 flags=0x90 [planar][alpha] + 0: p=0 st=2 o=0 sh=0 d=16 + 1: p=1 st=2 o=0 sh=0 d=16 + 2: p=2 st=2 o=0 sh=0 d=16 + 3: p=3 st=2 o=0 sh=0 d=16 +yuva444p16be: [GENERIC] ctype=uint + Basic desc: [ba][bb][a][yuvp][yuv][be][uint] + planes=4, chroma=0:0 align=1:1 + {16/[0:0] 16/[0:0] 16/[0:0] 16/[0:0] } + 0: 16bits endian_bytes=2 {0:16} {} {} {} + 1: 16bits endian_bytes=2 {} {0:16} {} {} + 2: 16bits endian_bytes=2 {} {} {0:16} {} + 3: 16bits endian_bytes=2 {} {} {} {0:16} + AVD: name=yuva444p16be chroma=0:0 flags=0x91 [be][planar][alpha] + 0: p=0 st=2 o=0 sh=0 d=16 + 1: p=1 st=2 o=0 sh=0 d=16 + 2: p=2 st=2 o=0 sh=0 d=16 + 3: p=3 st=2 o=0 sh=0 d=16 +yuva444p9: [GENERIC] ctype=uint + Basic desc: [ba][bb][a][yuvp][yuv][le][uint] + planes=4, chroma=0:0 align=1:1 + {16/[0:0] 16/[0:0] 16/[0:0] 16/[0:0] } + 0: 16bits {0:16/-7} {} {} {} + 1: 16bits {} {0:16/-7} {} {} + 2: 16bits {} {} {0:16/-7} {} + 3: 16bits {} {} {} {0:16/-7} + Regular: planes=4 compbytes=2 bitpad=-7 chroma=1x1 ctype=uint + 0: {1} + 1: {2} + 2: {3} + 3: {4} + AVD: name=yuva444p9le chroma=0:0 flags=0x90 [planar][alpha] + 0: p=0 st=2 o=0 sh=0 d=9 + 1: p=1 st=2 o=0 sh=0 d=9 + 2: p=2 st=2 o=0 sh=0 d=9 + 3: p=3 st=2 o=0 sh=0 d=9 +yuva444p9be: [GENERIC] ctype=uint + Basic desc: [ba][bb][a][yuvp][yuv][be][uint] + planes=4, chroma=0:0 align=1:1 + {16/[0:0] 16/[0:0] 16/[0:0] 16/[0:0] } + 0: 16bits endian_bytes=2 {0:16/-7} {} {} {} + 1: 16bits endian_bytes=2 {} {0:16/-7} {} {} + 2: 16bits endian_bytes=2 {} {} {0:16/-7} {} + 3: 16bits endian_bytes=2 {} {} {} {0:16/-7} + AVD: name=yuva444p9be chroma=0:0 flags=0x91 [be][planar][alpha] + 0: p=0 st=2 o=0 sh=0 d=9 + 1: p=1 st=2 o=0 sh=0 d=9 + 2: p=2 st=2 o=0 sh=0 d=9 + 3: p=3 st=2 o=0 sh=0 d=9 +yuva444pf: ctype=float + Basic desc: [ba][bb][a][yuv][le][float] + planes=4, chroma=0:0 align=1:1 + {32/[0:0] 32/[0:0] 32/[0:0] 32/[0:0] } + 0: 32bits {0:32} {} {} {} + 1: 32bits {} {0:32} {} {} + 2: 32bits {} {} {0:32} {} + 3: 32bits {} {} {} {0:32} + Regular: planes=4 compbytes=4 bitpad=0 chroma=1x1 ctype=float + 0: {1} + 1: {2} + 2: {3} + 3: {4} +yuvj411p: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][le][be][uint] + planes=3, chroma=2:0 align=4:1 + {8/[0:0] 8/[2:0] 8/[2:0] } + 0: 8bits {0:8} {} {} {} + 1: 8bits {} {0:8} {} {} + 2: 8bits {} {} {0:8} {} + Regular: planes=3 compbytes=1 bitpad=0 chroma=4x1 ctype=uint + 0: {1} + 1: {2} + 2: {3} + AVD: name=yuvj411p chroma=2:0 flags=0x10 [planar] + 0: p=0 st=1 o=0 sh=0 d=8 + 1: p=1 st=1 o=0 sh=0 d=8 + 2: p=2 st=1 o=0 sh=0 d=8 +yuvj422p: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][le][be][uint] + planes=3, chroma=1:0 align=2:1 + {8/[0:0] 8/[1:0] 8/[1:0] } + 0: 8bits {0:8} {} {} {} + 1: 8bits {} {0:8} {} {} + 2: 8bits {} {} {0:8} {} + Regular: planes=3 compbytes=1 bitpad=0 chroma=2x1 ctype=uint + 0: {1} + 1: {2} + 2: {3} + AVD: name=yuvj422p chroma=1:0 flags=0x10 [planar] + 0: p=0 st=1 o=0 sh=0 d=8 + 1: p=1 st=1 o=0 sh=0 d=8 + 2: p=2 st=1 o=0 sh=0 d=8 +yuvj440p: [GENERIC] ctype=uint + Basic desc: [ba][bb][yuvp][yuv][le][be][uint] + planes=3, chroma=0:1 align=1:2 + {8/[0:0] 8/[0:1] 8/[0:1] } + 0: 8bits {0:8} {} {} {} + 1: 8bits {} {0:8} {} {} + 2: 8bits {} {} {0:8} {} + Regular: planes=3 compbytes=1 bitpad=0 chroma=1x2 ctype=uint + 0: {1} + 1: {2} + 2: {3} + AVD: name=yuvj440p chroma=0:1 flags=0x10 [planar] + 0: p=0 st=1 o=0 sh=0 d=8 + 1: p=1 st=1 o=0 sh=0 d=8 + 2: p=2 st=1 o=0 sh=0 d=8 +yuyv422: [GENERIC] ctype=uint + Basic desc: [ba][yuv][le][be][uint] + planes=1, chroma=1:0 align=2:1 + {16/[0:0] } + 0: 16bits {0:8} {8:8} {24:8} {} + luma_offsets=[ 0 16] + AVD: name=yuyv422 chroma=1:0 flags=0x0 + 0: p=0 st=2 o=0 sh=0 d=8 + 1: p=0 st=4 o=1 sh=0 d=8 + 2: p=0 st=4 o=3 sh=0 d=8 +yvyu422: [GENERIC] ctype=uint + Basic desc: [ba][yuv][le][be][uint] + planes=1, chroma=1:0 align=2:1 + {16/[0:0] } + 0: 16bits {0:8} {24:8} {8:8} {} + luma_offsets=[ 0 16] + AVD: name=yvyu422 chroma=1:0 flags=0x0 + 0: p=0 st=2 o=0 sh=0 d=8 + 1: p=0 st=4 o=3 sh=0 d=8 + 2: p=0 st=4 o=1 sh=0 d=8 diff --git a/test/ref/ffmpeg7/repack.txt b/test/ref/ffmpeg7/repack.txt new file mode 100644 index 0000000..e245b54 --- /dev/null +++ b/test/ref/ffmpeg7/repack.txt @@ -0,0 +1,385 @@ +0bgr => [pa] [un] gbrp | a=1:1 [tu] [tp] +0bgr => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +0rgb => [pa] [un] gbrp | a=1:1 [tu] [tp] +0rgb => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +abgr => [pa] [un] gbrap | a=1:1 [tu] [tp] +abgr => [pa] [un] gbrapf32 | a=1:1 [planar-f32] +argb => [pa] [un] gbrap | a=1:1 [tu] [tp] +argb => [pa] [un] gbrapf32 | a=1:1 [planar-f32] +ayuv64 => [pa] [un] yuva444p16 | a=1:1 [tu] [tp] +ayuv64 => [pa] [un] yuva444pf | a=1:1 [planar-f32] +ayuv64be => [pa] [un] yuva444p16 | a=1:1 [tu] [tp] +ayuv64be => [pa] [un] yuva444pf | a=1:1 [planar-f32] +bayer_bggr16 => no +bayer_bggr16be => no +bayer_bggr8 => no +bayer_gbrg16 => no +bayer_gbrg16be => no +bayer_gbrg8 => no +bayer_grbg16 => no +bayer_grbg16be => no +bayer_grbg8 => no +bayer_rggb16 => no +bayer_rggb16be => no +bayer_rggb8 => no +bgr0 => [pa] [un] gbrp | a=1:1 [tu] [tp] +bgr0 => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +bgr24 => [pa] [un] gbrp | a=1:1 [tu] [tp] +bgr24 => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +bgr4 => no +bgr444 => [pa] [un] gbrp4 | a=1:1 +bgr444 => [pa] [un] gbrp | a=1:1 [expand-8bit] [tu] [tp] +bgr444 => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +bgr444be => [pa] [un] gbrp4 | a=1:1 +bgr444be => [pa] [un] gbrp | a=1:1 [expand-8bit] [tu] [tp] +bgr444be => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +bgr48 => [pa] [un] gbrp16 | a=1:1 [tu] [tp] +bgr48 => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +bgr48be => [pa] [un] gbrp16 | a=1:1 [tu] [tp] +bgr48be => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +bgr4_byte => [pa] [un] gbrp2 | a=1:1 +bgr4_byte => [pa] [un] gbrp1 | a=1:1 [round-down] +bgr4_byte => [pa] [un] gbrp | a=1:1 [expand-8bit] [tu] [tp] +bgr4_byte => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +bgr555 => [pa] [un] gbrp5 | a=1:1 +bgr555 => [pa] [un] gbrp | a=1:1 [expand-8bit] [tu] [tp] +bgr555 => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +bgr555be => [pa] [un] gbrp5 | a=1:1 +bgr555be => [pa] [un] gbrp | a=1:1 [expand-8bit] [tu] [tp] +bgr555be => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +bgr565 => [pa] [un] gbrp6 | a=1:1 +bgr565 => [pa] [un] gbrp5 | a=1:1 [round-down] +bgr565 => [pa] [un] gbrp | a=1:1 [expand-8bit] [tu] [tp] +bgr565 => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +bgr565be => [pa] [un] gbrp6 | a=1:1 +bgr565be => [pa] [un] gbrp5 | a=1:1 [round-down] +bgr565be => [pa] [un] gbrp | a=1:1 [expand-8bit] [tu] [tp] +bgr565be => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +bgr8 => [pa] [un] gbrp3 | a=1:1 +bgr8 => [pa] [un] gbrp2 | a=1:1 [round-down] +bgr8 => [pa] [un] gbrp | a=1:1 [expand-8bit] [tu] [tp] +bgr8 => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +bgra => [pa] [un] gbrap | a=1:1 [tu] [tp] +bgra => [pa] [un] gbrapf32 | a=1:1 [planar-f32] +bgra64 => [pa] [un] gbrap16 | a=1:1 [tu] [tp] +bgra64 => [pa] [un] gbrapf32 | a=1:1 [planar-f32] +bgra64be => [pa] [un] gbrap16 | a=1:1 [tu] [tp] +bgra64be => [pa] [un] gbrapf32 | a=1:1 [planar-f32] +cuda => no +d3d11 => no +d3d11va_vld => no +d3d12 => no +drm_prime => no +dxva2_vld => no +gbrap => [pa] [un] gbrapf32 | a=1:1 [planar-f32] +gbrap10 => [pa] [un] gbrapf32 | a=1:1 [planar-f32] +gbrap10be => [pa] [un] gbrap10 | a=1:1 +gbrap10be => [pa] [un] gbrapf32 | a=1:1 [planar-f32] +gbrap12 => [pa] [un] gbrapf32 | a=1:1 [planar-f32] +gbrap12be => [pa] [un] gbrap12 | a=1:1 +gbrap12be => [pa] [un] gbrapf32 | a=1:1 [planar-f32] +gbrap14 => [pa] [un] gbrapf32 | a=1:1 [planar-f32] +gbrap14be => [pa] [un] gbrap14 | a=1:1 +gbrap14be => [pa] [un] gbrapf32 | a=1:1 [planar-f32] +gbrap16 => [pa] [un] gbrapf32 | a=1:1 [planar-f32] +gbrap16be => [pa] [un] gbrap16 | a=1:1 +gbrap16be => [pa] [un] gbrapf32 | a=1:1 [planar-f32] +gbrapf32be => [pa] [un] gbrapf32 | a=1:1 +gbrp => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +gbrp1 => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +gbrp10 => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +gbrp10be => [pa] [un] gbrp10 | a=1:1 +gbrp10be => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +gbrp12 => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +gbrp12be => [pa] [un] gbrp12 | a=1:1 +gbrp12be => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +gbrp14 => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +gbrp14be => [pa] [un] gbrp14 | a=1:1 +gbrp14be => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +gbrp16 => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +gbrp16be => [pa] [un] gbrp16 | a=1:1 +gbrp16be => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +gbrp2 => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +gbrp3 => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +gbrp4 => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +gbrp5 => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +gbrp6 => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +gbrp9 => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +gbrp9be => [pa] [un] gbrp9 | a=1:1 +gbrp9be => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +gbrpf32be => [pa] [un] gbrpf32 | a=1:1 +gray => [pa] [un] grayf32 | a=1:1 [planar-f32] +gray10 => [pa] [un] grayf32 | a=1:1 [planar-f32] +gray10be => [pa] [un] gray10 | a=1:1 +gray10be => [pa] [un] grayf32 | a=1:1 [planar-f32] +gray12 => [pa] [un] grayf32 | a=1:1 [planar-f32] +gray12be => [pa] [un] gray12 | a=1:1 +gray12be => [pa] [un] grayf32 | a=1:1 [planar-f32] +gray14 => [pa] [un] grayf32 | a=1:1 [planar-f32] +gray14be => [pa] [un] gray14 | a=1:1 +gray14be => [pa] [un] grayf32 | a=1:1 [planar-f32] +gray16 => [pa] [un] grayf32 | a=1:1 [planar-f32] +gray16be => [pa] [un] gray16 | a=1:1 +gray16be => [pa] [un] grayf32 | a=1:1 [planar-f32] +gray9 => [pa] [un] grayf32 | a=1:1 [planar-f32] +gray9be => [pa] [un] gray9 | a=1:1 +gray9be => [pa] [un] grayf32 | a=1:1 [planar-f32] +grayf32be => [pa] [un] grayf32 | a=1:1 +mediacodec => no +mmal => no +monob => [pa] [un] y1 | a=8:1 [tu] [tp] +monob => [pa] [un] gray | a=8:1 [expand-8bit] +monow => [pa] [un] y1 | a=8:1 [tu] [tp] +monow => [pa] [un] gray | a=8:1 [expand-8bit] +nv12 => [pa] [un] yuv420p | a=2:2 [tu] [tp] +nv12 => [pa] [un] yuv420pf | a=2:2 [planar-f32] +nv16 => [pa] [un] yuv422p | a=2:1 +nv16 => [pa] [un] yuv422pf | a=2:1 [planar-f32] +nv20 => [pa] [un] yuv422p10 | a=2:1 +nv20 => [pa] [un] yuv422pf | a=2:1 [planar-f32] +nv20be => [pa] [un] yuv422p10 | a=2:1 +nv20be => [pa] [un] yuv422pf | a=2:1 [planar-f32] +nv21 => [pa] [un] yuv420p | a=2:2 [tu] [tp] +nv21 => [pa] [un] yuv420pf | a=2:2 [planar-f32] +nv24 => [pa] [un] yuv444p | a=1:1 +nv24 => [pa] [un] yuv444pf | a=1:1 [planar-f32] +nv42 => [pa] [un] yuv444p | a=1:1 +nv42 => [pa] [un] yuv444pf | a=1:1 [planar-f32] +opencl => no +p010 => [pa] [un] yuv420p16 | a=2:2 +p010 => [pa] [un] yuv420pf | a=2:2 [planar-f32] +p010be => [pa] [un] yuv420p16 | a=2:2 +p010be => [pa] [un] yuv420pf | a=2:2 [planar-f32] +p012 => [pa] [un] yuv420p16 | a=2:2 +p012 => [pa] [un] yuv420pf | a=2:2 [planar-f32] +p012be => [pa] [un] yuv420p16 | a=2:2 +p012be => [pa] [un] yuv420pf | a=2:2 [planar-f32] +p016 => [pa] [un] yuv420p16 | a=2:2 +p016 => [pa] [un] yuv420pf | a=2:2 [planar-f32] +p016be => [pa] [un] yuv420p16 | a=2:2 +p016be => [pa] [un] yuv420pf | a=2:2 [planar-f32] +p210 => [pa] [un] yuv422p16 | a=2:1 +p210 => [pa] [un] yuv422pf | a=2:1 [planar-f32] +p210be => [pa] [un] yuv422p16 | a=2:1 +p210be => [pa] [un] yuv422pf | a=2:1 [planar-f32] +p212 => [pa] [un] yuv422p16 | a=2:1 +p212 => [pa] [un] yuv422pf | a=2:1 [planar-f32] +p212be => [pa] [un] yuv422p16 | a=2:1 +p212be => [pa] [un] yuv422pf | a=2:1 [planar-f32] +p216 => [pa] [un] yuv422p16 | a=2:1 +p216 => [pa] [un] yuv422pf | a=2:1 [planar-f32] +p216be => [pa] [un] yuv422p16 | a=2:1 +p216be => [pa] [un] yuv422pf | a=2:1 [planar-f32] +p410 => [pa] [un] yuv444p16 | a=1:1 +p410 => [pa] [un] yuv444pf | a=1:1 [planar-f32] +p410be => [pa] [un] yuv444p16 | a=1:1 +p410be => [pa] [un] yuv444pf | a=1:1 [planar-f32] +p412 => [pa] [un] yuv444p16 | a=1:1 +p412 => [pa] [un] yuv444pf | a=1:1 [planar-f32] +p412be => [pa] [un] yuv444p16 | a=1:1 +p412be => [pa] [un] yuv444pf | a=1:1 [planar-f32] +p416 => [pa] [un] yuv444p16 | a=1:1 +p416 => [pa] [un] yuv444pf | a=1:1 [planar-f32] +p416be => [pa] [un] yuv444p16 | a=1:1 +p416be => [pa] [un] yuv444pf | a=1:1 [planar-f32] +pal8 => [un] gbrap | a=1:1 +pal8 => [un] gbrapf32 | a=1:1 [planar-f32] +qsv => no +rgb0 => [pa] [un] gbrp | a=1:1 [tu] [tp] +rgb0 => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +rgb24 => [pa] [un] gbrp | a=1:1 [tu] [tp] +rgb24 => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +rgb30 => [pa] [un] gbrp10 | a=1:1 [tu] [tp] +rgb30 => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +rgb4 => no +rgb444 => [pa] [un] gbrp4 | a=1:1 +rgb444 => [pa] [un] gbrp | a=1:1 [expand-8bit] [tu] [tp] +rgb444 => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +rgb444be => [pa] [un] gbrp4 | a=1:1 +rgb444be => [pa] [un] gbrp | a=1:1 [expand-8bit] [tu] [tp] +rgb444be => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +rgb48 => [pa] [un] gbrp16 | a=1:1 [tu] [tp] +rgb48 => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +rgb48be => [pa] [un] gbrp16 | a=1:1 [tu] [tp] +rgb48be => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +rgb4_byte => [pa] [un] gbrp2 | a=1:1 +rgb4_byte => [pa] [un] gbrp1 | a=1:1 [round-down] +rgb4_byte => [pa] [un] gbrp | a=1:1 [expand-8bit] [tu] [tp] +rgb4_byte => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +rgb555 => [pa] [un] gbrp5 | a=1:1 +rgb555 => [pa] [un] gbrp | a=1:1 [expand-8bit] [tu] [tp] +rgb555 => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +rgb555be => [pa] [un] gbrp5 | a=1:1 +rgb555be => [pa] [un] gbrp | a=1:1 [expand-8bit] [tu] [tp] +rgb555be => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +rgb565 => [pa] [un] gbrp6 | a=1:1 +rgb565 => [pa] [un] gbrp5 | a=1:1 [round-down] +rgb565 => [pa] [un] gbrp | a=1:1 [expand-8bit] [tu] [tp] +rgb565 => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +rgb565be => [pa] [un] gbrp6 | a=1:1 +rgb565be => [pa] [un] gbrp5 | a=1:1 [round-down] +rgb565be => [pa] [un] gbrp | a=1:1 [expand-8bit] [tu] [tp] +rgb565be => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +rgb8 => [pa] [un] gbrp3 | a=1:1 +rgb8 => [pa] [un] gbrp2 | a=1:1 [round-down] +rgb8 => [pa] [un] gbrp | a=1:1 [expand-8bit] [tu] [tp] +rgb8 => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +rgba => [pa] [un] gbrap | a=1:1 [tu] [tp] +rgba => [pa] [un] gbrapf32 | a=1:1 [planar-f32] +rgba64 => [pa] [un] gbrap16 | a=1:1 [tu] [tp] +rgba64 => [pa] [un] gbrapf32 | a=1:1 [planar-f32] +rgba64be => [pa] [un] gbrap16 | a=1:1 [tu] [tp] +rgba64be => [pa] [un] gbrapf32 | a=1:1 [planar-f32] +rgbaf16 => no +rgbaf16be => no +rgbaf32 => no +rgbaf32be => no +rgbf32 => no +rgbf32be => no +uyvy422 => [pa] [un] yuv422p | a=2:1 [tu] [tp] +uyvy422 => [pa] [un] yuv422pf | a=2:1 [planar-f32] +uyyvyy411 => [pa] [un] yuv411p | a=4:1 [tu] [tp] +uyyvyy411 => [pa] [un] yuv411pf | a=4:1 [planar-f32] +vaapi => no +vdpau => no +vdpau_output => no +videotoolbox => no +vulkan => no +vuya => [pa] [un] yuva444p | a=1:1 +vuya => [pa] [un] yuva444pf | a=1:1 [planar-f32] +vuyx => [pa] [un] yuv444p | a=1:1 +vuyx => [pa] [un] yuv444pf | a=1:1 [planar-f32] +x2bgr10 => [pa] [un] gbrp10 | a=1:1 +x2bgr10 => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +x2bgr10be => [pa] [un] gbrp10 | a=1:1 +x2bgr10be => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +x2rgb10be => [pa] [un] gbrp10 | a=1:1 [tu] [tp] +x2rgb10be => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +xv30 => [pa] [un] yuv444p10 | a=1:1 +xv30 => [pa] [un] yuv444pf | a=1:1 [planar-f32] +xv30be => [pa] [un] yuv444p10 | a=1:1 +xv30be => [pa] [un] yuv444pf | a=1:1 [planar-f32] +xv36 => [pa] [un] yuv444p16 | a=1:1 +xv36 => [pa] [un] yuv444pf | a=1:1 [planar-f32] +xv36be => [pa] [un] yuv444p16 | a=1:1 +xv36be => [pa] [un] yuv444pf | a=1:1 [planar-f32] +xyz12 => [pa] [un] gbrp16 | a=1:1 [tu] [tp] +xyz12 => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +xyz12be => [pa] [un] gbrp16 | a=1:1 [tu] [tp] +xyz12be => [pa] [un] gbrpf32 | a=1:1 [planar-f32] +y210 => [pa] [un] yuv422p16 | a=2:1 [tu] [tp] +y210 => [pa] [un] yuv422pf | a=2:1 [planar-f32] +y210be => [pa] [un] yuv422p16 | a=2:1 [tu] [tp] +y210be => [pa] [un] yuv422pf | a=2:1 [planar-f32] +y212 => [pa] [un] yuv422p16 | a=2:1 +y212 => [pa] [un] yuv422pf | a=2:1 [planar-f32] +y212be => [pa] [un] yuv422p16 | a=2:1 +y212be => [pa] [un] yuv422pf | a=2:1 [planar-f32] +ya16 => [pa] [un] yap16 | a=1:1 [tu] [tp] +ya16 => [pa] [un] grayaf32 | a=1:1 [planar-f32] +ya16be => [pa] [un] yap16 | a=1:1 +ya16be => [pa] [un] grayaf32 | a=1:1 [planar-f32] +ya8 => [pa] [un] yap8 | a=1:1 [tu] [tp] +ya8 => [pa] [un] grayaf32 | a=1:1 [planar-f32] +yap16 => [pa] [un] grayaf32 | a=1:1 [planar-f32] +yap8 => [pa] [un] grayaf32 | a=1:1 [planar-f32] +yuv410p => [pa] [un] yuv410pf | a=4:4 [planar-f32] +yuv411p => [pa] [un] yuv411pf | a=4:1 [planar-f32] +yuv420p => [pa] [un] yuv420pf | a=2:2 [planar-f32] +yuv420p10 => [pa] [un] yuv420pf | a=2:2 [planar-f32] +yuv420p10be => [pa] [un] yuv420p10 | a=2:2 +yuv420p10be => [pa] [un] yuv420pf | a=2:2 [planar-f32] +yuv420p12 => [pa] [un] yuv420pf | a=2:2 [planar-f32] +yuv420p12be => [pa] [un] yuv420p12 | a=2:2 +yuv420p12be => [pa] [un] yuv420pf | a=2:2 [planar-f32] +yuv420p14 => [pa] [un] yuv420pf | a=2:2 [planar-f32] +yuv420p14be => [pa] [un] yuv420p14 | a=2:2 +yuv420p14be => [pa] [un] yuv420pf | a=2:2 [planar-f32] +yuv420p16 => [pa] [un] yuv420pf | a=2:2 [planar-f32] +yuv420p16be => [pa] [un] yuv420p16 | a=2:2 +yuv420p16be => [pa] [un] yuv420pf | a=2:2 [planar-f32] +yuv420p9 => [pa] [un] yuv420pf | a=2:2 [planar-f32] +yuv420p9be => [pa] [un] yuv420p9 | a=2:2 +yuv420p9be => [pa] [un] yuv420pf | a=2:2 [planar-f32] +yuv422p => [pa] [un] yuv422pf | a=2:1 [planar-f32] +yuv422p10 => [pa] [un] yuv422pf | a=2:1 [planar-f32] +yuv422p10be => [pa] [un] yuv422p10 | a=2:1 +yuv422p10be => [pa] [un] yuv422pf | a=2:1 [planar-f32] +yuv422p12 => [pa] [un] yuv422pf | a=2:1 [planar-f32] +yuv422p12be => [pa] [un] yuv422p12 | a=2:1 +yuv422p12be => [pa] [un] yuv422pf | a=2:1 [planar-f32] +yuv422p14 => [pa] [un] yuv422pf | a=2:1 [planar-f32] +yuv422p14be => [pa] [un] yuv422p14 | a=2:1 +yuv422p14be => [pa] [un] yuv422pf | a=2:1 [planar-f32] +yuv422p16 => [pa] [un] yuv422pf | a=2:1 [planar-f32] +yuv422p16be => [pa] [un] yuv422p16 | a=2:1 [tu] [tp] +yuv422p16be => [pa] [un] yuv422pf | a=2:1 [planar-f32] +yuv422p9 => [pa] [un] yuv422pf | a=2:1 [planar-f32] +yuv422p9be => [pa] [un] yuv422p9 | a=2:1 +yuv422p9be => [pa] [un] yuv422pf | a=2:1 [planar-f32] +yuv440p => [pa] [un] yuv440pf | a=1:2 [planar-f32] +yuv440p10 => [pa] [un] yuv440pf | a=1:2 [planar-f32] +yuv440p10be => [pa] [un] yuv440p10 | a=1:2 +yuv440p10be => [pa] [un] yuv440pf | a=1:2 [planar-f32] +yuv440p12 => [pa] [un] yuv440pf | a=1:2 [planar-f32] +yuv440p12be => [pa] [un] yuv440p12 | a=1:2 +yuv440p12be => [pa] [un] yuv440pf | a=1:2 [planar-f32] +yuv444p => [pa] [un] yuv444pf | a=1:1 [planar-f32] +yuv444p10 => [pa] [un] yuv444pf | a=1:1 [planar-f32] +yuv444p10be => [pa] [un] yuv444p10 | a=1:1 +yuv444p10be => [pa] [un] yuv444pf | a=1:1 [planar-f32] +yuv444p12 => [pa] [un] yuv444pf | a=1:1 [planar-f32] +yuv444p12be => [pa] [un] yuv444p12 | a=1:1 +yuv444p12be => [pa] [un] yuv444pf | a=1:1 [planar-f32] +yuv444p14 => [pa] [un] yuv444pf | a=1:1 [planar-f32] +yuv444p14be => [pa] [un] yuv444p14 | a=1:1 +yuv444p14be => [pa] [un] yuv444pf | a=1:1 [planar-f32] +yuv444p16 => [pa] [un] yuv444pf | a=1:1 [planar-f32] +yuv444p16be => [pa] [un] yuv444p16 | a=1:1 +yuv444p16be => [pa] [un] yuv444pf | a=1:1 [planar-f32] +yuv444p9 => [pa] [un] yuv444pf | a=1:1 [planar-f32] +yuv444p9be => [pa] [un] yuv444p9 | a=1:1 +yuv444p9be => [pa] [un] yuv444pf | a=1:1 [planar-f32] +yuva420p => [pa] [un] yuva420pf | a=2:2 [planar-f32] +yuva420p10 => [pa] [un] yuva420pf | a=2:2 [planar-f32] +yuva420p10be => [pa] [un] yuva420p10 | a=2:2 +yuva420p10be => [pa] [un] yuva420pf | a=2:2 [planar-f32] +yuva420p16 => [pa] [un] yuva420pf | a=2:2 [planar-f32] +yuva420p16be => [pa] [un] yuva420p16 | a=2:2 +yuva420p16be => [pa] [un] yuva420pf | a=2:2 [planar-f32] +yuva420p9 => [pa] [un] yuva420pf | a=2:2 [planar-f32] +yuva420p9be => [pa] [un] yuva420p9 | a=2:2 +yuva420p9be => [pa] [un] yuva420pf | a=2:2 [planar-f32] +yuva422p => [pa] [un] yuva422pf | a=2:1 [planar-f32] +yuva422p10 => [pa] [un] yuva422pf | a=2:1 [planar-f32] +yuva422p10be => [pa] [un] yuva422p10 | a=2:1 +yuva422p10be => [pa] [un] yuva422pf | a=2:1 [planar-f32] +yuva422p12 => [pa] [un] yuva422pf | a=2:1 [planar-f32] +yuva422p12be => [pa] [un] yuva422p12 | a=2:1 +yuva422p12be => [pa] [un] yuva422pf | a=2:1 [planar-f32] +yuva422p16 => [pa] [un] yuva422pf | a=2:1 [planar-f32] +yuva422p16be => [pa] [un] yuva422p16 | a=2:1 +yuva422p16be => [pa] [un] yuva422pf | a=2:1 [planar-f32] +yuva422p9 => [pa] [un] yuva422pf | a=2:1 [planar-f32] +yuva422p9be => [pa] [un] yuva422p9 | a=2:1 +yuva422p9be => [pa] [un] yuva422pf | a=2:1 [planar-f32] +yuva444p => [pa] [un] yuva444pf | a=1:1 [planar-f32] +yuva444p10 => [pa] [un] yuva444pf | a=1:1 [planar-f32] +yuva444p10be => [pa] [un] yuva444p10 | a=1:1 +yuva444p10be => [pa] [un] yuva444pf | a=1:1 [planar-f32] +yuva444p12 => [pa] [un] yuva444pf | a=1:1 [planar-f32] +yuva444p12be => [pa] [un] yuva444p12 | a=1:1 +yuva444p12be => [pa] [un] yuva444pf | a=1:1 [planar-f32] +yuva444p16 => [pa] [un] yuva444pf | a=1:1 [planar-f32] +yuva444p16be => [pa] [un] yuva444p16 | a=1:1 +yuva444p16be => [pa] [un] yuva444pf | a=1:1 [planar-f32] +yuva444p9 => [pa] [un] yuva444pf | a=1:1 [planar-f32] +yuva444p9be => [pa] [un] yuva444p9 | a=1:1 +yuva444p9be => [pa] [un] yuva444pf | a=1:1 [planar-f32] +yuvj411p => [pa] [un] yuv411pf | a=4:1 [planar-f32] +yuvj422p => [pa] [un] yuv422pf | a=2:1 [planar-f32] +yuvj440p => [pa] [un] yuv440pf | a=1:2 [planar-f32] +yuyv422 => [pa] [un] yuv422p | a=2:1 [tu] [tp] +yuyv422 => [pa] [un] yuv422pf | a=2:1 [planar-f32] +yvyu422 => [pa] [un] yuv422p | a=2:1 [tu] [tp] +yvyu422 => [pa] [un] yuv422pf | a=2:1 [planar-f32] diff --git a/test/ref/ffmpeg7/zimg_formats.txt b/test/ref/ffmpeg7/zimg_formats.txt new file mode 100644 index 0000000..f886abe --- /dev/null +++ b/test/ref/ffmpeg7/zimg_formats.txt @@ -0,0 +1,249 @@ + 0bgr Zin Zout SWSin SWSout | + 0rgb Zin Zout SWSin SWSout | + abgr Zin Zout SWSin SWSout | + argb Zin Zout SWSin SWSout | + ayuv64 Zin Zout SWSin SWSout | + ayuv64be Zin Zout | + bayer_bggr16 SWSin | + bayer_bggr16be SWSin | + bayer_bggr8 SWSin | + bayer_gbrg16 SWSin | + bayer_gbrg16be SWSin | + bayer_gbrg8 SWSin | + bayer_grbg16 SWSin | + bayer_grbg16be SWSin | + bayer_grbg8 SWSin | + bayer_rggb16 SWSin | + bayer_rggb16be SWSin | + bayer_rggb8 SWSin | + bgr0 Zin Zout SWSin SWSout | + bgr24 Zin Zout SWSin SWSout | + bgr4 SWSout | + bgr444 Zin Zout SWSin SWSout | + bgr444be Zin Zout SWSin SWSout | + bgr48 Zin Zout SWSin SWSout | + bgr48be Zin Zout SWSin SWSout | + bgr4_byte Zin Zout SWSin SWSout | + bgr555 Zin Zout SWSin SWSout | + bgr555be Zin Zout SWSin SWSout | + bgr565 Zin Zout SWSin SWSout | + bgr565be Zin Zout SWSin SWSout | + bgr8 Zin Zout SWSin SWSout | + bgra Zin Zout SWSin SWSout | + bgra64 Zin Zout SWSin SWSout | + bgra64be Zin Zout SWSin SWSout | + cuda | + d3d11 | + d3d11va_vld | + d3d12 | + drm_prime | + dxva2_vld | + gbrap Zin Zout SWSin SWSout | + gbrap10 Zin Zout SWSin SWSout | + gbrap10be Zin Zout SWSin SWSout | + gbrap12 Zin Zout SWSin SWSout | + gbrap12be Zin Zout SWSin SWSout | + gbrap14 Zin Zout SWSin SWSout | + gbrap14be Zin Zout SWSin SWSout | + gbrap16 Zin Zout SWSin SWSout | + gbrap16be Zin Zout SWSin SWSout | + gbrapf32 Zin Zout SWSin SWSout | + gbrapf32be Zin Zout SWSin SWSout | + gbrp Zin Zout SWSin SWSout | + gbrp1 Zin Zout | + gbrp10 Zin Zout SWSin SWSout | + gbrp10be Zin Zout SWSin SWSout | + gbrp12 Zin Zout SWSin SWSout | + gbrp12be Zin Zout SWSin SWSout | + gbrp14 Zin Zout SWSin SWSout | + gbrp14be Zin Zout SWSin SWSout | + gbrp16 Zin Zout SWSin SWSout | + gbrp16be Zin Zout SWSin SWSout | + gbrp2 Zin Zout | + gbrp3 Zin Zout | + gbrp4 Zin Zout | + gbrp5 Zin Zout | + gbrp6 Zin Zout | + gbrp9 Zin Zout SWSin SWSout | + gbrp9be Zin Zout SWSin SWSout | + gbrpf32 Zin Zout SWSin SWSout | + gbrpf32be Zin Zout SWSin SWSout | + gray Zin Zout SWSin SWSout | + gray10 Zin Zout SWSin SWSout | + gray10be Zin Zout SWSin SWSout | + gray12 Zin Zout SWSin SWSout | + gray12be Zin Zout SWSin SWSout | + gray14 Zin Zout SWSin SWSout | + gray14be Zin Zout SWSin SWSout | + gray16 Zin Zout SWSin SWSout | + gray16be Zin Zout SWSin SWSout | + gray9 Zin Zout SWSin SWSout | + gray9be Zin Zout SWSin SWSout | + grayaf32 Zin Zout | + grayf32 Zin Zout SWSin SWSout | + grayf32be Zin Zout SWSin SWSout | + mediacodec | + mmal | + monob Zin Zout SWSin SWSout | + monow Zin Zout SWSin SWSout | + nv12 Zin Zout SWSin SWSout | + nv16 Zin Zout SWSin SWSout | + nv20 Zin Zout | + nv20be Zin Zout | + nv21 Zin Zout SWSin SWSout | + nv24 Zin Zout SWSin SWSout | + nv42 Zin Zout SWSin SWSout | + opencl | + p010 Zin Zout SWSin SWSout | + p010be Zin Zout SWSin SWSout | + p012 Zin Zout SWSin SWSout | + p012be Zin Zout SWSin SWSout | + p016 Zin Zout SWSin SWSout | + p016be Zin Zout SWSin SWSout | + p210 Zin Zout SWSin SWSout | + p210be Zin Zout SWSin SWSout | + p212 Zin Zout SWSin SWSout | + p212be Zin Zout SWSin SWSout | + p216 Zin Zout SWSin SWSout | + p216be Zin Zout SWSin SWSout | + p410 Zin Zout SWSin SWSout | + p410be Zin Zout SWSin SWSout | + p412 Zin Zout SWSin SWSout | + p412be Zin Zout SWSin SWSout | + p416 Zin Zout SWSin SWSout | + p416be Zin Zout SWSin SWSout | + pal8 Zin SWSin | + qsv | + rgb0 Zin Zout SWSin SWSout | + rgb24 Zin Zout SWSin SWSout | + rgb30 Zin Zout SWSin SWSout | + rgb4 SWSout | + rgb444 Zin Zout SWSin SWSout | + rgb444be Zin Zout SWSin SWSout | + rgb48 Zin Zout SWSin SWSout | + rgb48be Zin Zout SWSin SWSout | + rgb4_byte Zin Zout SWSin SWSout | + rgb555 Zin Zout SWSin SWSout | + rgb555be Zin Zout SWSin SWSout | + rgb565 Zin Zout SWSin SWSout | + rgb565be Zin Zout SWSin SWSout | + rgb8 Zin Zout SWSin SWSout | + rgba Zin Zout SWSin SWSout | + rgba64 Zin Zout SWSin SWSout | + rgba64be Zin Zout SWSin SWSout | + rgbaf16 SWSin | + rgbaf16be SWSin | + rgbaf32 | + rgbaf32be | + rgbf32 | + rgbf32be | + uyvy422 Zin Zout SWSin SWSout | + uyyvyy411 Zin Zout | + vaapi | + vdpau | + vdpau_output | + videotoolbox | + vulkan | + vuya Zin Zout SWSin SWSout | + vuyx Zin Zout SWSin SWSout | + x2bgr10 Zin Zout SWSin SWSout | + x2bgr10be Zin Zout | + x2rgb10be Zin Zout | + xv30 Zin Zout SWSin SWSout | + xv30be Zin Zout | + xv36 Zin Zout SWSin SWSout | + xv36be Zin Zout | + xyz12 Zin Zout SWSin SWSout | + xyz12be Zin Zout SWSin SWSout | + y1 Zin Zout | + y210 Zin Zout SWSin SWSout | + y210be Zin Zout | + y212 Zin Zout SWSin SWSout | + y212be Zin Zout | + ya16 Zin Zout SWSin SWSout | + ya16be Zin Zout SWSin SWSout | + ya8 Zin Zout SWSin SWSout | + yap16 Zin Zout | + yap8 Zin Zout | + yuv410p Zin Zout SWSin SWSout | + yuv410pf Zin Zout | + yuv411p Zin Zout SWSin SWSout | + yuv411pf Zin Zout | + yuv420p Zin Zout SWSin SWSout | + yuv420p10 Zin Zout SWSin SWSout | + yuv420p10be Zin Zout SWSin SWSout | + yuv420p12 Zin Zout SWSin SWSout | + yuv420p12be Zin Zout SWSin SWSout | + yuv420p14 Zin Zout SWSin SWSout | + yuv420p14be Zin Zout SWSin SWSout | + yuv420p16 Zin Zout SWSin SWSout | + yuv420p16be Zin Zout SWSin SWSout | + yuv420p9 Zin Zout SWSin SWSout | + yuv420p9be Zin Zout SWSin SWSout | + yuv420pf Zin Zout | + yuv422p Zin Zout SWSin SWSout | + yuv422p10 Zin Zout SWSin SWSout | + yuv422p10be Zin Zout SWSin SWSout | + yuv422p12 Zin Zout SWSin SWSout | + yuv422p12be Zin Zout SWSin SWSout | + yuv422p14 Zin Zout SWSin SWSout | + yuv422p14be Zin Zout SWSin SWSout | + yuv422p16 Zin Zout SWSin SWSout | + yuv422p16be Zin Zout SWSin SWSout | + yuv422p9 Zin Zout SWSin SWSout | + yuv422p9be Zin Zout SWSin SWSout | + yuv422pf Zin Zout | + yuv440p Zin Zout SWSin SWSout | + yuv440p10 Zin Zout SWSin SWSout | + yuv440p10be Zin Zout SWSin SWSout | + yuv440p12 Zin Zout SWSin SWSout | + yuv440p12be Zin Zout SWSin SWSout | + yuv440pf Zin Zout | + yuv444p Zin Zout SWSin SWSout | + yuv444p10 Zin Zout SWSin SWSout | + yuv444p10be Zin Zout SWSin SWSout | + yuv444p12 Zin Zout SWSin SWSout | + yuv444p12be Zin Zout SWSin SWSout | + yuv444p14 Zin Zout SWSin SWSout | + yuv444p14be Zin Zout SWSin SWSout | + yuv444p16 Zin Zout SWSin SWSout | + yuv444p16be Zin Zout SWSin SWSout | + yuv444p9 Zin Zout SWSin SWSout | + yuv444p9be Zin Zout SWSin SWSout | + yuv444pf Zin Zout | + yuva410pf Zin Zout | + yuva411pf Zin Zout | + yuva420p Zin Zout SWSin SWSout | + yuva420p10 Zin Zout SWSin SWSout | + yuva420p10be Zin Zout SWSin SWSout | + yuva420p16 Zin Zout SWSin SWSout | + yuva420p16be Zin Zout SWSin SWSout | + yuva420p9 Zin Zout SWSin SWSout | + yuva420p9be Zin Zout SWSin SWSout | + yuva420pf Zin Zout | + yuva422p Zin Zout SWSin SWSout | + yuva422p10 Zin Zout SWSin SWSout | + yuva422p10be Zin Zout SWSin SWSout | + yuva422p12 Zin Zout SWSin SWSout | + yuva422p12be Zin Zout SWSin SWSout | + yuva422p16 Zin Zout SWSin SWSout | + yuva422p16be Zin Zout SWSin SWSout | + yuva422p9 Zin Zout SWSin SWSout | + yuva422p9be Zin Zout SWSin SWSout | + yuva422pf Zin Zout | + yuva440pf Zin Zout | + yuva444p Zin Zout SWSin SWSout | + yuva444p10 Zin Zout SWSin SWSout | + yuva444p10be Zin Zout SWSin SWSout | + yuva444p12 Zin Zout SWSin SWSout | + yuva444p12be Zin Zout SWSin SWSout | + yuva444p16 Zin Zout SWSin SWSout | + yuva444p16be Zin Zout SWSin SWSout | + yuva444p9 Zin Zout SWSin SWSout | + yuva444p9be Zin Zout SWSin SWSout | + yuva444pf Zin Zout | + yuvj411p Zin Zout SWSin SWSout | + yuvj422p Zin Zout SWSin SWSout | + yuvj440p Zin Zout SWSin SWSout | + yuyv422 Zin Zout SWSin SWSout | + yvyu422 Zin Zout SWSin SWSout | diff --git a/test/repack.c b/test/repack.c index a37559b..c6ec506 100644 --- a/test/repack.c +++ b/test/repack.c @@ -326,8 +326,8 @@ static int try_repack(FILE *f, int imgfmt, int flags, int not_if_fmt) return b; } -static void check_float_repack(int imgfmt, enum mp_csp csp, - enum mp_csp_levels levels) +static void check_float_repack(int imgfmt, enum pl_color_system csp, + enum pl_color_levels levels) { imgfmt = UNFUCK(imgfmt); @@ -349,12 +349,12 @@ static void check_float_repack(int imgfmt, enum mp_csp csp, struct mp_image *src = mp_image_alloc(imgfmt, w, 1); assert(src); - src->params.color.space = csp; - src->params.color.levels = levels; + src->params.repr.sys = csp; + src->params.repr.levels = levels; mp_image_params_guess_csp(&src->params); // mpv may not allow all combinations - assert(src->params.color.space == csp); - assert(src->params.color.levels == levels); + assert(src->params.repr.sys == csp); + assert(src->params.repr.levels == levels); for (int p = 0; p < src->num_planes; p++) { int val = 0; @@ -384,6 +384,8 @@ static void check_float_repack(int imgfmt, enum mp_csp csp, z_f->params.color = r_f->params.color = z_i->params.color = r_i->params.color = src->params.color; + z_f->params.repr = r_f->params.repr = z_i->params.repr = + r_i->params.repr = src->params.repr; // The idea is to use zimg to cross-check conversion. struct mp_sws_context *s = mp_sws_alloc(NULL); @@ -503,15 +505,15 @@ int main(int argc, char *argv[]) assert_text_files_equal(refdir, outdir, "repack.txt", "This can fail if FFmpeg/libswscale adds or removes pixfmts."); - check_float_repack(-AV_PIX_FMT_GBRAP, MP_CSP_RGB, MP_CSP_LEVELS_PC); - check_float_repack(-AV_PIX_FMT_GBRAP10, MP_CSP_RGB, MP_CSP_LEVELS_PC); - check_float_repack(-AV_PIX_FMT_GBRAP16, MP_CSP_RGB, MP_CSP_LEVELS_PC); - check_float_repack(-AV_PIX_FMT_YUVA444P, MP_CSP_BT_709, MP_CSP_LEVELS_PC); - check_float_repack(-AV_PIX_FMT_YUVA444P, MP_CSP_BT_709, MP_CSP_LEVELS_TV); - check_float_repack(-AV_PIX_FMT_YUVA444P10, MP_CSP_BT_709, MP_CSP_LEVELS_PC); - check_float_repack(-AV_PIX_FMT_YUVA444P10, MP_CSP_BT_709, MP_CSP_LEVELS_TV); - check_float_repack(-AV_PIX_FMT_YUVA444P16, MP_CSP_BT_709, MP_CSP_LEVELS_PC); - check_float_repack(-AV_PIX_FMT_YUVA444P16, MP_CSP_BT_709, MP_CSP_LEVELS_TV); + check_float_repack(-AV_PIX_FMT_GBRAP, PL_COLOR_SYSTEM_RGB, PL_COLOR_LEVELS_FULL); + check_float_repack(-AV_PIX_FMT_GBRAP10, PL_COLOR_SYSTEM_RGB, PL_COLOR_LEVELS_FULL); + check_float_repack(-AV_PIX_FMT_GBRAP16, PL_COLOR_SYSTEM_RGB, PL_COLOR_LEVELS_FULL); + check_float_repack(-AV_PIX_FMT_YUVA444P, PL_COLOR_SYSTEM_BT_709, PL_COLOR_LEVELS_FULL); + check_float_repack(-AV_PIX_FMT_YUVA444P, PL_COLOR_SYSTEM_BT_709, PL_COLOR_LEVELS_LIMITED); + check_float_repack(-AV_PIX_FMT_YUVA444P10, PL_COLOR_SYSTEM_BT_709, PL_COLOR_LEVELS_FULL); + check_float_repack(-AV_PIX_FMT_YUVA444P10, PL_COLOR_SYSTEM_BT_709, PL_COLOR_LEVELS_LIMITED); + check_float_repack(-AV_PIX_FMT_YUVA444P16, PL_COLOR_SYSTEM_BT_709, PL_COLOR_LEVELS_FULL); + check_float_repack(-AV_PIX_FMT_YUVA444P16, PL_COLOR_SYSTEM_BT_709, PL_COLOR_LEVELS_LIMITED); // Determine the list of possible draw_bmp input formats. Do this here // because it mostly depends on repack and imgformat stuff. diff --git a/test/scale_test.c b/test/scale_test.c index f919dca..6e01271 100644 --- a/test/scale_test.c +++ b/test/scale_test.c @@ -10,7 +10,7 @@ static struct mp_image *gen_repack_test_img(int w, int h, int bytes, bool rgb, struct mp_regular_imgfmt planar_desc = { .component_type = MP_COMPONENT_TYPE_UINT, .component_size = bytes, - .forced_csp = rgb ? MP_CSP_RGB : 0, + .forced_csp = rgb ? PL_COLOR_SYSTEM_RGB : 0, .num_planes = alpha ? 4 : 3, .planes = { {1, {rgb ? 2 : 1}}, @@ -60,7 +60,7 @@ static void dump_image(struct scale_test *stest, const char *name, struct image_writer_opts opts = image_writer_opts_defaults; opts.format = AV_CODEC_ID_PNG; - if (!write_image(img, &opts, path, NULL, NULL)) { + if (!write_image(img, &opts, path, NULL, NULL, true)) { printf("Failed to write '%s'.\n", path); abort(); } @@ -101,7 +101,7 @@ static void assert_imgs_equal(struct scale_test *stest, FILE *f, void repack_test_run(struct scale_test *stest) { - char *logname = mp_tprintf(80, "%s.log", stest->test_name); + char *logname = mp_tprintf(80, "../%s.log", stest->test_name); FILE *f = test_open_out(stest->outdir, logname); if (!stest->sws) { @@ -129,7 +129,7 @@ void repack_test_run(struct scale_test *stest) if (!mp_get_regular_imgfmt(&rdesc, ofmt)) continue; } - if (rdesc.num_planes > 1 || rdesc.forced_csp != MP_CSP_RGB) + if (rdesc.num_planes > 1 || rdesc.forced_csp != PL_COLOR_SYSTEM_RGB) continue; struct mp_image *test_img = NULL; diff --git a/test/test_utils.c b/test/test_utils.c index b80caf8..94fd2e6 100644 --- a/test/test_utils.c +++ b/test/test_utils.c @@ -4,6 +4,7 @@ #include "options/m_option.h" #include "options/path.h" #include "osdep/subprocess.h" +#include "osdep/terminal.h" #include "test_utils.h" #ifdef NDEBUG @@ -14,6 +15,7 @@ void assert_int_equal_impl(const char *file, int line, int64_t a, int64_t b) { if (a != b) { printf("%s:%d: %"PRId64" != %"PRId64"\n", file, line, a, b); + fflush(stdout); abort(); } } @@ -23,6 +25,7 @@ void assert_string_equal_impl(const char *file, int line, { if (strcmp(a, b) != 0) { printf("%s:%d: '%s' != '%s'\n", file, line, a, b); + fflush(stdout); abort(); } } @@ -32,6 +35,7 @@ void assert_float_equal_impl(const char *file, int line, { if (fabs(a - b) > tolerance) { printf("%s:%d: %f != %f\n", file, line, a, b); + fflush(stdout); abort(); } } @@ -45,6 +49,7 @@ FILE *test_open_out(const char *outdir, const char *name) if (!f) { printf("Could not open '%s' for writing: %s\n", path, mp_strerror(errno)); + fflush(stdout); abort(); } return f; @@ -72,6 +77,7 @@ void assert_text_files_equal_impl(const char *file, int line, if (res.error) printf("Note: %s\n", mp_subprocess_err_str(res.error)); printf("Giving up.\n"); + fflush(stdout); abort(); } } @@ -95,6 +101,7 @@ void assert_memcmp_impl(const char *file, int line, printf("%s:%d: mismatching data:\n", file, line); hexdump(a, size); hexdump(b, size); + fflush(stdout); abort(); } @@ -105,7 +112,10 @@ const char *mp_help_text; void mp_msg(struct mp_log *log, int lev, const char *format, ...) {}; int mp_msg_find_level(const char *s) {return 0;}; int mp_msg_level(struct mp_log *log) {return 0;}; -void mp_write_console_ansi(void) {}; +void mp_msg_set_max_level(struct mp_log *log, int lev) {}; +int mp_console_vfprintf(void *wstream, const char *format, va_list args) {return 0;}; +int mp_console_write(void *wstream, bstr str) {return 0;}; +bool mp_check_console(void *handle) { return false; }; void mp_set_avdict(AVDictionary **dict, char **kv) {}; struct mp_log *mp_log_new(void *talloc_ctx, struct mp_log *parent, const char *name) { return NULL; }; diff --git a/test/test_utils.h b/test/test_utils.h index 66615d3..7db358e 100644 --- a/test/test_utils.h +++ b/test/test_utils.h @@ -51,6 +51,6 @@ void mp_msg(struct mp_log *log, int lev, const char *format, ...) PRINTF_ATTRIBUTE(3, 4); int mp_msg_find_level(const char *s); int mp_msg_level(struct mp_log *log); -void mp_write_console_ansi(void); +void mp_msg_set_max_level(struct mp_log *log, int lev); typedef struct AVDictionary AVDictionary; void mp_set_avdict(AVDictionary **dict, char **kv); diff --git a/test/timer.c b/test/timer.c index f85009c..962760f 100644 --- a/test/timer.c +++ b/test/timer.c @@ -13,7 +13,7 @@ int main(void) /* timekeeping */ { int64_t now = mp_time_ns(); - assert_true(now > 0); + assert_true(now >= 0); mp_sleep_ns(MP_TIME_MS_TO_NS(10)); diff --git a/video/csputils.c b/video/csputils.c index 59200c5..555f0c0 100644 --- a/video/csputils.c +++ b/video/csputils.c @@ -3,8 +3,6 @@ * * Copyleft (C) 2009 Reimar Döffinger <Reimar.Doeffinger@gmx.de> * - * mp_invert_cmat based on DarkPlaces engine (relicensed from GPL to LGPL) - * * This file is part of mpv. * * mpv is free software; you can redistribute it and/or @@ -32,66 +30,69 @@ #include "options/m_config.h" #include "options/m_option.h" -const struct m_opt_choice_alternatives mp_csp_names[] = { - {"auto", MP_CSP_AUTO}, - {"bt.601", MP_CSP_BT_601}, - {"bt.709", MP_CSP_BT_709}, - {"smpte-240m", MP_CSP_SMPTE_240M}, - {"bt.2020-ncl", MP_CSP_BT_2020_NC}, - {"bt.2020-cl", MP_CSP_BT_2020_C}, - {"rgb", MP_CSP_RGB}, - {"xyz", MP_CSP_XYZ}, - {"ycgco", MP_CSP_YCGCO}, +const struct m_opt_choice_alternatives pl_csp_names[] = { + {"auto", PL_COLOR_SYSTEM_UNKNOWN}, + {"bt.601", PL_COLOR_SYSTEM_BT_601}, + {"bt.709", PL_COLOR_SYSTEM_BT_709}, + {"smpte-240m", PL_COLOR_SYSTEM_SMPTE_240M}, + {"bt.2020-ncl", PL_COLOR_SYSTEM_BT_2020_NC}, + {"bt.2020-cl", PL_COLOR_SYSTEM_BT_2020_C}, + {"bt.2100-pq", PL_COLOR_SYSTEM_BT_2100_PQ}, + {"bt.2100-hlg", PL_COLOR_SYSTEM_BT_2100_HLG}, + {"dolbyvision", PL_COLOR_SYSTEM_DOLBYVISION}, + {"rgb", PL_COLOR_SYSTEM_RGB}, + {"xyz", PL_COLOR_SYSTEM_XYZ}, + {"ycgco", PL_COLOR_SYSTEM_YCGCO}, {0} }; -const struct m_opt_choice_alternatives mp_csp_levels_names[] = { - {"auto", MP_CSP_LEVELS_AUTO}, - {"limited", MP_CSP_LEVELS_TV}, - {"full", MP_CSP_LEVELS_PC}, +const struct m_opt_choice_alternatives pl_csp_levels_names[] = { + {"auto", PL_COLOR_LEVELS_UNKNOWN}, + {"limited", PL_COLOR_LEVELS_LIMITED}, + {"full", PL_COLOR_LEVELS_FULL}, {0} }; -const struct m_opt_choice_alternatives mp_csp_prim_names[] = { - {"auto", MP_CSP_PRIM_AUTO}, - {"bt.601-525", MP_CSP_PRIM_BT_601_525}, - {"bt.601-625", MP_CSP_PRIM_BT_601_625}, - {"bt.709", MP_CSP_PRIM_BT_709}, - {"bt.2020", MP_CSP_PRIM_BT_2020}, - {"bt.470m", MP_CSP_PRIM_BT_470M}, - {"apple", MP_CSP_PRIM_APPLE}, - {"adobe", MP_CSP_PRIM_ADOBE}, - {"prophoto", MP_CSP_PRIM_PRO_PHOTO}, - {"cie1931", MP_CSP_PRIM_CIE_1931}, - {"dci-p3", MP_CSP_PRIM_DCI_P3}, - {"display-p3", MP_CSP_PRIM_DISPLAY_P3}, - {"v-gamut", MP_CSP_PRIM_V_GAMUT}, - {"s-gamut", MP_CSP_PRIM_S_GAMUT}, - {"ebu3213", MP_CSP_PRIM_EBU_3213}, - {"film-c", MP_CSP_PRIM_FILM_C}, - {"aces-ap0", MP_CSP_PRIM_ACES_AP0}, - {"aces-ap1", MP_CSP_PRIM_ACES_AP1}, +const struct m_opt_choice_alternatives pl_csp_prim_names[] = { + {"auto", PL_COLOR_PRIM_UNKNOWN}, + {"bt.601-525", PL_COLOR_PRIM_BT_601_525}, + {"bt.601-625", PL_COLOR_PRIM_BT_601_625}, + {"bt.709", PL_COLOR_PRIM_BT_709}, + {"bt.2020", PL_COLOR_PRIM_BT_2020}, + {"bt.470m", PL_COLOR_PRIM_BT_470M}, + {"apple", PL_COLOR_PRIM_APPLE}, + {"adobe", PL_COLOR_PRIM_ADOBE}, + {"prophoto", PL_COLOR_PRIM_PRO_PHOTO}, + {"cie1931", PL_COLOR_PRIM_CIE_1931}, + {"dci-p3", PL_COLOR_PRIM_DCI_P3}, + {"display-p3", PL_COLOR_PRIM_DISPLAY_P3}, + {"v-gamut", PL_COLOR_PRIM_V_GAMUT}, + {"s-gamut", PL_COLOR_PRIM_S_GAMUT}, + {"ebu3213", PL_COLOR_PRIM_EBU_3213}, + {"film-c", PL_COLOR_PRIM_FILM_C}, + {"aces-ap0", PL_COLOR_PRIM_ACES_AP0}, + {"aces-ap1", PL_COLOR_PRIM_ACES_AP1}, {0} }; -const struct m_opt_choice_alternatives mp_csp_trc_names[] = { - {"auto", MP_CSP_TRC_AUTO}, - {"bt.1886", MP_CSP_TRC_BT_1886}, - {"srgb", MP_CSP_TRC_SRGB}, - {"linear", MP_CSP_TRC_LINEAR}, - {"gamma1.8", MP_CSP_TRC_GAMMA18}, - {"gamma2.0", MP_CSP_TRC_GAMMA20}, - {"gamma2.2", MP_CSP_TRC_GAMMA22}, - {"gamma2.4", MP_CSP_TRC_GAMMA24}, - {"gamma2.6", MP_CSP_TRC_GAMMA26}, - {"gamma2.8", MP_CSP_TRC_GAMMA28}, - {"prophoto", MP_CSP_TRC_PRO_PHOTO}, - {"pq", MP_CSP_TRC_PQ}, - {"hlg", MP_CSP_TRC_HLG}, - {"v-log", MP_CSP_TRC_V_LOG}, - {"s-log1", MP_CSP_TRC_S_LOG1}, - {"s-log2", MP_CSP_TRC_S_LOG2}, - {"st428", MP_CSP_TRC_ST428}, +const struct m_opt_choice_alternatives pl_csp_trc_names[] = { + {"auto", PL_COLOR_TRC_UNKNOWN}, + {"bt.1886", PL_COLOR_TRC_BT_1886}, + {"srgb", PL_COLOR_TRC_SRGB}, + {"linear", PL_COLOR_TRC_LINEAR}, + {"gamma1.8", PL_COLOR_TRC_GAMMA18}, + {"gamma2.0", PL_COLOR_TRC_GAMMA20}, + {"gamma2.2", PL_COLOR_TRC_GAMMA22}, + {"gamma2.4", PL_COLOR_TRC_GAMMA24}, + {"gamma2.6", PL_COLOR_TRC_GAMMA26}, + {"gamma2.8", PL_COLOR_TRC_GAMMA28}, + {"prophoto", PL_COLOR_TRC_PRO_PHOTO}, + {"pq", PL_COLOR_TRC_PQ}, + {"hlg", PL_COLOR_TRC_HLG}, + {"v-log", PL_COLOR_TRC_V_LOG}, + {"s-log1", PL_COLOR_TRC_S_LOG1}, + {"s-log2", PL_COLOR_TRC_S_LOG2}, + {"st428", PL_COLOR_TRC_ST428}, {0} }; @@ -104,36 +105,24 @@ const struct m_opt_choice_alternatives mp_csp_light_names[] = { {0} }; -const struct m_opt_choice_alternatives mp_chroma_names[] = { - {"unknown", MP_CHROMA_AUTO}, - {"uhd", MP_CHROMA_TOPLEFT}, - {"mpeg2/4/h264",MP_CHROMA_LEFT}, - {"mpeg1/jpeg", MP_CHROMA_CENTER}, +const struct m_opt_choice_alternatives pl_chroma_names[] = { + {"unknown", PL_CHROMA_UNKNOWN}, + {"uhd", PL_CHROMA_TOP_LEFT}, + {"mpeg2/4/h264",PL_CHROMA_LEFT}, + {"mpeg1/jpeg", PL_CHROMA_CENTER}, + {"top", PL_CHROMA_TOP_CENTER}, + {"bottom left", PL_CHROMA_BOTTOM_LEFT}, + {"bottom", PL_CHROMA_BOTTOM_CENTER}, {0} }; -const struct m_opt_choice_alternatives mp_alpha_names[] = { - {"auto", MP_ALPHA_AUTO}, - {"straight", MP_ALPHA_STRAIGHT}, - {"premul", MP_ALPHA_PREMUL}, +const struct m_opt_choice_alternatives pl_alpha_names[] = { + {"auto", PL_ALPHA_UNKNOWN}, + {"straight", PL_ALPHA_INDEPENDENT}, + {"premul", PL_ALPHA_PREMULTIPLIED}, {0} }; -void mp_colorspace_merge(struct mp_colorspace *orig, struct mp_colorspace *new) -{ - if (!orig->space) - orig->space = new->space; - if (!orig->levels) - orig->levels = new->levels; - if (!orig->primaries) - orig->primaries = new->primaries; - if (!orig->gamma) - orig->gamma = new->gamma; - if (!orig->light) - orig->light = new->light; - pl_hdr_metadata_merge(&orig->hdr, &new->hdr); -} - // The short name _must_ match with what vf_stereo3d accepts (if supported). // The long name in comments is closer to the Matroska spec (StereoMode element). // The numeric index matches the Matroska StereoMode value. If you add entries @@ -158,428 +147,41 @@ const struct m_opt_choice_alternatives mp_stereo3d_names[] = { {0} }; -enum mp_csp avcol_spc_to_mp_csp(int avcolorspace) -{ - switch (avcolorspace) { - case AVCOL_SPC_BT709: return MP_CSP_BT_709; - case AVCOL_SPC_BT470BG: return MP_CSP_BT_601; - case AVCOL_SPC_BT2020_NCL: return MP_CSP_BT_2020_NC; - case AVCOL_SPC_BT2020_CL: return MP_CSP_BT_2020_C; - case AVCOL_SPC_SMPTE170M: return MP_CSP_BT_601; - case AVCOL_SPC_SMPTE240M: return MP_CSP_SMPTE_240M; - case AVCOL_SPC_RGB: return MP_CSP_RGB; - case AVCOL_SPC_YCOCG: return MP_CSP_YCGCO; - default: return MP_CSP_AUTO; - } -} - -enum mp_csp_levels avcol_range_to_mp_csp_levels(int avrange) -{ - switch (avrange) { - case AVCOL_RANGE_MPEG: return MP_CSP_LEVELS_TV; - case AVCOL_RANGE_JPEG: return MP_CSP_LEVELS_PC; - default: return MP_CSP_LEVELS_AUTO; - } -} - -enum mp_csp_prim avcol_pri_to_mp_csp_prim(int avpri) -{ - switch (avpri) { - case AVCOL_PRI_SMPTE240M: // Same as below - case AVCOL_PRI_SMPTE170M: return MP_CSP_PRIM_BT_601_525; - case AVCOL_PRI_BT470BG: return MP_CSP_PRIM_BT_601_625; - case AVCOL_PRI_BT709: return MP_CSP_PRIM_BT_709; - case AVCOL_PRI_BT2020: return MP_CSP_PRIM_BT_2020; - case AVCOL_PRI_BT470M: return MP_CSP_PRIM_BT_470M; - case AVCOL_PRI_SMPTE431: return MP_CSP_PRIM_DCI_P3; - case AVCOL_PRI_SMPTE432: return MP_CSP_PRIM_DISPLAY_P3; - default: return MP_CSP_PRIM_AUTO; - } -} - -enum mp_csp_trc avcol_trc_to_mp_csp_trc(int avtrc) -{ - switch (avtrc) { - case AVCOL_TRC_BT709: - case AVCOL_TRC_SMPTE170M: - case AVCOL_TRC_SMPTE240M: - case AVCOL_TRC_BT1361_ECG: - case AVCOL_TRC_BT2020_10: - case AVCOL_TRC_BT2020_12: return MP_CSP_TRC_BT_1886; - case AVCOL_TRC_IEC61966_2_1: return MP_CSP_TRC_SRGB; - case AVCOL_TRC_LINEAR: return MP_CSP_TRC_LINEAR; - case AVCOL_TRC_GAMMA22: return MP_CSP_TRC_GAMMA22; - case AVCOL_TRC_GAMMA28: return MP_CSP_TRC_GAMMA28; - case AVCOL_TRC_SMPTEST2084: return MP_CSP_TRC_PQ; - case AVCOL_TRC_ARIB_STD_B67: return MP_CSP_TRC_HLG; - case AVCOL_TRC_SMPTE428: return MP_CSP_TRC_ST428; - default: return MP_CSP_TRC_AUTO; - } -} - -int mp_csp_to_avcol_spc(enum mp_csp colorspace) -{ - switch (colorspace) { - case MP_CSP_BT_709: return AVCOL_SPC_BT709; - case MP_CSP_BT_601: return AVCOL_SPC_BT470BG; - case MP_CSP_BT_2020_NC: return AVCOL_SPC_BT2020_NCL; - case MP_CSP_BT_2020_C: return AVCOL_SPC_BT2020_CL; - case MP_CSP_SMPTE_240M: return AVCOL_SPC_SMPTE240M; - case MP_CSP_RGB: return AVCOL_SPC_RGB; - case MP_CSP_YCGCO: return AVCOL_SPC_YCOCG; - default: return AVCOL_SPC_UNSPECIFIED; - } -} - -int mp_csp_levels_to_avcol_range(enum mp_csp_levels range) -{ - switch (range) { - case MP_CSP_LEVELS_TV: return AVCOL_RANGE_MPEG; - case MP_CSP_LEVELS_PC: return AVCOL_RANGE_JPEG; - default: return AVCOL_RANGE_UNSPECIFIED; - } -} - -int mp_csp_prim_to_avcol_pri(enum mp_csp_prim prim) -{ - switch (prim) { - case MP_CSP_PRIM_BT_601_525: return AVCOL_PRI_SMPTE170M; - case MP_CSP_PRIM_BT_601_625: return AVCOL_PRI_BT470BG; - case MP_CSP_PRIM_BT_709: return AVCOL_PRI_BT709; - case MP_CSP_PRIM_BT_2020: return AVCOL_PRI_BT2020; - case MP_CSP_PRIM_BT_470M: return AVCOL_PRI_BT470M; - case MP_CSP_PRIM_DCI_P3: return AVCOL_PRI_SMPTE431; - case MP_CSP_PRIM_DISPLAY_P3: return AVCOL_PRI_SMPTE432; - default: return AVCOL_PRI_UNSPECIFIED; - } -} - -int mp_csp_trc_to_avcol_trc(enum mp_csp_trc trc) -{ - switch (trc) { - // We just call it BT.1886 since we're decoding, but it's still BT.709 - case MP_CSP_TRC_BT_1886: return AVCOL_TRC_BT709; - case MP_CSP_TRC_SRGB: return AVCOL_TRC_IEC61966_2_1; - case MP_CSP_TRC_LINEAR: return AVCOL_TRC_LINEAR; - case MP_CSP_TRC_GAMMA22: return AVCOL_TRC_GAMMA22; - case MP_CSP_TRC_GAMMA28: return AVCOL_TRC_GAMMA28; - case MP_CSP_TRC_PQ: return AVCOL_TRC_SMPTEST2084; - case MP_CSP_TRC_HLG: return AVCOL_TRC_ARIB_STD_B67; - case MP_CSP_TRC_ST428: return AVCOL_TRC_SMPTE428; - default: return AVCOL_TRC_UNSPECIFIED; - } -} - -enum mp_csp mp_csp_guess_colorspace(int width, int height) +enum pl_color_system mp_csp_guess_colorspace(int width, int height) { - return width >= 1280 || height > 576 ? MP_CSP_BT_709 : MP_CSP_BT_601; + return width >= 1280 || height > 576 ? PL_COLOR_SYSTEM_BT_709 : PL_COLOR_SYSTEM_BT_601; } -enum mp_csp_prim mp_csp_guess_primaries(int width, int height) +enum pl_color_primaries mp_csp_guess_primaries(int width, int height) { // HD content if (width >= 1280 || height > 576) - return MP_CSP_PRIM_BT_709; + return PL_COLOR_PRIM_BT_709; switch (height) { case 576: // Typical PAL content, including anamorphic/squared - return MP_CSP_PRIM_BT_601_625; + return PL_COLOR_PRIM_BT_601_625; case 480: // Typical NTSC content, including squared case 486: // NTSC Pro or anamorphic NTSC - return MP_CSP_PRIM_BT_601_525; + return PL_COLOR_PRIM_BT_601_525; default: // No good metric, just pick BT.709 to minimize damage - return MP_CSP_PRIM_BT_709; - } -} - -enum mp_chroma_location avchroma_location_to_mp(int avloc) -{ - switch (avloc) { - case AVCHROMA_LOC_TOPLEFT: return MP_CHROMA_TOPLEFT; - case AVCHROMA_LOC_LEFT: return MP_CHROMA_LEFT; - case AVCHROMA_LOC_CENTER: return MP_CHROMA_CENTER; - default: return MP_CHROMA_AUTO; - } -} - -int mp_chroma_location_to_av(enum mp_chroma_location mploc) -{ - switch (mploc) { - case MP_CHROMA_TOPLEFT: return AVCHROMA_LOC_TOPLEFT; - case MP_CHROMA_LEFT: return AVCHROMA_LOC_LEFT; - case MP_CHROMA_CENTER: return AVCHROMA_LOC_CENTER; - default: return AVCHROMA_LOC_UNSPECIFIED; - } -} - -// Return location of chroma samples relative to luma samples. 0/0 means -// centered. Other possible values are -1 (top/left) and +1 (right/bottom). -void mp_get_chroma_location(enum mp_chroma_location loc, int *x, int *y) -{ - *x = 0; - *y = 0; - if (loc == MP_CHROMA_LEFT || loc == MP_CHROMA_TOPLEFT) - *x = -1; - if (loc == MP_CHROMA_TOPLEFT) - *y = -1; -} - -void mp_invert_matrix3x3(float m[3][3]) -{ - float m00 = m[0][0], m01 = m[0][1], m02 = m[0][2], - m10 = m[1][0], m11 = m[1][1], m12 = m[1][2], - m20 = m[2][0], m21 = m[2][1], m22 = m[2][2]; - - // calculate the adjoint - m[0][0] = (m11 * m22 - m21 * m12); - m[0][1] = -(m01 * m22 - m21 * m02); - m[0][2] = (m01 * m12 - m11 * m02); - m[1][0] = -(m10 * m22 - m20 * m12); - m[1][1] = (m00 * m22 - m20 * m02); - m[1][2] = -(m00 * m12 - m10 * m02); - m[2][0] = (m10 * m21 - m20 * m11); - m[2][1] = -(m00 * m21 - m20 * m01); - m[2][2] = (m00 * m11 - m10 * m01); - - // calculate the determinant (as inverse == 1/det * adjoint, - // adjoint * m == identity * det, so this calculates the det) - float det = m00 * m[0][0] + m10 * m[0][1] + m20 * m[0][2]; - det = 1.0f / det; - - for (int i = 0; i < 3; i++) { - for (int j = 0; j < 3; j++) - m[i][j] *= det; - } -} - -// A := A * B -static void mp_mul_matrix3x3(float a[3][3], float b[3][3]) -{ - float a00 = a[0][0], a01 = a[0][1], a02 = a[0][2], - a10 = a[1][0], a11 = a[1][1], a12 = a[1][2], - a20 = a[2][0], a21 = a[2][1], a22 = a[2][2]; - - for (int i = 0; i < 3; i++) { - a[0][i] = a00 * b[0][i] + a01 * b[1][i] + a02 * b[2][i]; - a[1][i] = a10 * b[0][i] + a11 * b[1][i] + a12 * b[2][i]; - a[2][i] = a20 * b[0][i] + a21 * b[1][i] + a22 * b[2][i]; - } -} - -// return the primaries associated with a certain mp_csp_primaries val -struct mp_csp_primaries mp_get_csp_primaries(enum mp_csp_prim spc) -{ - /* - Values from: ITU-R Recommendations BT.470-6, BT.601-7, BT.709-5, BT.2020-0 - - https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.470-6-199811-S!!PDF-E.pdf - https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.601-7-201103-I!!PDF-E.pdf - https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.709-5-200204-I!!PDF-E.pdf - https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.2020-0-201208-I!!PDF-E.pdf - - Other colorspaces from https://en.wikipedia.org/wiki/RGB_color_space#Specifications - */ - - // CIE standard illuminant series - static const struct mp_csp_col_xy - d50 = {0.34577, 0.35850}, - d65 = {0.31271, 0.32902}, - c = {0.31006, 0.31616}, - dci = {0.31400, 0.35100}, - e = {1.0/3.0, 1.0/3.0}; - - switch (spc) { - case MP_CSP_PRIM_BT_470M: - return (struct mp_csp_primaries) { - .red = {0.670, 0.330}, - .green = {0.210, 0.710}, - .blue = {0.140, 0.080}, - .white = c - }; - case MP_CSP_PRIM_BT_601_525: - return (struct mp_csp_primaries) { - .red = {0.630, 0.340}, - .green = {0.310, 0.595}, - .blue = {0.155, 0.070}, - .white = d65 - }; - case MP_CSP_PRIM_BT_601_625: - return (struct mp_csp_primaries) { - .red = {0.640, 0.330}, - .green = {0.290, 0.600}, - .blue = {0.150, 0.060}, - .white = d65 - }; - // This is the default assumption if no colorspace information could - // be determined, eg. for files which have no video channel. - case MP_CSP_PRIM_AUTO: - case MP_CSP_PRIM_BT_709: - return (struct mp_csp_primaries) { - .red = {0.640, 0.330}, - .green = {0.300, 0.600}, - .blue = {0.150, 0.060}, - .white = d65 - }; - case MP_CSP_PRIM_BT_2020: - return (struct mp_csp_primaries) { - .red = {0.708, 0.292}, - .green = {0.170, 0.797}, - .blue = {0.131, 0.046}, - .white = d65 - }; - case MP_CSP_PRIM_APPLE: - return (struct mp_csp_primaries) { - .red = {0.625, 0.340}, - .green = {0.280, 0.595}, - .blue = {0.115, 0.070}, - .white = d65 - }; - case MP_CSP_PRIM_ADOBE: - return (struct mp_csp_primaries) { - .red = {0.640, 0.330}, - .green = {0.210, 0.710}, - .blue = {0.150, 0.060}, - .white = d65 - }; - case MP_CSP_PRIM_PRO_PHOTO: - return (struct mp_csp_primaries) { - .red = {0.7347, 0.2653}, - .green = {0.1596, 0.8404}, - .blue = {0.0366, 0.0001}, - .white = d50 - }; - case MP_CSP_PRIM_CIE_1931: - return (struct mp_csp_primaries) { - .red = {0.7347, 0.2653}, - .green = {0.2738, 0.7174}, - .blue = {0.1666, 0.0089}, - .white = e - }; - // From SMPTE RP 431-2 and 432-1 - case MP_CSP_PRIM_DCI_P3: - case MP_CSP_PRIM_DISPLAY_P3: - return (struct mp_csp_primaries) { - .red = {0.680, 0.320}, - .green = {0.265, 0.690}, - .blue = {0.150, 0.060}, - .white = spc == MP_CSP_PRIM_DCI_P3 ? dci : d65 - }; - // From Panasonic VARICAM reference manual - case MP_CSP_PRIM_V_GAMUT: - return (struct mp_csp_primaries) { - .red = {0.730, 0.280}, - .green = {0.165, 0.840}, - .blue = {0.100, -0.03}, - .white = d65 - }; - // From Sony S-Log reference manual - case MP_CSP_PRIM_S_GAMUT: - return (struct mp_csp_primaries) { - .red = {0.730, 0.280}, - .green = {0.140, 0.855}, - .blue = {0.100, -0.05}, - .white = d65 - }; - // from EBU Tech. 3213-E - case MP_CSP_PRIM_EBU_3213: - return (struct mp_csp_primaries) { - .red = {0.630, 0.340}, - .green = {0.295, 0.605}, - .blue = {0.155, 0.077}, - .white = d65 - }; - // From H.273, traditional film with Illuminant C - case MP_CSP_PRIM_FILM_C: - return (struct mp_csp_primaries) { - .red = {0.681, 0.319}, - .green = {0.243, 0.692}, - .blue = {0.145, 0.049}, - .white = c - }; - // From libplacebo source code - case MP_CSP_PRIM_ACES_AP0: - return (struct mp_csp_primaries) { - .red = {0.7347, 0.2653}, - .green = {0.0000, 1.0000}, - .blue = {0.0001, -0.0770}, - .white = {0.32168, 0.33767}, - }; - // From libplacebo source code - case MP_CSP_PRIM_ACES_AP1: - return (struct mp_csp_primaries) { - .red = {0.713, 0.293}, - .green = {0.165, 0.830}, - .blue = {0.128, 0.044}, - .white = {0.32168, 0.33767}, - }; - default: - return (struct mp_csp_primaries) {{0}}; - } -} - -// Get the nominal peak for a given colorspace, relative to the reference white -// level. In other words, this returns the brightest encodable value that can -// be represented by a given transfer curve. -float mp_trc_nom_peak(enum mp_csp_trc trc) -{ - switch (trc) { - case MP_CSP_TRC_PQ: return 10000.0 / MP_REF_WHITE; - case MP_CSP_TRC_HLG: return 12.0 / MP_REF_WHITE_HLG; - case MP_CSP_TRC_V_LOG: return 46.0855; - case MP_CSP_TRC_S_LOG1: return 6.52; - case MP_CSP_TRC_S_LOG2: return 9.212; + return PL_COLOR_PRIM_BT_709; } - - return 1.0; -} - -bool mp_trc_is_hdr(enum mp_csp_trc trc) -{ - return mp_trc_nom_peak(trc) > 1.0; } -// Compute the RGB/XYZ matrix as described here: -// http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html -void mp_get_rgb2xyz_matrix(struct mp_csp_primaries space, float m[3][3]) -{ - float S[3], X[4], Z[4]; - - // Convert from CIE xyY to XYZ. Note that Y=1 holds true for all primaries - X[0] = space.red.x / space.red.y; - X[1] = space.green.x / space.green.y; - X[2] = space.blue.x / space.blue.y; - X[3] = space.white.x / space.white.y; - - Z[0] = (1 - space.red.x - space.red.y) / space.red.y; - Z[1] = (1 - space.green.x - space.green.y) / space.green.y; - Z[2] = (1 - space.blue.x - space.blue.y) / space.blue.y; - Z[3] = (1 - space.white.x - space.white.y) / space.white.y; - - // S = XYZ^-1 * W - for (int i = 0; i < 3; i++) { - m[0][i] = X[i]; - m[1][i] = 1; - m[2][i] = Z[i]; - } - - mp_invert_matrix3x3(m); - - for (int i = 0; i < 3; i++) - S[i] = m[i][0] * X[3] + m[i][1] * 1 + m[i][2] * Z[3]; - - // M = [Sc * XYZc] - for (int i = 0; i < 3; i++) { - m[0][i] = S[i] * X[i]; - m[1][i] = S[i] * 1; - m[2][i] = S[i] * Z[i]; - } -} +// LMS<-XYZ revised matrix from CIECAM97, based on a linear transform and +// normalized for equal energy on monochrome inputs +static const pl_matrix3x3 m_cat97 = {{ + { 0.8562, 0.3372, -0.1934 }, + { -0.8360, 1.8327, 0.0033 }, + { 0.0357, -0.0469, 1.0112 }, +}}; // M := M * XYZd<-XYZs -static void mp_apply_chromatic_adaptation(struct mp_csp_col_xy src, - struct mp_csp_col_xy dest, float m[3][3]) +static void apply_chromatic_adaptation(struct pl_cie_xy src, + struct pl_cie_xy dest, pl_matrix3x3 *mat) { // If the white points are nearly identical, this is a wasteful identity // operation. @@ -588,104 +190,39 @@ static void mp_apply_chromatic_adaptation(struct mp_csp_col_xy src, // XYZd<-XYZs = Ma^-1 * (I*[Cd/Cs]) * Ma // http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html - float C[3][2], tmp[3][3] = {{0}}; - - // Ma = Bradford matrix, arguably most popular method in use today. - // This is derived experimentally and thus hard-coded. - float bradford[3][3] = { - { 0.8951, 0.2664, -0.1614 }, - { -0.7502, 1.7135, 0.0367 }, - { 0.0389, -0.0685, 1.0296 }, - }; + // For Ma, we use the CIECAM97 revised (linear) matrix + float C[3][2]; for (int i = 0; i < 3; i++) { // source cone - C[i][0] = bradford[i][0] * mp_xy_X(src) - + bradford[i][1] * 1 - + bradford[i][2] * mp_xy_Z(src); + C[i][0] = m_cat97.m[i][0] * pl_cie_X(src) + + m_cat97.m[i][1] * 1 + + m_cat97.m[i][2] * pl_cie_Z(src); // dest cone - C[i][1] = bradford[i][0] * mp_xy_X(dest) - + bradford[i][1] * 1 - + bradford[i][2] * mp_xy_Z(dest); + C[i][1] = m_cat97.m[i][0] * pl_cie_X(dest) + + m_cat97.m[i][1] * 1 + + m_cat97.m[i][2] * pl_cie_Z(dest); } // tmp := I * [Cd/Cs] * Ma + pl_matrix3x3 tmp = {0}; for (int i = 0; i < 3; i++) - tmp[i][i] = C[i][1] / C[i][0]; + tmp.m[i][i] = C[i][1] / C[i][0]; - mp_mul_matrix3x3(tmp, bradford); + pl_matrix3x3_mul(&tmp, &m_cat97); // M := M * Ma^-1 * tmp - mp_invert_matrix3x3(bradford); - mp_mul_matrix3x3(m, bradford); - mp_mul_matrix3x3(m, tmp); -} - -// get the coefficients of the source -> dest cms matrix -void mp_get_cms_matrix(struct mp_csp_primaries src, struct mp_csp_primaries dest, - enum mp_render_intent intent, float m[3][3]) -{ - float tmp[3][3]; - - // In saturation mapping, we don't care about accuracy and just want - // primaries to map to primaries, making this an identity transformation. - if (intent == MP_INTENT_SATURATION) { - for (int i = 0; i < 3; i++) - m[i][i] = 1; - return; - } - - // RGBd<-RGBs = RGBd<-XYZd * XYZd<-XYZs * XYZs<-RGBs - // Equations from: http://www.brucelindbloom.com/index.html?Math.html - // Note: Perceptual is treated like relative colorimetric. There's no - // definition for perceptual other than "make it look good". - - // RGBd<-XYZd, inverted from XYZd<-RGBd - mp_get_rgb2xyz_matrix(dest, m); - mp_invert_matrix3x3(m); - - // Chromatic adaptation, except in absolute colorimetric intent - if (intent != MP_INTENT_ABSOLUTE_COLORIMETRIC) - mp_apply_chromatic_adaptation(src.white, dest.white, m); - - // XYZs<-RGBs - mp_get_rgb2xyz_matrix(src, tmp); - mp_mul_matrix3x3(m, tmp); -} - -// get the coefficients of an ST 428-1 xyz -> rgb conversion matrix -// intent = the rendering intent used to convert to the target primaries -static void mp_get_xyz2rgb_coeffs(struct mp_csp_params *params, - enum mp_render_intent intent, struct mp_cmat *m) -{ - // Convert to DCI-P3 - struct mp_csp_primaries prim = mp_get_csp_primaries(MP_CSP_PRIM_DCI_P3); - float brightness = params->brightness; - mp_get_rgb2xyz_matrix(prim, m->m); - mp_invert_matrix3x3(m->m); - - // All non-absolute mappings want to map source white to target white - if (intent != MP_INTENT_ABSOLUTE_COLORIMETRIC) { - // SMPTE EG 432-1 Annex H defines the white point as equal energy - static const struct mp_csp_col_xy smpte432 = {1.0/3.0, 1.0/3.0}; - mp_apply_chromatic_adaptation(smpte432, prim.white, m->m); - } - - // Since this outputs linear RGB rather than companded RGB, we - // want to linearize any brightness additions. 2 is a reasonable - // approximation for any sort of gamma function that could be in use. - // As this is an aesthetic setting only, any exact values do not matter. - brightness *= fabs(brightness); - - for (int i = 0; i < 3; i++) - m->c[i] = brightness; + pl_matrix3x3 ma_inv = m_cat97; + pl_matrix3x3_invert(&ma_inv); + pl_matrix3x3_mul(mat, &ma_inv); + pl_matrix3x3_mul(mat, &tmp); } // Get multiplication factor required if image data is fit within the LSBs of a // higher smaller bit depth fixed-point texture data. // This is broken. Use mp_get_csp_uint_mul(). -double mp_get_csp_mul(enum mp_csp csp, int input_bits, int texture_bits) +double mp_get_csp_mul(enum pl_color_system csp, int input_bits, int texture_bits) { assert(texture_bits >= input_bits); @@ -694,10 +231,10 @@ double mp_get_csp_mul(enum mp_csp csp, int input_bits, int texture_bits) return 1; // RGB always uses the full range available. - if (csp == MP_CSP_RGB) + if (csp == PL_COLOR_SYSTEM_RGB) return ((1LL << input_bits) - 1.) / ((1LL << texture_bits) - 1.); - if (csp == MP_CSP_XYZ) + if (csp == PL_COLOR_SYSTEM_XYZ) return 1; // High bit depth YUV uses a range shifted from 8 bit. @@ -716,24 +253,24 @@ double mp_get_csp_mul(enum mp_csp csp, int input_bits, int texture_bits) // bits: number of significant bits, e.g. 10 for yuv420p10, 16 for p010 // out_m: returns factor to multiply the uint number with // out_o: returns offset to add after multiplication -void mp_get_csp_uint_mul(enum mp_csp csp, enum mp_csp_levels levels, +void mp_get_csp_uint_mul(enum pl_color_system csp, enum pl_color_levels levels, int bits, int component, double *out_m, double *out_o) { uint16_t i_min = 0; uint16_t i_max = (1u << bits) - 1; double f_min = 0; // min. float value - if (csp != MP_CSP_RGB && component != 4) { + if (csp != PL_COLOR_SYSTEM_RGB && component != 4) { if (component == 2 || component == 3) { f_min = (1u << (bits - 1)) / -(double)i_max; // force center => 0 - if (levels != MP_CSP_LEVELS_PC && bits >= 8) { + if (levels != PL_COLOR_LEVELS_FULL && bits >= 8) { i_min = 16 << (bits - 8); // => -0.5 i_max = 240 << (bits - 8); // => 0.5 f_min = -0.5; } } else { - if (levels != MP_CSP_LEVELS_PC && bits >= 8) { + if (levels != PL_COLOR_LEVELS_FULL && bits >= 8) { i_min = 16 << (bits - 8); // => 0 i_max = 235 << (bits - 8); // => 1 } @@ -764,60 +301,67 @@ void mp_get_csp_uint_mul(enum mp_csp csp, enum mp_csp_levels levels, * Under these conditions the given parameters lr, lg, lb uniquely * determine the mapping of Y, U, V to R, G, B. */ -static void luma_coeffs(struct mp_cmat *mat, float lr, float lg, float lb) +static void luma_coeffs(struct pl_transform3x3 *mat, float lr, float lg, float lb) { assert(fabs(lr+lg+lb - 1) < 1e-6); - *mat = (struct mp_cmat) { - { {1, 0, 2 * (1-lr) }, - {1, -2 * (1-lb) * lb/lg, -2 * (1-lr) * lr/lg }, - {1, 2 * (1-lb), 0 } }, + *mat = (struct pl_transform3x3) { + { {{1, 0, 2 * (1-lr) }, + {1, -2 * (1-lb) * lb/lg, -2 * (1-lr) * lr/lg }, + {1, 2 * (1-lb), 0 }} }, // Constant coefficients (mat->c) not set here }; } // get the coefficients of the yuv -> rgb conversion matrix -void mp_get_csp_matrix(struct mp_csp_params *params, struct mp_cmat *m) -{ - enum mp_csp colorspace = params->color.space; - if (colorspace <= MP_CSP_AUTO || colorspace >= MP_CSP_COUNT) - colorspace = MP_CSP_BT_601; - enum mp_csp_levels levels_in = params->color.levels; - if (levels_in <= MP_CSP_LEVELS_AUTO || levels_in >= MP_CSP_LEVELS_COUNT) - levels_in = MP_CSP_LEVELS_TV; +void mp_get_csp_matrix(struct mp_csp_params *params, struct pl_transform3x3 *m) +{ + enum pl_color_system colorspace = params->repr.sys; + if (colorspace <= PL_COLOR_SYSTEM_UNKNOWN || colorspace >= PL_COLOR_SYSTEM_COUNT) + colorspace = PL_COLOR_SYSTEM_BT_601; + // Not supported. TODO: replace with pl_color_repr_decode + if (colorspace == PL_COLOR_SYSTEM_BT_2100_PQ || + colorspace == PL_COLOR_SYSTEM_BT_2100_HLG || + colorspace == PL_COLOR_SYSTEM_DOLBYVISION) { + colorspace = PL_COLOR_SYSTEM_BT_2020_NC; + } + enum pl_color_levels levels_in = params->repr.levels; + if (levels_in <= PL_COLOR_LEVELS_UNKNOWN || levels_in >= PL_COLOR_LEVELS_COUNT) + levels_in = PL_COLOR_LEVELS_LIMITED; switch (colorspace) { - case MP_CSP_BT_601: luma_coeffs(m, 0.299, 0.587, 0.114 ); break; - case MP_CSP_BT_709: luma_coeffs(m, 0.2126, 0.7152, 0.0722); break; - case MP_CSP_SMPTE_240M: luma_coeffs(m, 0.2122, 0.7013, 0.0865); break; - case MP_CSP_BT_2020_NC: luma_coeffs(m, 0.2627, 0.6780, 0.0593); break; - case MP_CSP_BT_2020_C: { + case PL_COLOR_SYSTEM_BT_601: luma_coeffs(m, 0.299, 0.587, 0.114 ); break; + case PL_COLOR_SYSTEM_BT_709: luma_coeffs(m, 0.2126, 0.7152, 0.0722); break; + case PL_COLOR_SYSTEM_SMPTE_240M: luma_coeffs(m, 0.2122, 0.7013, 0.0865); break; + case PL_COLOR_SYSTEM_BT_2020_NC: luma_coeffs(m, 0.2627, 0.6780, 0.0593); break; + case PL_COLOR_SYSTEM_BT_2020_C: { // Note: This outputs into the [-0.5,0.5] range for chroma information. // If this clips on any VO, a constant 0.5 coefficient can be added // to the chroma channels to normalize them into [0,1]. This is not // currently needed by anything, though. - *m = (struct mp_cmat){{{0, 0, 1}, {1, 0, 0}, {0, 1, 0}}}; + *m = (struct pl_transform3x3){{{{0, 0, 1}, {1, 0, 0}, {0, 1, 0}}}}; break; } - case MP_CSP_RGB: { - *m = (struct mp_cmat){{{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}}; + case PL_COLOR_SYSTEM_RGB: { + *m = (struct pl_transform3x3){{{{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}}}; levels_in = -1; break; } - case MP_CSP_XYZ: { - // The vo should probably not be using a matrix generated by this - // function for XYZ sources, but if it does, let's just convert it to - // an equivalent RGB space based on the colorimetry metadata it - // provided in mp_csp_params. (At the risk of clipping, if the - // chosen primaries are too small to fit the actual data) - mp_get_xyz2rgb_coeffs(params, MP_INTENT_RELATIVE_COLORIMETRIC, m); + case PL_COLOR_SYSTEM_XYZ: { + // For lack of anything saner to do, just assume the caller wants + // DCI-P3 primaries, which is a reasonable assumption. + const struct pl_raw_primaries *dst = pl_raw_primaries_get(PL_COLOR_PRIM_DCI_P3); + pl_matrix3x3 mat = pl_get_xyz2rgb_matrix(dst); + // DCDM X'Y'Z' is expected to have equal energy white point (EG 432-1 Annex H) + apply_chromatic_adaptation((struct pl_cie_xy){1.0/3.0, 1.0/3.0}, dst->white, &mat); + *m = (struct pl_transform3x3) { .mat = mat }; levels_in = -1; break; } - case MP_CSP_YCGCO: { - *m = (struct mp_cmat) { - {{1, -1, 1}, - {1, 1, 0}, - {1, -1, -1}}, + case PL_COLOR_SYSTEM_YCGCO: { + *m = (struct pl_transform3x3) { + {{{1, -1, 1}, + {1, 1, 0}, + {1, -1, -1}}}, }; break; } @@ -828,17 +372,17 @@ void mp_get_csp_matrix(struct mp_csp_params *params, struct mp_cmat *m) if (params->is_float) levels_in = -1; - if ((colorspace == MP_CSP_BT_601 || colorspace == MP_CSP_BT_709 || - colorspace == MP_CSP_SMPTE_240M || colorspace == MP_CSP_BT_2020_NC)) + if ((colorspace == PL_COLOR_SYSTEM_BT_601 || colorspace == PL_COLOR_SYSTEM_BT_709 || + colorspace == PL_COLOR_SYSTEM_SMPTE_240M || colorspace == PL_COLOR_SYSTEM_BT_2020_NC)) { // Hue is equivalent to rotating input [U, V] subvector around the origin. // Saturation scales [U, V]. float huecos = params->gray ? 0 : params->saturation * cos(params->hue); float huesin = params->gray ? 0 : params->saturation * sin(params->hue); for (int i = 0; i < 3; i++) { - float u = m->m[i][1], v = m->m[i][2]; - m->m[i][1] = huecos * u - huesin * v; - m->m[i][2] = huesin * u + huecos * v; + float u = m->mat.m[i][1], v = m->mat.m[i][2]; + m->mat.m[i][1] = huecos * u - huesin * v; + m->mat.m[i][2] = huesin * u + huecos * v; } } @@ -855,23 +399,23 @@ void mp_get_csp_matrix(struct mp_csp_params *params, struct mp_cmat *m) anyfull = { 0*s, 255*s, 255*s/2, 0 }, // cmax picked to make cmul=ymul yuvlev; switch (levels_in) { - case MP_CSP_LEVELS_TV: yuvlev = yuvlim; break; - case MP_CSP_LEVELS_PC: yuvlev = yuvfull; break; + case PL_COLOR_LEVELS_LIMITED: yuvlev = yuvlim; break; + case PL_COLOR_LEVELS_FULL: yuvlev = yuvfull; break; case -1: yuvlev = anyfull; break; default: MP_ASSERT_UNREACHABLE(); } int levels_out = params->levels_out; - if (levels_out <= MP_CSP_LEVELS_AUTO || levels_out >= MP_CSP_LEVELS_COUNT) - levels_out = MP_CSP_LEVELS_PC; + if (levels_out <= PL_COLOR_LEVELS_UNKNOWN || levels_out >= PL_COLOR_LEVELS_COUNT) + levels_out = PL_COLOR_LEVELS_FULL; struct rgblevels { double min, max; } rgblim = { 16/255., 235/255. }, rgbfull = { 0, 1 }, rgblev; switch (levels_out) { - case MP_CSP_LEVELS_TV: rgblev = rgblim; break; - case MP_CSP_LEVELS_PC: rgblev = rgbfull; break; + case PL_COLOR_LEVELS_LIMITED: rgblev = rgblim; break; + case PL_COLOR_LEVELS_FULL: rgblev = rgbfull; break; default: MP_ASSERT_UNREACHABLE(); } @@ -884,13 +428,13 @@ void mp_get_csp_matrix(struct mp_csp_params *params, struct mp_cmat *m) cmul *= params->contrast; for (int i = 0; i < 3; i++) { - m->m[i][0] *= ymul; - m->m[i][1] *= cmul; - m->m[i][2] *= cmul; + m->mat.m[i][0] *= ymul; + m->mat.m[i][1] *= cmul; + m->mat.m[i][2] *= cmul; // Set c so that Y=umin,UV=cmid maps to RGB=min (black to black), // also add brightness offset (black lift) - m->c[i] = rgblev.min - m->m[i][0] * yuvlev.ymin - - (m->m[i][1] + m->m[i][2]) * yuvlev.cmid + m->c[i] = rgblev.min - m->mat.m[i][0] * yuvlev.ymin + - (m->mat.m[i][1] + m->mat.m[i][2]) * yuvlev.cmid + params->brightness; } } @@ -901,19 +445,10 @@ void mp_csp_set_image_params(struct mp_csp_params *params, { struct mp_image_params p = *imgparams; mp_image_params_guess_csp(&p); // ensure consistency + params->repr = p.repr; params->color = p.color; } -bool mp_colorspace_equal(struct mp_colorspace c1, struct mp_colorspace c2) -{ - return c1.space == c2.space && - c1.levels == c2.levels && - c1.primaries == c2.primaries && - c1.gamma == c2.gamma && - c1.light == c2.light && - pl_hdr_metadata_equal(&c1.hdr, &c2.hdr); -} - enum mp_csp_equalizer_param { MP_CSP_EQ_BRIGHTNESS, MP_CSP_EQ_CONTRAST, @@ -946,10 +481,11 @@ const struct m_sub_options mp_csp_equalizer_conf = { {"gamma", OPT_FLOAT(values[MP_CSP_EQ_GAMMA]), M_RANGE(-100, 100)}, {"video-output-levels", - OPT_CHOICE_C(output_levels, mp_csp_levels_names)}, + OPT_CHOICE_C(output_levels, pl_csp_levels_names)}, {0} }, .size = sizeof(struct mp_csp_equalizer_opts), + .change_flags = UPDATE_VIDEO, }; // Copy settings from eq into params. @@ -988,32 +524,17 @@ void mp_csp_equalizer_state_get(struct mp_csp_equalizer_state *state, mp_csp_copy_equalizer_values(params, opts); } -void mp_invert_cmat(struct mp_cmat *out, struct mp_cmat *in) -{ - *out = *in; - mp_invert_matrix3x3(out->m); - - // fix the constant coefficient - // rgb = M * yuv + C - // M^-1 * rgb = yuv + M^-1 * C - // yuv = M^-1 * rgb - M^-1 * C - // ^^^^^^^^^^ - out->c[0] = -(out->m[0][0] * in->c[0] + out->m[0][1] * in->c[1] + out->m[0][2] * in->c[2]); - out->c[1] = -(out->m[1][0] * in->c[0] + out->m[1][1] * in->c[1] + out->m[1][2] * in->c[2]); - out->c[2] = -(out->m[2][0] * in->c[0] + out->m[2][1] * in->c[1] + out->m[2][2] * in->c[2]); -} - // Multiply the color in c with the given matrix. // i/o is {R, G, B} or {Y, U, V} (depending on input/output and matrix), using // a fixed point representation with the given number of bits (so for bits==8, // [0,255] maps to [0,1]). The output is clipped to the range as needed. -void mp_map_fixp_color(struct mp_cmat *matrix, int ibits, int in[3], +void mp_map_fixp_color(struct pl_transform3x3 *matrix, int ibits, int in[3], int obits, int out[3]) { for (int i = 0; i < 3; i++) { double val = matrix->c[i]; for (int x = 0; x < 3; x++) - val += matrix->m[i][x] * in[x] / ((1 << ibits) - 1); + val += matrix->mat.m[i][x] * in[x] / ((1 << ibits) - 1); int ival = lrint(val * ((1 << obits) - 1)); out[i] = av_clip(ival, 0, (1 << obits) - 1); } diff --git a/video/csputils.h b/video/csputils.h index 3a904cb..5ab1287 100644 --- a/video/csputils.h +++ b/video/csputils.h @@ -30,76 +30,10 @@ * nonzero at vf/vo level. */ -enum mp_csp { - MP_CSP_AUTO, - MP_CSP_BT_601, - MP_CSP_BT_709, - MP_CSP_SMPTE_240M, - MP_CSP_BT_2020_NC, - MP_CSP_BT_2020_C, - MP_CSP_RGB, - MP_CSP_XYZ, - MP_CSP_YCGCO, - MP_CSP_COUNT -}; - -extern const struct m_opt_choice_alternatives mp_csp_names[]; - -enum mp_csp_levels { - MP_CSP_LEVELS_AUTO, - MP_CSP_LEVELS_TV, - MP_CSP_LEVELS_PC, - MP_CSP_LEVELS_COUNT, -}; - -extern const struct m_opt_choice_alternatives mp_csp_levels_names[]; - -enum mp_csp_prim { - MP_CSP_PRIM_AUTO, - MP_CSP_PRIM_BT_601_525, - MP_CSP_PRIM_BT_601_625, - MP_CSP_PRIM_BT_709, - MP_CSP_PRIM_BT_2020, - MP_CSP_PRIM_BT_470M, - MP_CSP_PRIM_APPLE, - MP_CSP_PRIM_ADOBE, - MP_CSP_PRIM_PRO_PHOTO, - MP_CSP_PRIM_CIE_1931, - MP_CSP_PRIM_DCI_P3, - MP_CSP_PRIM_DISPLAY_P3, - MP_CSP_PRIM_V_GAMUT, - MP_CSP_PRIM_S_GAMUT, - MP_CSP_PRIM_EBU_3213, - MP_CSP_PRIM_FILM_C, - MP_CSP_PRIM_ACES_AP0, - MP_CSP_PRIM_ACES_AP1, - MP_CSP_PRIM_COUNT -}; - -extern const struct m_opt_choice_alternatives mp_csp_prim_names[]; - -enum mp_csp_trc { - MP_CSP_TRC_AUTO, - MP_CSP_TRC_BT_1886, - MP_CSP_TRC_SRGB, - MP_CSP_TRC_LINEAR, - MP_CSP_TRC_GAMMA18, - MP_CSP_TRC_GAMMA20, - MP_CSP_TRC_GAMMA22, - MP_CSP_TRC_GAMMA24, - MP_CSP_TRC_GAMMA26, - MP_CSP_TRC_GAMMA28, - MP_CSP_TRC_PRO_PHOTO, - MP_CSP_TRC_PQ, - MP_CSP_TRC_HLG, - MP_CSP_TRC_V_LOG, - MP_CSP_TRC_S_LOG1, - MP_CSP_TRC_S_LOG2, - MP_CSP_TRC_ST428, - MP_CSP_TRC_COUNT -}; - -extern const struct m_opt_choice_alternatives mp_csp_trc_names[]; +extern const struct m_opt_choice_alternatives pl_csp_names[]; +extern const struct m_opt_choice_alternatives pl_csp_levels_names[]; +extern const struct m_opt_choice_alternatives pl_csp_prim_names[]; +extern const struct m_opt_choice_alternatives pl_csp_trc_names[]; enum mp_csp_light { MP_CSP_LIGHT_AUTO, @@ -112,15 +46,6 @@ enum mp_csp_light { extern const struct m_opt_choice_alternatives mp_csp_light_names[]; -// These constants are based on the ICC specification (Table 23) and match -// up with the API of LittleCMS, which treats them as integers. -enum mp_render_intent { - MP_INTENT_PERCEPTUAL = 0, - MP_INTENT_RELATIVE_COLORIMETRIC = 1, - MP_INTENT_SATURATION = 2, - MP_INTENT_ABSOLUTE_COLORIMETRIC = 3 -}; - // The numeric values (except -1) match the Matroska StereoMode element value. enum mp_stereo3d_mode { MP_STEREO3D_INVALID = -1, @@ -141,15 +66,6 @@ extern const struct m_opt_choice_alternatives mp_stereo3d_names[]; #define MP_STEREO3D_NAME_DEF(x, def) \ (MP_STEREO3D_NAME(x) ? MP_STEREO3D_NAME(x) : (def)) -struct mp_colorspace { - enum mp_csp space; - enum mp_csp_levels levels; - enum mp_csp_prim primaries; - enum mp_csp_trc gamma; - enum mp_csp_light light; - struct pl_hdr_metadata hdr; -}; - // For many colorspace conversions, in particular those involving HDR, an // implicit reference white level is needed. Since this magic constant shows up // a lot, give it an explicit name. The value of 203 cd/m² comes from ITU-R @@ -158,12 +74,10 @@ struct mp_colorspace { #define MP_REF_WHITE 203.0 #define MP_REF_WHITE_HLG 3.17955 -// Replaces unknown values in the first struct by those of the second struct -void mp_colorspace_merge(struct mp_colorspace *orig, struct mp_colorspace *new); - struct mp_csp_params { - struct mp_colorspace color; // input colorspace - enum mp_csp_levels levels_out; // output device + struct pl_color_repr repr; + struct pl_color_space color; + enum pl_color_levels levels_out; // output device float brightness; float contrast; float hue; @@ -179,9 +93,8 @@ struct mp_csp_params { }; #define MP_CSP_PARAMS_DEFAULTS { \ - .color = { .space = MP_CSP_BT_601, \ - .levels = MP_CSP_LEVELS_TV }, \ - .levels_out = MP_CSP_LEVELS_PC, \ + .repr = pl_color_repr_sdtv, \ + .levels_out = PL_COLOR_LEVELS_FULL, \ .brightness = 0, .contrast = 1, .hue = 0, .saturation = 1, \ .gamma = 1, .texture_bits = 8, .input_bits = 8} @@ -189,25 +102,8 @@ struct mp_image_params; void mp_csp_set_image_params(struct mp_csp_params *params, const struct mp_image_params *imgparams); -bool mp_colorspace_equal(struct mp_colorspace c1, struct mp_colorspace c2); - -enum mp_chroma_location { - MP_CHROMA_AUTO, - MP_CHROMA_TOPLEFT, // uhd - MP_CHROMA_LEFT, // mpeg2/4, h264 - MP_CHROMA_CENTER, // mpeg1, jpeg - MP_CHROMA_COUNT, -}; - -extern const struct m_opt_choice_alternatives mp_chroma_names[]; - -enum mp_alpha_type { - MP_ALPHA_AUTO, - MP_ALPHA_STRAIGHT, - MP_ALPHA_PREMUL, -}; - -extern const struct m_opt_choice_alternatives mp_alpha_names[]; +extern const struct m_opt_choice_alternatives pl_chroma_names[]; +extern const struct m_opt_choice_alternatives pl_alpha_names[]; extern const struct m_sub_options mp_csp_equalizer_conf; @@ -218,73 +114,15 @@ bool mp_csp_equalizer_state_changed(struct mp_csp_equalizer_state *state); void mp_csp_equalizer_state_get(struct mp_csp_equalizer_state *state, struct mp_csp_params *params); -struct mp_csp_col_xy { - float x, y; -}; - -static inline float mp_xy_X(struct mp_csp_col_xy xy) { - return xy.x / xy.y; -} - -static inline float mp_xy_Z(struct mp_csp_col_xy xy) { - return (1 - xy.x - xy.y) / xy.y; -} - -struct mp_csp_primaries { - struct mp_csp_col_xy red, green, blue, white; -}; - -enum mp_csp avcol_spc_to_mp_csp(int avcolorspace); -enum mp_csp_levels avcol_range_to_mp_csp_levels(int avrange); -enum mp_csp_prim avcol_pri_to_mp_csp_prim(int avpri); -enum mp_csp_trc avcol_trc_to_mp_csp_trc(int avtrc); - -int mp_csp_to_avcol_spc(enum mp_csp colorspace); -int mp_csp_levels_to_avcol_range(enum mp_csp_levels range); -int mp_csp_prim_to_avcol_pri(enum mp_csp_prim prim); -int mp_csp_trc_to_avcol_trc(enum mp_csp_trc trc); - -enum mp_csp mp_csp_guess_colorspace(int width, int height); -enum mp_csp_prim mp_csp_guess_primaries(int width, int height); - -enum mp_chroma_location avchroma_location_to_mp(int avloc); -int mp_chroma_location_to_av(enum mp_chroma_location mploc); -void mp_get_chroma_location(enum mp_chroma_location loc, int *x, int *y); - -struct mp_csp_primaries mp_get_csp_primaries(enum mp_csp_prim csp); -float mp_trc_nom_peak(enum mp_csp_trc trc); -bool mp_trc_is_hdr(enum mp_csp_trc trc); - -/* Color conversion matrix: RGB = m * YUV + c - * m is in row-major matrix, with m[row][col], e.g.: - * [ a11 a12 a13 ] float m[3][3] = { { a11, a12, a13 }, - * [ a21 a22 a23 ] { a21, a22, a23 }, - * [ a31 a32 a33 ] { a31, a32, a33 } }; - * This is accessed as e.g.: m[2-1][1-1] = a21 - * In particular, each row contains all the coefficients for one of R, G, B, - * while each column contains all the coefficients for one of Y, U, V: - * m[r,g,b][y,u,v] = ... - * The matrix could also be viewed as group of 3 vectors, e.g. the 1st column - * is the Y vector (1, 1, 1), the 2nd is the U vector, the 3rd the V vector. - * The matrix might also be used for other conversions and colorspaces. - */ -struct mp_cmat { - float m[3][3]; - float c[3]; -}; - -void mp_get_rgb2xyz_matrix(struct mp_csp_primaries space, float m[3][3]); -void mp_get_cms_matrix(struct mp_csp_primaries src, struct mp_csp_primaries dest, - enum mp_render_intent intent, float cms_matrix[3][3]); +enum pl_color_system mp_csp_guess_colorspace(int width, int height); +enum pl_color_primaries mp_csp_guess_primaries(int width, int height); -double mp_get_csp_mul(enum mp_csp csp, int input_bits, int texture_bits); -void mp_get_csp_uint_mul(enum mp_csp csp, enum mp_csp_levels levels, +double mp_get_csp_mul(enum pl_color_system csp, int input_bits, int texture_bits); +void mp_get_csp_uint_mul(enum pl_color_system csp, enum pl_color_levels levels, int bits, int component, double *out_m, double *out_o); -void mp_get_csp_matrix(struct mp_csp_params *params, struct mp_cmat *out); +void mp_get_csp_matrix(struct mp_csp_params *params, struct pl_transform3x3 *out); -void mp_invert_matrix3x3(float m[3][3]); -void mp_invert_cmat(struct mp_cmat *out, struct mp_cmat *in); -void mp_map_fixp_color(struct mp_cmat *matrix, int ibits, int in[3], +void mp_map_fixp_color(struct pl_transform3x3 *matrix, int ibits, int in[3], int obits, int out[3]); #endif /* MPLAYER_CSPUTILS_H */ diff --git a/video/decode/vd_lavc.c b/video/decode/vd_lavc.c index b971d26..05b66ed 100644 --- a/video/decode/vd_lavc.c +++ b/video/decode/vd_lavc.c @@ -1215,6 +1215,8 @@ static int decode_frame(struct mp_filter *vd) return ret; } + mp_codec_info_from_av(avctx, ctx->codec); + // If something was decoded successfully, it must return a frame with valid // data. assert(ctx->pic->buf[0]); @@ -1369,14 +1371,14 @@ static int control(struct mp_filter *vd, enum dec_ctrl cmd, void *arg) return CONTROL_UNKNOWN; } -static void process(struct mp_filter *vd) +static void vd_lavc_process(struct mp_filter *vd) { vd_ffmpeg_ctx *ctx = vd->priv; lavc_process(vd, &ctx->state, send_packet, receive_frame); } -static void reset(struct mp_filter *vd) +static void vd_lavc_reset(struct mp_filter *vd) { vd_ffmpeg_ctx *ctx = vd->priv; @@ -1386,7 +1388,7 @@ static void reset(struct mp_filter *vd) ctx->framedrop_flags = 0; } -static void destroy(struct mp_filter *vd) +static void vd_lavc_destroy(struct mp_filter *vd) { vd_ffmpeg_ctx *ctx = vd->priv; @@ -1398,9 +1400,9 @@ static void destroy(struct mp_filter *vd) static const struct mp_filter_info vd_lavc_filter = { .name = "vd_lavc", .priv_size = sizeof(vd_ffmpeg_ctx), - .process = process, - .reset = reset, - .destroy = destroy, + .process = vd_lavc_process, + .reset = vd_lavc_reset, + .destroy = vd_lavc_destroy, }; static struct mp_decoder *create(struct mp_filter *parent, @@ -1443,6 +1445,9 @@ static struct mp_decoder *create(struct mp_filter *parent, talloc_free(vd); return NULL; } + + codec->codec_desc = ctx->avctx->codec_descriptor->long_name; + return &ctx->public; } diff --git a/video/filter/refqueue.c b/video/filter/refqueue.c index d018e38..f512408 100644 --- a/video/filter/refqueue.c +++ b/video/filter/refqueue.c @@ -39,6 +39,7 @@ struct mp_refqueue { int needed_past_frames; int needed_future_frames; int flags; + int field_parity; bool second_field; // current frame has to output a second field yet bool eof; @@ -97,6 +98,11 @@ void mp_refqueue_set_mode(struct mp_refqueue *q, int flags) q->flags = flags; } +void mp_refqueue_set_parity(struct mp_refqueue *q, int parity) +{ + q->field_parity = parity; +} + // Whether the current frame should be deinterlaced. bool mp_refqueue_should_deint(struct mp_refqueue *q) { @@ -113,8 +119,14 @@ bool mp_refqueue_is_top_field(struct mp_refqueue *q) { if (!mp_refqueue_has_output(q)) return false; - - return !!(q->queue[q->pos]->fields & MP_IMGFIELD_TOP_FIRST) ^ q->second_field; + + bool tff = q->field_parity == MP_FIELD_PARITY_TFF; + bool bff = q->field_parity == MP_FIELD_PARITY_BFF; + bool ret = (!!(q->queue[q->pos]->fields & MP_IMGFIELD_TOP_FIRST) ^ q->second_field + && !tff && !bff); // Default parity + ret = ret || (tff && !q->second_field); // Check if top field is forced + ret = ret || (bff && q->second_field); // Check if bottom field is forced + return ret; } // Whether top-field-first mode is enabled. @@ -123,7 +135,9 @@ bool mp_refqueue_top_field_first(struct mp_refqueue *q) if (!mp_refqueue_has_output(q)) return false; - return q->queue[q->pos]->fields & MP_IMGFIELD_TOP_FIRST; + bool tff = q->field_parity == MP_FIELD_PARITY_TFF; + bool bff = q->field_parity == MP_FIELD_PARITY_BFF; + return ((q->queue[q->pos]->fields & MP_IMGFIELD_TOP_FIRST) || tff) && !bff; } // Discard all state. @@ -319,7 +333,7 @@ bool mp_refqueue_can_output(struct mp_refqueue *q) if (!q->in_format || !!q->in_format->hwctx != !!img->hwctx || (img->hwctx && img->hwctx->data != q->in_format->hwctx->data) || - !mp_image_params_equal(&q->in_format->params, &img->params)) + !mp_image_params_static_equal(&q->in_format->params, &img->params)) { q->next = img; q->eof = true; diff --git a/video/filter/refqueue.h b/video/filter/refqueue.h index 0a8ace0..d14058d 100644 --- a/video/filter/refqueue.h +++ b/video/filter/refqueue.h @@ -29,7 +29,12 @@ enum { MP_MODE_INTERLACED_ONLY = (1 << 2), // only deinterlace marked frames }; +#define MP_FIELD_PARITY_AUTO -1 +#define MP_FIELD_PARITY_TFF 0 +#define MP_FIELD_PARITY_BFF 1 + void mp_refqueue_set_mode(struct mp_refqueue *q, int flags); +void mp_refqueue_set_parity(struct mp_refqueue *q, int parity); bool mp_refqueue_should_deint(struct mp_refqueue *q); bool mp_refqueue_is_top_field(struct mp_refqueue *q); bool mp_refqueue_top_field_first(struct mp_refqueue *q); diff --git a/video/filter/vf_d3d11vpp.c b/video/filter/vf_d3d11vpp.c index 3f00c5a..cedb91d 100644 --- a/video/filter/vf_d3d11vpp.c +++ b/video/filter/vf_d3d11vpp.c @@ -46,6 +46,7 @@ struct opts { bool deint_enabled; bool interlaced_only; int mode; + int field_parity; }; struct priv { @@ -210,8 +211,8 @@ static int recreate_video_proc(struct mp_filter *vf) FALSE, 0); D3D11_VIDEO_PROCESSOR_COLOR_SPACE csp = { - .YCbCr_Matrix = p->params.color.space != MP_CSP_BT_601, - .Nominal_Range = p->params.color.levels == MP_CSP_LEVELS_TV ? 1 : 2, + .YCbCr_Matrix = p->params.repr.sys != PL_COLOR_SYSTEM_BT_601, + .Nominal_Range = p->params.repr.levels == PL_COLOR_LEVELS_LIMITED ? 1 : 2, }; ID3D11VideoContext_VideoProcessorSetStreamColorSpace(p->video_ctx, p->video_proc, @@ -469,6 +470,7 @@ static struct mp_filter *vf_d3d11vpp_create(struct mp_filter *parent, (p->opts->deint_enabled ? MP_MODE_DEINT : 0) | MP_MODE_OUTPUT_FIELDS | (p->opts->interlaced_only ? MP_MODE_INTERLACED_ONLY : 0)); + mp_refqueue_set_parity(p->queue, p->opts->field_parity); return f; @@ -488,6 +490,10 @@ static const m_option_t vf_opts_fields[] = { {"mocomp", D3D11_VIDEO_PROCESSOR_PROCESSOR_CAPS_DEINTERLACE_MOTION_COMPENSATION}, {"ivctc", D3D11_VIDEO_PROCESSOR_PROCESSOR_CAPS_INVERSE_TELECINE}, {"none", 0})}, + {"parity", OPT_CHOICE(field_parity, + {"tff", MP_FIELD_PARITY_TFF}, + {"bff", MP_FIELD_PARITY_BFF}, + {"auto", MP_FIELD_PARITY_AUTO})}, {0} }; @@ -499,6 +505,7 @@ const struct mp_user_filter_entry vf_d3d11vpp = { .priv_defaults = &(const OPT_BASE_STRUCT) { .deint_enabled = true, .mode = D3D11_VIDEO_PROCESSOR_PROCESSOR_CAPS_DEINTERLACE_BOB, + .field_parity = MP_FIELD_PARITY_AUTO, }, .options = vf_opts_fields, }, diff --git a/video/filter/vf_fingerprint.c b/video/filter/vf_fingerprint.c index 8714382..ea2c314 100644 --- a/video/filter/vf_fingerprint.c +++ b/video/filter/vf_fingerprint.c @@ -102,9 +102,10 @@ static void f_process(struct mp_filter *f) // Try to achieve minimum conversion, even if it makes the fingerprints less // "portable" across source video. + p->scaled->params.repr = mpi->params.repr; p->scaled->params.color = mpi->params.color; // Make output always full range; no reason to lose precision. - p->scaled->params.color.levels = MP_CSP_LEVELS_PC; + p->scaled->params.repr.levels = PL_COLOR_LEVELS_FULL; if (!mp_zimg_convert(p->zimg, p->scaled, mpi)) { if (!p->fallback_warning) { diff --git a/video/filter/vf_format.c b/video/filter/vf_format.c index 4997d6f..f226bf2 100644 --- a/video/filter/vf_format.c +++ b/video/filter/vf_format.c @@ -23,6 +23,8 @@ #include <libavutil/rational.h> #include <libavutil/buffer.h> +#include <libavutil/frame.h> +#include <libplacebo/utils/libav.h> #include "common/msg.h" #include "common/common.h" @@ -58,6 +60,7 @@ struct vf_format_opts { bool convert; int force_scaler; bool dovi; + bool hdr10plus; bool film_grain; }; @@ -65,26 +68,26 @@ static void set_params(struct vf_format_opts *p, struct mp_image_params *out, bool set_size) { if (p->colormatrix) - out->color.space = p->colormatrix; + out->repr.sys = p->colormatrix; if (p->colorlevels) - out->color.levels = p->colorlevels; + out->repr.levels = p->colorlevels; if (p->primaries) out->color.primaries = p->primaries; if (p->gamma) { - enum mp_csp_trc in_gamma = p->gamma; - out->color.gamma = p->gamma; - if (in_gamma != out->color.gamma) { + enum pl_color_transfer in_gamma = p->gamma; + out->color.transfer = p->gamma; + if (in_gamma != out->color.transfer) { // When changing the gamma function explicitly, also reset stuff // related to the gamma function since that information will almost // surely be false now and have to be re-inferred out->color.hdr = (struct pl_hdr_metadata){0}; - out->color.light = MP_CSP_LIGHT_AUTO; + out->light = MP_CSP_LIGHT_AUTO; } } if (p->sig_peak) out->color.hdr = (struct pl_hdr_metadata){ .max_luma = p->sig_peak * MP_REF_WHITE }; if (p->light) - out->color.light = p->light; + out->light = p->light; if (p->chroma_location) out->chroma_location = p->chroma_location; if (p->stereo_in) @@ -92,7 +95,7 @@ static void set_params(struct vf_format_opts *p, struct mp_image_params *out, if (p->rotate >= 0) out->rotate = p->rotate; if (p->alpha) - out->alpha = p->alpha; + out->repr.alpha = p->alpha; if (p->w > 0 && set_size) out->w = p->w; @@ -109,6 +112,16 @@ static void set_params(struct vf_format_opts *p, struct mp_image_params *out, mp_image_params_set_dsize(out, dsize.num, dsize.den); } +static inline void *get_side_data(const struct mp_image *mpi, + enum AVFrameSideDataType type) +{ + for (int i = 0; i < mpi->num_ff_side_data; i++) { + if (mpi->ff_side_data[i].type == type) + return (void *)mpi->ff_side_data[i].buf->data; + } + return NULL; +} + static void vf_format_process(struct mp_filter *f) { struct priv *priv = f->priv; @@ -122,10 +135,10 @@ static void vf_format_process(struct mp_filter *f) int outfmt = priv->opts->fmt; // If we convert from RGB to YUV, default to limited range. - if (mp_imgfmt_get_forced_csp(img->imgfmt) == MP_CSP_RGB && - outfmt && mp_imgfmt_get_forced_csp(outfmt) == MP_CSP_AUTO) + if (mp_imgfmt_get_forced_csp(img->imgfmt) == PL_COLOR_SYSTEM_RGB && + outfmt && mp_imgfmt_get_forced_csp(outfmt) == PL_COLOR_SYSTEM_UNKNOWN) { - par.color.levels = MP_CSP_LEVELS_TV; + par.repr.levels = PL_COLOR_LEVELS_LIMITED; } set_params(priv->opts, &par, true); @@ -155,8 +168,22 @@ static void vf_format_process(struct mp_filter *f) } if (!priv->opts->dovi) { - av_buffer_unref(&img->dovi); - av_buffer_unref(&img->dovi_buf); + if (img->params.repr.sys == PL_COLOR_SYSTEM_DOLBYVISION) + img->params.repr.sys = PL_COLOR_SYSTEM_BT_2020_NC; + // Map again to strip any DV metadata set to common fields. + img->params.color.hdr = (struct pl_hdr_metadata){0}; + pl_map_hdr_metadata(&img->params.color.hdr, &(struct pl_av_hdr_metadata) { + .mdm = get_side_data(img, AV_FRAME_DATA_MASTERING_DISPLAY_METADATA), + .clm = get_side_data(img, AV_FRAME_DATA_CONTENT_LIGHT_LEVEL), + .dhp = get_side_data(img, AV_FRAME_DATA_DYNAMIC_HDR_PLUS), + }); + } + + if (!priv->opts->hdr10plus) { + memset(img->params.color.hdr.scene_max, 0, + sizeof(img->params.color.hdr.scene_max)); + img->params.color.hdr.scene_avg = 0; + img->params.color.hdr.ootf = (struct pl_hdr_bezier){0}; } if (!priv->opts->film_grain) @@ -204,16 +231,16 @@ static struct mp_filter *vf_format_create(struct mp_filter *parent, void *option #define OPT_BASE_STRUCT struct vf_format_opts static const m_option_t vf_opts_fields[] = { {"fmt", OPT_IMAGEFORMAT(fmt)}, - {"colormatrix", OPT_CHOICE_C(colormatrix, mp_csp_names)}, - {"colorlevels", OPT_CHOICE_C(colorlevels, mp_csp_levels_names)}, - {"primaries", OPT_CHOICE_C(primaries, mp_csp_prim_names)}, - {"gamma", OPT_CHOICE_C(gamma, mp_csp_trc_names)}, + {"colormatrix", OPT_CHOICE_C(colormatrix, pl_csp_names)}, + {"colorlevels", OPT_CHOICE_C(colorlevels, pl_csp_levels_names)}, + {"primaries", OPT_CHOICE_C(primaries, pl_csp_prim_names)}, + {"gamma", OPT_CHOICE_C(gamma, pl_csp_trc_names)}, {"sig-peak", OPT_FLOAT(sig_peak)}, {"light", OPT_CHOICE_C(light, mp_csp_light_names)}, - {"chroma-location", OPT_CHOICE_C(chroma_location, mp_chroma_names)}, + {"chroma-location", OPT_CHOICE_C(chroma_location, pl_chroma_names)}, {"stereo-in", OPT_CHOICE_C(stereo_in, mp_stereo3d_names)}, {"rotate", OPT_INT(rotate), M_RANGE(-1, 359)}, - {"alpha", OPT_CHOICE_C(alpha, mp_alpha_names)}, + {"alpha", OPT_CHOICE_C(alpha, pl_alpha_names)}, {"w", OPT_INT(w)}, {"h", OPT_INT(h)}, {"dw", OPT_INT(dw)}, @@ -221,6 +248,7 @@ static const m_option_t vf_opts_fields[] = { {"dar", OPT_DOUBLE(dar)}, {"convert", OPT_BOOL(convert)}, {"dolbyvision", OPT_BOOL(dovi)}, + {"hdr10plus", OPT_BOOL(hdr10plus)}, {"film-grain", OPT_BOOL(film_grain)}, {"force-scaler", OPT_CHOICE(force_scaler, {"auto", MP_SWS_AUTO}, @@ -237,6 +265,7 @@ const struct mp_user_filter_entry vf_format = { .priv_defaults = &(const OPT_BASE_STRUCT){ .rotate = -1, .dovi = true, + .hdr10plus = true, .film_grain = true, }, .options = vf_opts_fields, diff --git a/video/filter/vf_gpu.c b/video/filter/vf_gpu.c index fb11941..e19faae 100644 --- a/video/filter/vf_gpu.c +++ b/video/filter/vf_gpu.c @@ -166,12 +166,14 @@ static struct mp_image *gpu_render_frame(struct mp_filter *f, struct mp_image *i bool need_reconfig = m_config_cache_update(priv->vo_opts_cache); - if (!mp_image_params_equal(&priv->img_params, &in->params)) { - priv->img_params = in->params; + if (!mp_image_params_static_equal(&priv->img_params, &in->params)) { gl_video_config(priv->renderer, &in->params); need_reconfig = true; } + if (!mp_image_params_equal(&priv->img_params, &in->params)) + priv->img_params = in->params; + if (need_reconfig) { struct mp_rect src, dst; struct mp_osd_res osd; @@ -212,7 +214,7 @@ static struct mp_image *gpu_render_frame(struct mp_filter *f, struct mp_image *i // (it doesn't have access to the OSD though) int flags = RENDER_FRAME_SUBS | RENDER_FRAME_VF_SUBS; - gl_video_render_frame(priv->renderer, &frame, (struct ra_fbo){priv->target}, + gl_video_render_frame(priv->renderer, &frame, &(struct ra_fbo){priv->target}, flags); res = mp_image_alloc(IMGFMT_RGB0, w, h); diff --git a/video/filter/vf_vapoursynth.c b/video/filter/vf_vapoursynth.c index 583a196..0b798c8 100644 --- a/video/filter/vf_vapoursynth.c +++ b/video/filter/vf_vapoursynth.c @@ -27,6 +27,7 @@ #include <libavutil/rational.h> #include <libavutil/cpu.h> +#include <libplacebo/utils/libav.h> #include "common/msg.h" #include "filters/f_autoconvert.h" @@ -184,16 +185,16 @@ static void copy_mp_to_vs_frame_props_map(struct priv *p, VSMap *map, struct mp_image_params *params = &img->params; p->vsapi->propSetInt(map, "_SARNum", params->p_w, 0); p->vsapi->propSetInt(map, "_SARDen", params->p_h, 0); - if (params->color.levels) { + if (params->repr.levels) { p->vsapi->propSetInt(map, "_ColorRange", - params->color.levels == MP_CSP_LEVELS_TV, 0); + params->repr.levels == PL_COLOR_LEVELS_LIMITED, 0); } // The docs explicitly say it uses libavcodec values. p->vsapi->propSetInt(map, "_ColorSpace", - mp_csp_to_avcol_spc(params->color.space), 0); + pl_system_to_av(params->repr.sys), 0); if (params->chroma_location) { p->vsapi->propSetInt(map, "_ChromaLocation", - params->chroma_location == MP_CHROMA_CENTER, 0); + params->chroma_location == PL_CHROMA_CENTER, 0); } char pict_type = 0; switch (img->pict_type) { diff --git a/video/filter/vf_vavpp.c b/video/filter/vf_vavpp.c index 52be148..960c745 100644 --- a/video/filter/vf_vavpp.c +++ b/video/filter/vf_vavpp.c @@ -51,6 +51,7 @@ struct pipeline { struct opts { int deint_type; + int field_parity; bool interlaced_only; bool reversal_bug; }; @@ -143,11 +144,13 @@ static void update_pipeline(struct mp_filter *vf) (p->do_deint ? MP_MODE_DEINT : 0) | (p->opts->deint_type >= 2 ? MP_MODE_OUTPUT_FIELDS : 0) | (p->opts->interlaced_only ? MP_MODE_INTERLACED_ONLY : 0)); + mp_refqueue_set_parity(p->queue, p->opts->field_parity); return; nodeint: mp_refqueue_set_refs(p->queue, 0, 0); mp_refqueue_set_mode(p->queue, 0); + mp_refqueue_set_parity(p->queue, p->opts->field_parity); } static struct mp_image *alloc_out(struct mp_filter *vf) @@ -208,7 +211,7 @@ static struct mp_image *render(struct mp_filter *vf) mp_image_copy_attributes(img, in); - unsigned int flags = va_get_colorspace_flag(p->params.color.space); + unsigned int flags = va_get_colorspace_flag(p->params.repr.sys); if (!mp_refqueue_should_deint(p->queue)) { flags |= VA_FRAME_PICTURE; } else if (mp_refqueue_is_top_field(p->queue)) { @@ -485,6 +488,10 @@ static const m_option_t vf_opts_fields[] = { {"motion-compensated", 5})}, {"interlaced-only", OPT_BOOL(interlaced_only)}, {"reversal-bug", OPT_BOOL(reversal_bug)}, + {"parity", OPT_CHOICE(field_parity, + {"tff", MP_FIELD_PARITY_TFF}, + {"bff", MP_FIELD_PARITY_BFF}, + {"auto", MP_FIELD_PARITY_AUTO})}, {0} }; @@ -496,6 +503,7 @@ const struct mp_user_filter_entry vf_vavpp = { .priv_defaults = &(const OPT_BASE_STRUCT){ .deint_type = -1, .reversal_bug = true, + .field_parity = MP_FIELD_PARITY_AUTO, }, .options = vf_opts_fields, }, diff --git a/video/filter/vf_vdpaupp.c b/video/filter/vf_vdpaupp.c index 0519f5a..b5434cd 100644 --- a/video/filter/vf_vdpaupp.c +++ b/video/filter/vf_vdpaupp.c @@ -43,6 +43,7 @@ struct opts { bool deint_enabled; bool interlaced_only; + int field_parity; struct mp_vdpau_mixer_opts opts; }; @@ -74,8 +75,7 @@ static void vf_vdpaupp_process(struct mp_filter *f) struct mp_image *mpi = mp_vdpau_mixed_frame_create(mp_refqueue_get_field(p->queue, 0)); - if (!mpi) - return; // OOM + MP_HANDLE_OOM(mpi); struct mp_vdpau_mixer_frame *frame = mp_vdpau_mixed_frame_get(mpi); if (!mp_refqueue_should_deint(p->queue)) { @@ -157,6 +157,8 @@ static struct mp_filter *vf_vdpaupp_create(struct mp_filter *parent, void *optio (p->opts->interlaced_only ? MP_MODE_INTERLACED_ONLY : 0) | (p->opts->opts.deint >= 2 ? MP_MODE_OUTPUT_FIELDS : 0)); + mp_refqueue_set_parity(p->queue, p->opts->field_parity); + mp_refqueue_add_in_format(p->queue, IMGFMT_VDPAU, 0); return f; @@ -181,6 +183,10 @@ static const m_option_t vf_opts_fields[] = { {"sharpen", OPT_FLOAT(opts.sharpen), M_RANGE(-1, 1)}, {"hqscaling", OPT_INT(opts.hqscaling), M_RANGE(0, 9)}, {"interlaced-only", OPT_BOOL(interlaced_only)}, + {"parity", OPT_CHOICE(field_parity, + {"tff", MP_FIELD_PARITY_TFF}, + {"bff", MP_FIELD_PARITY_BFF}, + {"auto", MP_FIELD_PARITY_AUTO})}, {0} }; @@ -189,6 +195,9 @@ const struct mp_user_filter_entry vf_vdpaupp = { .description = "vdpau postprocessing", .name = "vdpaupp", .priv_size = sizeof(OPT_BASE_STRUCT), + .priv_defaults = &(const OPT_BASE_STRUCT){ + .field_parity = MP_FIELD_PARITY_AUTO, + }, .options = vf_opts_fields, }, .create = vf_vdpaupp_create, diff --git a/video/fmt-conversion.c b/video/fmt-conversion.c index aa7d857..39dead4 100644 --- a/video/fmt-conversion.c +++ b/video/fmt-conversion.c @@ -66,9 +66,7 @@ static const struct { {IMGFMT_CUDA, AV_PIX_FMT_CUDA}, {IMGFMT_P010, AV_PIX_FMT_P010}, {IMGFMT_DRMPRIME, AV_PIX_FMT_DRM_PRIME}, -#if HAVE_VULKAN_INTEROP {IMGFMT_VULKAN, AV_PIX_FMT_VULKAN}, -#endif {0, AV_PIX_FMT_NONE} }; diff --git a/video/image_writer.c b/video/image_writer.c index 288d809..cc79573 100644 --- a/video/image_writer.c +++ b/video/image_writer.c @@ -19,12 +19,14 @@ #include <stdlib.h> #include <string.h> #include <inttypes.h> +#include <unistd.h> #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libavutil/mem.h> #include <libavutil/opt.h> #include <libavutil/pixdesc.h> +#include <libplacebo/utils/libav.h> #include "common/msg.h" #include "config.h" @@ -35,6 +37,7 @@ #endif #include "osdep/io.h" +#include "misc/path_utils.h" #include "common/av_common.h" #include "common/msg.h" @@ -59,12 +62,10 @@ const struct image_writer_opts image_writer_opts_defaults = { .jxl_distance = 1.0, .jxl_effort = 4, .avif_encoder = "libaom-av1", - .avif_pixfmt = "yuv420p", .avif_opts = (char*[]){ "usage", "allintra", - "crf", "32", + "crf", "0", "cpu-used", "8", - "tune", "ssim", NULL }, .tag_csp = true, @@ -136,19 +137,18 @@ static void prepare_avframe(AVFrame *pic, AVCodecContext *avctx, pic->format = avctx->pix_fmt; pic->width = avctx->width; pic->height = avctx->height; - avctx->color_range = pic->color_range = - mp_csp_levels_to_avcol_range(image->params.color.levels); + pl_avframe_set_repr(pic, image->params.repr); + avctx->colorspace = pic->colorspace; + avctx->color_range = pic->color_range; if (!tag_csp) return; - avctx->color_primaries = pic->color_primaries = - mp_csp_prim_to_avcol_pri(image->params.color.primaries); - avctx->color_trc = pic->color_trc = - mp_csp_trc_to_avcol_trc(image->params.color.gamma); - avctx->colorspace = pic->colorspace = - mp_csp_to_avcol_spc(image->params.color.space); + pl_avframe_set_color(pic, image->params.color); + avctx->color_primaries = pic->color_primaries; + avctx->color_trc = pic->color_trc; avctx->chroma_sample_location = pic->chroma_location = - mp_chroma_location_to_av(image->params.chroma_location); + pl_chroma_to_av(image->params.chroma_location); + mp_dbg(log, "mapped color params:\n" " trc = %s\n" " primaries = %s\n" @@ -163,14 +163,8 @@ static void prepare_avframe(AVFrame *pic, AVCodecContext *avctx, ); } -static bool write_lavc(struct image_writer_ctx *ctx, mp_image_t *image, const char *filename) +static bool write_lavc(struct image_writer_ctx *ctx, mp_image_t *image, FILE *fp) { - FILE *fp = fopen(filename, "wb"); - if (!fp) { - MP_ERR(ctx, "Error opening '%s' for writing!\n", filename); - return false; - } - bool success = false; AVFrame *pic = NULL; AVPacket *pkt = NULL; @@ -195,7 +189,7 @@ static bool write_lavc(struct image_writer_ctx *ctx, mp_image_t *image, const ch avctx->pix_fmt = imgfmt2pixfmt(image->imgfmt); if (codec->id == AV_CODEC_ID_MJPEG) { // Annoying deprecated garbage for the jpg encoder. - if (image->params.color.levels == MP_CSP_LEVELS_PC) + if (image->params.repr.levels == PL_COLOR_LEVELS_FULL) avctx->pix_fmt = replace_j_format(avctx->pix_fmt); } if (avctx->pix_fmt == AV_PIX_FMT_NONE) { @@ -260,7 +254,7 @@ error_exit: avcodec_free_context(&avctx); av_frame_free(&pic); av_packet_free(&pkt); - return !fclose(fp) && success; + return success; } #if HAVE_JPEG @@ -274,15 +268,8 @@ static void write_jpeg_error_exit(j_common_ptr cinfo) longjmp(*(jmp_buf*)cinfo->client_data, 1); } -static bool write_jpeg(struct image_writer_ctx *ctx, mp_image_t *image, - const char *filename) +static bool write_jpeg(struct image_writer_ctx *ctx, mp_image_t *image, FILE *fp) { - FILE *fp = fopen(filename, "wb"); - if (!fp) { - MP_ERR(ctx, "Error opening '%s' for writing!\n", filename); - return false; - } - struct jpeg_compress_struct cinfo; struct jpeg_error_mgr jerr; @@ -293,7 +280,6 @@ static bool write_jpeg(struct image_writer_ctx *ctx, mp_image_t *image, cinfo.client_data = &error_return_jmpbuf; if (setjmp(cinfo.client_data)) { jpeg_destroy_compress(&cinfo); - fclose(fp); return false; } @@ -330,7 +316,7 @@ static bool write_jpeg(struct image_writer_ctx *ctx, mp_image_t *image, jpeg_destroy_compress(&cinfo); - return !fclose(fp); + return true; } #endif @@ -354,8 +340,7 @@ static void log_side_data(struct image_writer_ctx *ctx, AVPacketSideData *data, } } -static bool write_avif(struct image_writer_ctx *ctx, mp_image_t *image, - const char *filename) +static bool write_avif(struct image_writer_ctx *ctx, mp_image_t *image, FILE *fp) { const AVCodec *codec = NULL; const AVOutputFormat *ofmt = NULL; @@ -423,11 +408,8 @@ static bool write_avif(struct image_writer_ctx *ctx, mp_image_t *image, goto free_data; } - ret = avio_open(&avioctx, filename, AVIO_FLAG_WRITE); - if (ret < 0) { - MP_ERR(ctx, "Could not open file '%s' for saving images\n", filename); - goto free_data; - } + avio_open_dyn_buf(&avioctx); + MP_HANDLE_OOM(avioctx); fmtctx = avformat_alloc_context(); if (!fmtctx) { @@ -505,10 +487,12 @@ static bool write_avif(struct image_writer_ctx *ctx, mp_image_t *image, } MP_DBG(ctx, "write_avif(): avio_size() = %"PRIi64"\n", avio_size(avioctx)); - success = true; + uint8_t *buf = NULL; + int written_size = avio_close_dyn_buf(avioctx, &buf); + success = fwrite(buf, written_size, 1, fp) == 1; + av_freep(&buf); free_data: - success = !avio_closep(&avioctx) && success; avformat_free_context(fmtctx); avcodec_free_context(&avctx); av_packet_free(&pkt); @@ -616,7 +600,7 @@ int image_writer_format_from_ext(const char *ext) } static struct mp_image *convert_image(struct mp_image *image, int destfmt, - enum mp_csp_levels yuv_levels, + enum pl_color_levels yuv_levels, const struct image_writer_opts *opts, struct mpv_global *global, struct mp_log *log) @@ -631,19 +615,22 @@ static struct mp_image *convert_image(struct mp_image *image, int destfmt, .p_w = 1, .p_h = 1, .color = image->params.color, + .repr = image->params.repr, + .chroma_location = image->params.chroma_location, + .crop = {0, 0, d_w, d_h}, }; mp_image_params_guess_csp(&p); if (!image_writer_flexible_csp(opts)) { // If our format can't tag csps, set something sane - p.color.primaries = MP_CSP_PRIM_BT_709; - p.color.gamma = MP_CSP_TRC_AUTO; - p.color.light = MP_CSP_LIGHT_DISPLAY; + p.color.primaries = PL_COLOR_PRIM_BT_709; + p.color.transfer = PL_COLOR_TRC_UNKNOWN; + p.light = MP_CSP_LIGHT_DISPLAY; p.color.hdr = (struct pl_hdr_metadata){0}; - if (p.color.space != MP_CSP_RGB) { - p.color.levels = yuv_levels; - p.color.space = MP_CSP_BT_601; - p.chroma_location = MP_CHROMA_CENTER; + if (p.repr.sys != PL_COLOR_SYSTEM_RGB) { + p.repr.levels = yuv_levels; + p.repr.sys = PL_COLOR_SYSTEM_BT_601; + p.chroma_location = PL_CHROMA_CENTER; } mp_image_params_guess_csp(&p); } @@ -651,7 +638,7 @@ static struct mp_image *convert_image(struct mp_image *image, int destfmt, if (mp_image_params_equal(&p, &image->params)) return mp_image_new_ref(image); - mp_dbg(log, "will convert image to %s\n", mp_imgfmt_to_name(p.imgfmt)); + mp_verbose(log, "will convert image to %s\n", mp_imgfmt_to_name(p.imgfmt)); struct mp_image *src = image; if (mp_image_crop_valid(&src->params) && @@ -696,16 +683,16 @@ static struct mp_image *convert_image(struct mp_image *image, int destfmt, bool write_image(struct mp_image *image, const struct image_writer_opts *opts, const char *filename, struct mpv_global *global, - struct mp_log *log) + struct mp_log *log, bool overwrite) { struct image_writer_opts defs = image_writer_opts_defaults; if (!opts) opts = &defs; - mp_dbg(log, "input: %s\n", mp_image_params_to_str(&image->params)); + mp_verbose(log, "input: %s\n", mp_image_params_to_str(&image->params)); struct image_writer_ctx ctx = { log, opts, image->fmt }; - bool (*write)(struct image_writer_ctx *, mp_image_t *, const char *) = write_lavc; + bool (*write)(struct image_writer_ctx *, mp_image_t *, FILE *) = write_lavc; int destfmt = 0; #if HAVE_JPEG @@ -717,7 +704,8 @@ bool write_image(struct mp_image *image, const struct image_writer_opts *opts, #if HAVE_AVIF_MUXER if (opts->format == AV_CODEC_ID_AV1) { write = write_avif; - destfmt = mp_imgfmt_from_name(bstr0(opts->avif_pixfmt)); + if (opts->avif_pixfmt && opts->avif_pixfmt[0]) + destfmt = mp_imgfmt_from_name(bstr0(opts->avif_pixfmt)); } #endif if (opts->format == AV_CODEC_ID_WEBP && !opts->webp_lossless) { @@ -730,21 +718,32 @@ bool write_image(struct mp_image *image, const struct image_writer_opts *opts, if (!destfmt) destfmt = get_target_format(&ctx); - enum mp_csp_levels levels; // Ignored if destfmt is a RGB format + enum pl_color_levels levels; // Ignored if destfmt is a RGB format if (opts->format == AV_CODEC_ID_WEBP) { - levels = MP_CSP_LEVELS_TV; + levels = PL_COLOR_LEVELS_LIMITED; } else { - levels = MP_CSP_LEVELS_PC; + levels = PL_COLOR_LEVELS_FULL; } struct mp_image *dst = convert_image(image, destfmt, levels, opts, global, log); if (!dst) return false; - bool success = write(&ctx, dst, filename); - if (!success) + bool success = false; + FILE *fp = fopen(filename, overwrite ? "wb" : "wbx"); + if (!fp) { + mp_err(log, "Error creating '%s' for writing: %s!\n", + filename, mp_strerror(errno)); + goto done; + } + + success = write(&ctx, dst, fp); + if (fclose(fp) || !success) { mp_err(log, "Error writing file '%s'!\n", filename); + unlink(filename); + } +done: talloc_free(dst); return success; } @@ -753,5 +752,5 @@ void dump_png(struct mp_image *image, const char *filename, struct mp_log *log) { struct image_writer_opts opts = image_writer_opts_defaults; opts.format = AV_CODEC_ID_PNG; - write_image(image, &opts, filename, NULL, log); + write_image(image, &opts, filename, NULL, log, true); } diff --git a/video/image_writer.h b/video/image_writer.h index 72d1602..c22606e 100644 --- a/video/image_writer.h +++ b/video/image_writer.h @@ -68,7 +68,7 @@ int image_writer_format_from_ext(const char *ext); */ bool write_image(struct mp_image *image, const struct image_writer_opts *opts, const char *filename, struct mpv_global *global, - struct mp_log *log); + struct mp_log *log, bool overwrite); // Debugging helper. void dump_png(struct mp_image *image, const char *filename, struct mp_log *log); diff --git a/video/img_format.c b/video/img_format.c index 6b7857f..c9e2a3c 100644 --- a/video/img_format.c +++ b/video/img_format.c @@ -664,18 +664,18 @@ static bool validate_regular_imgfmt(const struct mp_regular_imgfmt *fmt) return true; } -static enum mp_csp get_forced_csp_from_flags(int flags) +static enum pl_color_system get_forced_csp_from_flags(int flags) { if (flags & MP_IMGFLAG_COLOR_XYZ) - return MP_CSP_XYZ; + return PL_COLOR_SYSTEM_XYZ; if (flags & MP_IMGFLAG_COLOR_RGB) - return MP_CSP_RGB; + return PL_COLOR_SYSTEM_RGB; - return MP_CSP_AUTO; + return PL_COLOR_SYSTEM_UNKNOWN; } -enum mp_csp mp_imgfmt_get_forced_csp(int imgfmt) +enum pl_color_system mp_imgfmt_get_forced_csp(int imgfmt) { return get_forced_csp_from_flags(mp_imgfmt_get_desc(imgfmt).flags); } diff --git a/video/img_format.h b/video/img_format.h index 0753829..975f58a 100644 --- a/video/img_format.h +++ b/video/img_format.h @@ -155,9 +155,9 @@ int mp_imgfmt_desc_get_num_comps(struct mp_imgfmt_desc *desc); // luma pixel. luma_offsets[0] == mp_imgfmt_desc.comps[0].offset. bool mp_imgfmt_get_packed_yuv_locations(int imgfmt, uint8_t *luma_offsets); -// MP_CSP_AUTO for YUV, MP_CSP_RGB or MP_CSP_XYZ otherwise. +// PL_COLOR_SYSTEM_UNKNOWN for YUV, PL_COLOR_SYSTEM_RGB or PL_COLOR_SYSTEM_XYZ otherwise. // (Because IMGFMT/AV_PIX_FMT conflate format and csp for RGB and XYZ.) -enum mp_csp mp_imgfmt_get_forced_csp(int imgfmt); +enum pl_color_system mp_imgfmt_get_forced_csp(int imgfmt); enum mp_component_type { MP_COMPONENT_TYPE_UNKNOWN = 0, @@ -184,7 +184,7 @@ struct mp_regular_imgfmt { // See mp_imgfmt_get_forced_csp(). Normally code should use // mp_image_params.colors. This field is only needed to map the format // unambiguously to FFmpeg formats. - enum mp_csp forced_csp; + enum pl_color_system forced_csp; // Size of each component in bytes. uint8_t component_size; @@ -313,9 +313,7 @@ enum mp_imgfmt { IMGFMT_VDPAU_OUTPUT, // VdpOutputSurface IMGFMT_VAAPI, IMGFMT_VIDEOTOOLBOX, // CVPixelBufferRef -#if HAVE_VULKAN_INTEROP IMGFMT_VULKAN, // VKImage -#endif IMGFMT_DRMPRIME, // AVDRMFrameDescriptor // Generic pass-through of AV_PIX_FMT_*. Used for formats which don't have diff --git a/video/mp_image.c b/video/mp_image.c index dff2051..609fb17 100644 --- a/video/mp_image.c +++ b/video/mp_image.c @@ -192,13 +192,11 @@ static bool mp_image_alloc_planes(struct mp_image *mpi) void mp_image_setfmt(struct mp_image *mpi, int out_fmt) { - struct mp_image_params params = mpi->params; struct mp_imgfmt_desc fmt = mp_imgfmt_get_desc(out_fmt); - params.imgfmt = fmt.id; + mpi->params.imgfmt = fmt.id; mpi->fmt = fmt; mpi->imgfmt = fmt.id; mpi->num_planes = fmt.num_planes; - mpi->params = params; } static void mp_image_destructor(void *ptr) @@ -211,7 +209,6 @@ static void mp_image_destructor(void *ptr) av_buffer_unref(&mpi->a53_cc); av_buffer_unref(&mpi->dovi); av_buffer_unref(&mpi->film_grain); - av_buffer_unref(&mpi->dovi_buf); for (int n = 0; n < mpi->num_ff_side_data; n++) av_buffer_unref(&mpi->ff_side_data[n].buf); talloc_free(mpi->ff_side_data); @@ -346,7 +343,6 @@ struct mp_image *mp_image_new_ref(struct mp_image *img) ref_buffer(&new->a53_cc); ref_buffer(&new->dovi); ref_buffer(&new->film_grain); - ref_buffer(&new->dovi_buf); new->ff_side_data = talloc_memdup(NULL, new->ff_side_data, new->num_ff_side_data * sizeof(new->ff_side_data[0])); @@ -384,7 +380,6 @@ struct mp_image *mp_image_new_dummy_ref(struct mp_image *img) new->a53_cc = NULL; new->dovi = NULL; new->film_grain = NULL; - new->dovi_buf = NULL; new->num_ff_side_data = 0; new->ff_side_data = NULL; return new; @@ -493,7 +488,7 @@ void mp_image_copy(struct mp_image *dst, struct mp_image *src) memcpy(dst->planes[1], src->planes[1], AVPALETTE_SIZE); } -static enum mp_csp mp_image_params_get_forced_csp(struct mp_image_params *params) +static enum pl_color_system mp_image_params_get_forced_csp(struct mp_image_params *params) { int imgfmt = params->hw_subfmt ? params->hw_subfmt : params->imgfmt; return mp_imgfmt_get_forced_csp(imgfmt); @@ -522,15 +517,16 @@ void mp_image_copy_attributes(struct mp_image *dst, struct mp_image *src) dst->params.p_w = src->params.p_w; dst->params.p_h = src->params.p_h; dst->params.color = src->params.color; + dst->params.repr = src->params.repr; + dst->params.light = src->params.light; dst->params.chroma_location = src->params.chroma_location; - dst->params.alpha = src->params.alpha; dst->params.crop = src->params.crop; dst->nominal_fps = src->nominal_fps; // ensure colorspace consistency - enum mp_csp dst_forced_csp = mp_image_params_get_forced_csp(&dst->params); + enum pl_color_system dst_forced_csp = mp_image_params_get_forced_csp(&dst->params); if (mp_image_params_get_forced_csp(&src->params) != dst_forced_csp) { - dst->params.color.space = dst_forced_csp != MP_CSP_AUTO ? + dst->params.repr.sys = dst_forced_csp != PL_COLOR_SYSTEM_UNKNOWN ? dst_forced_csp : mp_csp_guess_colorspace(src->w, src->h); } @@ -543,7 +539,6 @@ void mp_image_copy_attributes(struct mp_image *dst, struct mp_image *src) } assign_bufref(&dst->icc_profile, src->icc_profile); assign_bufref(&dst->dovi, src->dovi); - assign_bufref(&dst->dovi_buf, src->dovi_buf); assign_bufref(&dst->film_grain, src->film_grain); assign_bufref(&dst->a53_cc, src->a53_cc); @@ -670,8 +665,8 @@ void mp_image_clear(struct mp_image *img, int x0, int y0, int x1, int y1) plane_size[cd->plane] = plane_bits / 8u; int depth = cd->size + MPMIN(cd->pad, 0); double m, o; - mp_get_csp_uint_mul(area.params.color.space, - area.params.color.levels, + mp_get_csp_uint_mul(area.params.repr.sys, + area.params.repr.levels, depth, c + 1, &m, &o); uint64_t val = MPCLAMP(lrint((0 - o) / m), 0, 1ull << depth); plane_clear_i[cd->plane] |= val << cd->offset; @@ -773,13 +768,13 @@ char *mp_image_params_to_str_buf(char *b, size_t bs, if (p->hw_subfmt) mp_snprintf_cat(b, bs, "[%s]", mp_imgfmt_to_name(p->hw_subfmt)); mp_snprintf_cat(b, bs, " %s/%s/%s/%s/%s", - m_opt_choice_str(mp_csp_names, p->color.space), - m_opt_choice_str(mp_csp_prim_names, p->color.primaries), - m_opt_choice_str(mp_csp_trc_names, p->color.gamma), - m_opt_choice_str(mp_csp_levels_names, p->color.levels), - m_opt_choice_str(mp_csp_light_names, p->color.light)); + m_opt_choice_str(pl_csp_names, p->repr.sys), + m_opt_choice_str(pl_csp_prim_names, p->color.primaries), + m_opt_choice_str(pl_csp_trc_names, p->color.transfer), + m_opt_choice_str(pl_csp_levels_names, p->repr.levels), + m_opt_choice_str(mp_csp_light_names, p->light)); mp_snprintf_cat(b, bs, " CL=%s", - m_opt_choice_str(mp_chroma_names, p->chroma_location)); + m_opt_choice_str(pl_chroma_names, p->chroma_location)); if (mp_image_crop_valid(p)) { mp_snprintf_cat(b, bs, " crop=%dx%d+%d+%d", mp_rect_w(p->crop), mp_rect_h(p->crop), p->crop.x0, p->crop.y0); @@ -790,9 +785,9 @@ char *mp_image_params_to_str_buf(char *b, size_t bs, mp_snprintf_cat(b, bs, " stereo=%s", MP_STEREO3D_NAME_DEF(p->stereo3d, "?")); } - if (p->alpha) { + if (p->repr.alpha) { mp_snprintf_cat(b, bs, " A=%s", - m_opt_choice_str(mp_alpha_names, p->alpha)); + m_opt_choice_str(pl_alpha_names, p->repr.alpha)); } } else { snprintf(b, bs, "???"); @@ -836,14 +831,26 @@ bool mp_image_params_equal(const struct mp_image_params *p1, p1->w == p2->w && p1->h == p2->h && p1->p_w == p2->p_w && p1->p_h == p2->p_h && p1->force_window == p2->force_window && - mp_colorspace_equal(p1->color, p2->color) && + pl_color_space_equal(&p1->color, &p2->color) && + pl_color_repr_equal(&p1->repr, &p2->repr) && + p1->light == p2->light && p1->chroma_location == p2->chroma_location && p1->rotate == p2->rotate && p1->stereo3d == p2->stereo3d && - p1->alpha == p2->alpha && mp_rect_equals(&p1->crop, &p2->crop); } +bool mp_image_params_static_equal(const struct mp_image_params *p1, + const struct mp_image_params *p2) +{ + // Compare only static video parameters, excluding dynamic metadata. + struct mp_image_params a = *p1; + struct mp_image_params b = *p2; + a.repr.dovi = b.repr.dovi = NULL; + a.color.hdr = b.color.hdr = (struct pl_hdr_metadata){0}; + return mp_image_params_equal(&a, &b); +} + // Set most image parameters, but not image format or size. // Display size is used to set the PAR. void mp_image_set_attributes(struct mp_image *image, @@ -853,12 +860,14 @@ void mp_image_set_attributes(struct mp_image *image, nparams.imgfmt = image->imgfmt; nparams.w = image->w; nparams.h = image->h; - if (nparams.imgfmt != params->imgfmt) - nparams.color = (struct mp_colorspace){0}; + if (nparams.imgfmt != params->imgfmt) { + nparams.repr = (struct pl_color_repr){0}; + nparams.color = (struct pl_color_space){0}; + } mp_image_set_params(image, &nparams); } -static enum mp_csp_levels infer_levels(enum mp_imgfmt imgfmt) +static enum pl_color_levels infer_levels(enum mp_imgfmt imgfmt) { switch (imgfmt2pixfmt(imgfmt)) { case AV_PIX_FMT_YUVJ420P: @@ -880,9 +889,9 @@ static enum mp_csp_levels infer_levels(enum mp_imgfmt imgfmt) case AV_PIX_FMT_GRAY16BE: case AV_PIX_FMT_YA16BE: case AV_PIX_FMT_YA16LE: - return MP_CSP_LEVELS_PC; + return PL_COLOR_LEVELS_FULL; default: - return MP_CSP_LEVELS_TV; + return PL_COLOR_LEVELS_LIMITED; } } @@ -891,100 +900,103 @@ static enum mp_csp_levels infer_levels(enum mp_imgfmt imgfmt) // the colorspace as implied by the pixel format. void mp_image_params_guess_csp(struct mp_image_params *params) { - enum mp_csp forced_csp = mp_image_params_get_forced_csp(params); - if (forced_csp == MP_CSP_AUTO) { // YUV/other - if (params->color.space != MP_CSP_BT_601 && - params->color.space != MP_CSP_BT_709 && - params->color.space != MP_CSP_BT_2020_NC && - params->color.space != MP_CSP_BT_2020_C && - params->color.space != MP_CSP_SMPTE_240M && - params->color.space != MP_CSP_YCGCO) + enum pl_color_system forced_csp = mp_image_params_get_forced_csp(params); + if (forced_csp == PL_COLOR_SYSTEM_UNKNOWN) { // YUV/other + if (params->repr.sys != PL_COLOR_SYSTEM_BT_601 && + params->repr.sys != PL_COLOR_SYSTEM_BT_709 && + params->repr.sys != PL_COLOR_SYSTEM_BT_2020_NC && + params->repr.sys != PL_COLOR_SYSTEM_BT_2020_C && + params->repr.sys != PL_COLOR_SYSTEM_BT_2100_PQ && + params->repr.sys != PL_COLOR_SYSTEM_BT_2100_HLG && + params->repr.sys != PL_COLOR_SYSTEM_DOLBYVISION && + params->repr.sys != PL_COLOR_SYSTEM_SMPTE_240M && + params->repr.sys != PL_COLOR_SYSTEM_YCGCO) { // Makes no sense, so guess instead // YCGCO should be separate, but libavcodec disagrees - params->color.space = MP_CSP_AUTO; + params->repr.sys = PL_COLOR_SYSTEM_UNKNOWN; } - if (params->color.space == MP_CSP_AUTO) - params->color.space = mp_csp_guess_colorspace(params->w, params->h); - if (params->color.levels == MP_CSP_LEVELS_AUTO) { - if (params->color.gamma == MP_CSP_TRC_V_LOG) { - params->color.levels = MP_CSP_LEVELS_PC; + if (params->repr.sys == PL_COLOR_SYSTEM_UNKNOWN) + params->repr.sys = mp_csp_guess_colorspace(params->w, params->h); + if (params->repr.levels == PL_COLOR_LEVELS_UNKNOWN) { + if (params->color.transfer == PL_COLOR_TRC_V_LOG) { + params->repr.levels = PL_COLOR_LEVELS_FULL; } else { - params->color.levels = infer_levels(params->imgfmt); + params->repr.levels = infer_levels(params->imgfmt); } } - if (params->color.primaries == MP_CSP_PRIM_AUTO) { + if (params->color.primaries == PL_COLOR_PRIM_UNKNOWN) { // Guess based on the colormatrix as a first priority - if (params->color.space == MP_CSP_BT_2020_NC || - params->color.space == MP_CSP_BT_2020_C) { - params->color.primaries = MP_CSP_PRIM_BT_2020; - } else if (params->color.space == MP_CSP_BT_709) { - params->color.primaries = MP_CSP_PRIM_BT_709; + if (params->repr.sys == PL_COLOR_SYSTEM_BT_2020_NC || + params->repr.sys == PL_COLOR_SYSTEM_BT_2020_C) { + params->color.primaries = PL_COLOR_PRIM_BT_2020; + } else if (params->repr.sys == PL_COLOR_SYSTEM_BT_709) { + params->color.primaries = PL_COLOR_PRIM_BT_709; } else { // Ambiguous colormatrix for BT.601, guess based on res params->color.primaries = mp_csp_guess_primaries(params->w, params->h); } } - if (params->color.gamma == MP_CSP_TRC_AUTO) - params->color.gamma = MP_CSP_TRC_BT_1886; - } else if (forced_csp == MP_CSP_RGB) { - params->color.space = MP_CSP_RGB; - params->color.levels = MP_CSP_LEVELS_PC; + if (params->color.transfer == PL_COLOR_TRC_UNKNOWN) + params->color.transfer = PL_COLOR_TRC_BT_1886; + } else if (forced_csp == PL_COLOR_SYSTEM_RGB) { + params->repr.sys = PL_COLOR_SYSTEM_RGB; + params->repr.levels = PL_COLOR_LEVELS_FULL; // The majority of RGB content is either sRGB or (rarely) some other // color space which we don't even handle, like AdobeRGB or // ProPhotoRGB. The only reasonable thing we can do is assume it's // sRGB and hope for the best, which should usually just work out fine. // Note: sRGB primaries = BT.709 primaries - if (params->color.primaries == MP_CSP_PRIM_AUTO) - params->color.primaries = MP_CSP_PRIM_BT_709; - if (params->color.gamma == MP_CSP_TRC_AUTO) - params->color.gamma = MP_CSP_TRC_SRGB; - } else if (forced_csp == MP_CSP_XYZ) { - params->color.space = MP_CSP_XYZ; - params->color.levels = MP_CSP_LEVELS_PC; + if (params->color.primaries == PL_COLOR_PRIM_UNKNOWN) + params->color.primaries = PL_COLOR_PRIM_BT_709; + if (params->color.transfer == PL_COLOR_TRC_UNKNOWN) + params->color.transfer = PL_COLOR_TRC_SRGB; + } else if (forced_csp == PL_COLOR_SYSTEM_XYZ) { + params->repr.sys = PL_COLOR_SYSTEM_XYZ; + params->repr.levels = PL_COLOR_LEVELS_FULL; // Force gamma to ST428 as this is the only correct for DCDM X'Y'Z' - params->color.gamma = MP_CSP_TRC_ST428; + params->color.transfer = PL_COLOR_TRC_ST428; // Don't care about primaries, they shouldn't be used, or if anything // MP_CSP_PRIM_ST428 should be defined. } else { // We have no clue. - params->color.space = MP_CSP_AUTO; - params->color.levels = MP_CSP_LEVELS_AUTO; - params->color.primaries = MP_CSP_PRIM_AUTO; - params->color.gamma = MP_CSP_TRC_AUTO; + params->repr.sys = PL_COLOR_SYSTEM_UNKNOWN; + params->repr.levels = PL_COLOR_LEVELS_UNKNOWN; + params->color.primaries = PL_COLOR_PRIM_UNKNOWN; + params->color.transfer = PL_COLOR_TRC_UNKNOWN; } if (!params->color.hdr.max_luma) { - if (params->color.gamma == MP_CSP_TRC_HLG) { + if (params->color.transfer == PL_COLOR_TRC_HLG) { params->color.hdr.max_luma = 1000; // reference display } else { // If the signal peak is unknown, we're forced to pick the TRC's // nominal range as the signal peak to prevent clipping - params->color.hdr.max_luma = mp_trc_nom_peak(params->color.gamma) * MP_REF_WHITE; + params->color.hdr.max_luma = pl_color_transfer_nominal_peak(params->color.transfer) * MP_REF_WHITE; } } - if (!mp_trc_is_hdr(params->color.gamma)) { + if (!pl_color_space_is_hdr(¶ms->color)) { // Some clips have leftover HDR metadata after conversion to SDR, so to // avoid blowing up the tone mapping code, strip/sanitize it params->color.hdr = pl_hdr_metadata_empty; } - if (params->chroma_location == MP_CHROMA_AUTO) { - if (params->color.levels == MP_CSP_LEVELS_TV) - params->chroma_location = MP_CHROMA_LEFT; - if (params->color.levels == MP_CSP_LEVELS_PC) - params->chroma_location = MP_CHROMA_CENTER; + if (params->chroma_location == PL_CHROMA_UNKNOWN) { + if (params->repr.levels == PL_COLOR_LEVELS_LIMITED) + params->chroma_location = PL_CHROMA_LEFT; + if (params->repr.levels == PL_COLOR_LEVELS_FULL) + params->chroma_location = PL_CHROMA_CENTER; } - if (params->color.light == MP_CSP_LIGHT_AUTO) { + if (params->light == MP_CSP_LIGHT_AUTO) { // HLG is always scene-referred (using its own OOTF), everything else // we assume is display-referred by default. - if (params->color.gamma == MP_CSP_TRC_HLG) { - params->color.light = MP_CSP_LIGHT_SCENE_HLG; + if (params->color.transfer == PL_COLOR_TRC_HLG) { + params->light = MP_CSP_LIGHT_SCENE_HLG; } else { - params->color.light = MP_CSP_LIGHT_DISPLAY; + params->light = MP_CSP_LIGHT_DISPLAY; } } } @@ -1033,21 +1045,24 @@ struct mp_image *mp_image_from_av_frame(struct AVFrame *src) if (src->repeat_pict == 1) dst->fields |= MP_IMGFIELD_REPEAT_FIRST; - dst->params.color = (struct mp_colorspace){ - .space = avcol_spc_to_mp_csp(src->colorspace), - .levels = avcol_range_to_mp_csp_levels(src->color_range), - .primaries = avcol_pri_to_mp_csp_prim(src->color_primaries), - .gamma = avcol_trc_to_mp_csp_trc(src->color_trc), + dst->params.repr = (struct pl_color_repr){ + .sys = pl_system_from_av(src->colorspace), + .levels = pl_levels_from_av(src->color_range), }; - dst->params.chroma_location = avchroma_location_to_mp(src->chroma_location); + dst->params.color = (struct pl_color_space){ + .primaries = pl_primaries_from_av(src->color_primaries), + .transfer = pl_transfer_from_av(src->color_trc), + }; + + dst->params.chroma_location = pl_chroma_from_av(src->chroma_location); if (src->opaque_ref) { struct mp_image_params *p = (void *)src->opaque_ref->data; dst->params.stereo3d = p->stereo3d; // Might be incorrect if colorspace changes. - dst->params.color.light = p->color.light; - dst->params.alpha = p->alpha; + dst->params.light = p->light; + dst->params.repr.alpha = p->repr.alpha; } sd = av_frame_get_side_data(src, AV_FRAME_DATA_DISPLAYMATRIX); @@ -1074,14 +1089,38 @@ struct mp_image *mp_image_from_av_frame(struct AVFrame *src) if (sd) dst->a53_cc = sd->buf; + AVBufferRef *dovi = NULL; #if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 16, 100) sd = av_frame_get_side_data(src, AV_FRAME_DATA_DOVI_METADATA); - if (sd) - dst->dovi = sd->buf; + if (sd) { +#ifdef PL_HAVE_LAV_DOLBY_VISION + const AVDOVIMetadata *metadata = (const AVDOVIMetadata *)sd->buf->data; + const AVDOVIRpuDataHeader *header = av_dovi_get_header(metadata); + if (header->disable_residual_flag) { + dst->dovi = dovi = av_buffer_alloc(sizeof(struct pl_dovi_metadata)); + MP_HANDLE_OOM(dovi); +#if PL_API_VER >= 343 + pl_map_avdovi_metadata(&dst->params.color, &dst->params.repr, + (void *)dst->dovi->data, metadata); +#else + struct pl_frame frame; + frame.repr = dst->params.repr; + frame.color = dst->params.color; + pl_frame_map_avdovi_metadata(&frame, (void *)dst->dovi->data, metadata); + dst->params.repr = frame.repr; + dst->params.color = frame.color; +#endif + } +#endif + } sd = av_frame_get_side_data(src, AV_FRAME_DATA_DOVI_RPU_BUFFER); - if (sd) - dst->dovi_buf = sd->buf; + if (sd) { +#ifdef PL_HAVE_LIBDOVI + pl_hdr_metadata_from_dovi_rpu(&dst->params.color.hdr, sd->buf->data, + sd->buf->size); +#endif + } #endif sd = av_frame_get_side_data(src, AV_FRAME_DATA_FILM_GRAIN_PARAMS); @@ -1106,6 +1145,7 @@ struct mp_image *mp_image_from_av_frame(struct AVFrame *src) // Allocated, but non-refcounted data. talloc_free(dst->ff_side_data); + av_buffer_unref(&dovi); return res; } @@ -1163,13 +1203,9 @@ struct AVFrame *mp_image_to_av_frame(struct mp_image *src) if (src->fields & MP_IMGFIELD_REPEAT_FIRST) dst->repeat_pict = 1; - dst->colorspace = mp_csp_to_avcol_spc(src->params.color.space); - dst->color_range = mp_csp_levels_to_avcol_range(src->params.color.levels); - dst->color_primaries = - mp_csp_prim_to_avcol_pri(src->params.color.primaries); - dst->color_trc = mp_csp_trc_to_avcol_trc(src->params.color.gamma); + pl_avframe_set_repr(dst, src->params.repr); - dst->chroma_location = mp_chroma_location_to_av(src->params.chroma_location); + dst->chroma_location = pl_chroma_to_av(src->params.chroma_location); dst->opaque_ref = av_buffer_alloc(sizeof(struct mp_image_params)); MP_HANDLE_OOM(dst->opaque_ref); @@ -1183,11 +1219,7 @@ struct AVFrame *mp_image_to_av_frame(struct mp_image *src) new_ref->icc_profile = NULL; } - pl_avframe_set_color(dst, (struct pl_color_space){ - .primaries = mp_prim_to_pl(src->params.color.primaries), - .transfer = mp_trc_to_pl(src->params.color.gamma), - .hdr = src->params.color.hdr, - }); + pl_avframe_set_color(dst, src->params.color); { AVFrameSideData *sd = av_frame_new_side_data(dst, diff --git a/video/mp_image.h b/video/mp_image.h index 0408aab..af0d9fd 100644 --- a/video/mp_image.h +++ b/video/mp_image.h @@ -43,16 +43,18 @@ // usually copy the whole struct, so that fields added later will be preserved. struct mp_image_params { enum mp_imgfmt imgfmt; // pixel format + const char *imgfmt_name; // pixel format name enum mp_imgfmt hw_subfmt; // underlying format for some hwaccel pixfmts int w, h; // image dimensions int p_w, p_h; // define pixel aspect ratio (undefined: 0/0) bool force_window; // fake image created by handle_force_window - struct mp_colorspace color; - enum mp_chroma_location chroma_location; + struct pl_color_space color; + struct pl_color_repr repr; + enum mp_csp_light light; + enum pl_chroma_location chroma_location; // The image should be rotated clockwise (0-359 degrees). int rotate; enum mp_stereo3d_mode stereo3d; // image is encoded with this mode - enum mp_alpha_type alpha; // usually auto; only set if explicitly known struct mp_rect crop; // crop applied on image }; @@ -114,8 +116,6 @@ typedef struct mp_image { struct AVBufferRef *dovi; // Film grain data, if any struct AVBufferRef *film_grain; - // Dolby Vision RPU buffer, if any - struct AVBufferRef *dovi_buf; // Other side data we don't care about. struct mp_ff_side_data *ff_side_data; int num_ff_side_data; @@ -175,6 +175,8 @@ bool mp_image_crop_valid(const struct mp_image_params *p); bool mp_image_params_valid(const struct mp_image_params *p); bool mp_image_params_equal(const struct mp_image_params *p1, const struct mp_image_params *p2); +bool mp_image_params_static_equal(const struct mp_image_params *p1, + const struct mp_image_params *p2); void mp_image_params_get_dsize(const struct mp_image_params *p, int *d_w, int *d_h); diff --git a/video/mp_image_pool.c b/video/mp_image_pool.c index 0b5e520..922854a 100644 --- a/video/mp_image_pool.c +++ b/video/mp_image_pool.c @@ -337,7 +337,7 @@ bool mp_image_hw_upload(struct mp_image *hw_img, struct mp_image *src) AVFrame *srcav = NULL; // This means the destination image will not be "writable", which would be - // a pain if Libav enforced this - fortunately it doesn't care. We can + // a pain if FFmpeg enforced this - fortunately it doesn't care. We can // transfer data to it even if there are multiple refs. dstav = mp_image_to_av_frame(hw_img); if (!dstav) diff --git a/video/out/cocoa_cb_common.swift b/video/out/cocoa_cb_common.swift index 9c0054a..9f32ed6 100644 --- a/video/out/cocoa_cb_common.swift +++ b/video/out/cocoa_cb_common.swift @@ -17,11 +17,11 @@ import Cocoa -class CocoaCB: Common { +class CocoaCB: Common, EventSubscriber { var libmpv: LibmpvHelper var layer: GLLayer? - @objc var isShuttingDown: Bool = false + var isShuttingDown: Bool = false enum State { case uninitialized @@ -30,22 +30,24 @@ class CocoaCB: Common { } var backendState: State = .uninitialized - - @objc init(_ mpvHandle: OpaquePointer) { - let newlog = mp_log_new(UnsafeMutablePointer<MPContext>(mpvHandle), mp_client_get_log(mpvHandle), "cocoacb") - libmpv = LibmpvHelper(mpvHandle, newlog) - super.init(newlog) + init(_ mpv: OpaquePointer) { + let log = LogHelper(mp_log_new(UnsafeMutablePointer(mpv), mp_client_get_log(mpv), "cocoacb")) + let option = OptionHelper(UnsafeMutablePointer(mpv), mp_client_get_global(mpv)) + libmpv = LibmpvHelper(mpv, log) + super.init(option, log) layer = GLLayer(cocoaCB: self) + AppHub.shared.event?.subscribe(self, event: .init(name: "MPV_EVENT_SHUTDOWN")) } func preinit(_ vo: UnsafeMutablePointer<vo>) { - mpv = MPVHelper(vo, log) + self.vo = vo + input = InputHelper(vo.pointee.input_ctx, option) if backendState == .uninitialized { backendState = .needsInit guard let layer = self.layer else { - log.sendError("Something went wrong, no GLLayer was initialized") + log.error("Something went wrong, no GLLayer was initialized") exit(1) } @@ -57,17 +59,19 @@ class CocoaCB: Common { func uninit() { window?.orderOut(nil) window?.close() - mpv = nil } func reconfig(_ vo: UnsafeMutablePointer<vo>) { - mpv?.vo = vo + self.vo = vo if backendState == .needsInit { DispatchQueue.main.sync { self.initBackend(vo) } - } else { + } else if option.vo.auto_window_resize { DispatchQueue.main.async { self.updateWindowSize(vo) self.layer?.update(force: true) + if self.option.vo.focus_on == 2 { + NSApp.activate(ignoringOtherApps: true) + } } } } @@ -85,15 +89,12 @@ class CocoaCB: Common { func updateWindowSize(_ vo: UnsafeMutablePointer<vo>) { guard let targetScreen = getTargetScreen(forFullscreen: false) ?? NSScreen.main else { - log.sendWarning("Couldn't update Window size, no Screen available") + log.warning("Couldn't update Window size, no Screen available") return } let wr = getWindowGeometry(forScreen: targetScreen, videoOut: vo) - if !(window?.isVisible ?? false) && - !(window?.isMiniaturized ?? false) && - !NSApp.isHidden - { + if !(window?.isVisible ?? false) && !(window?.isMiniaturized ?? false) && !NSApp.isHidden { window?.makeKeyAndOrderFront(nil) } layer?.atomicDrawingStart() @@ -116,7 +117,7 @@ class CocoaCB: Common { override func updateICCProfile() { guard let colorSpace = window?.screen?.colorSpace else { - log.sendWarning("Couldn't update ICC Profile, no color space available") + log.warning("Couldn't update ICC Profile, no color space available") return } @@ -169,7 +170,7 @@ class CocoaCB: Common { let ccb = unsafeBitCast(ctx, to: CocoaCB.self) guard let vo = v, let events = e else { - ccb.log.sendWarning("Unexpected nil value in Control Callback") + ccb.log.warning("Unexpected nil value in Control Callback") return VO_FALSE } @@ -198,9 +199,9 @@ class CocoaCB: Common { return super.control(vo, events: events, request: request, data: data) } - func shutdown(_ destroy: Bool = false) { + func shutdown() { isShuttingDown = window?.isAnimating ?? false || - window?.isInFullscreen ?? false && mpv?.opts.native_fs ?? true + window?.isInFullscreen ?? false && option.vo.native_fs if window?.isInFullscreen ?? false && !(window?.isAnimating ?? false) { window?.close() } @@ -209,22 +210,18 @@ class CocoaCB: Common { uninit() uninitCommon() - libmpv.deinitRender() - libmpv.deinitMPV(destroy) + layer?.lockCglContext() + libmpv.uninit() + layer?.unlockCglContext() } func checkShutdown() { if isShuttingDown { - shutdown(true) + shutdown() } } - @objc func processEvent(_ event: UnsafePointer<mpv_event>) { - switch event.pointee.event_id { - case MPV_EVENT_SHUTDOWN: - shutdown() - default: - break - } + func handle(event: EventHelper.Event) { + if event.name == String(describing: MPV_EVENT_SHUTDOWN) { shutdown() } } } diff --git a/video/out/d3d11/context.c b/video/out/d3d11/context.c index 05f04fd..c563b5f 100644 --- a/video/out/d3d11/context.c +++ b/video/out/d3d11/context.c @@ -27,10 +27,6 @@ #include "context.h" #include "ra_d3d11.h" -static int d3d11_validate_adapter(struct mp_log *log, - const struct m_option *opt, - struct bstr name, const char **value); - struct d3d11_opts { int feature_level; int warp; @@ -62,7 +58,7 @@ const struct m_sub_options d3d11_conf = { {"d3d11-flip", OPT_BOOL(flip)}, {"d3d11-sync-interval", OPT_INT(sync_interval), M_RANGE(0, 4)}, {"d3d11-adapter", OPT_STRING_VALIDATE(adapter_name, - d3d11_validate_adapter)}, + mp_dxgi_validate_adapter)}, {"d3d11-output-format", OPT_CHOICE(output_format, {"auto", DXGI_FORMAT_UNKNOWN}, {"rgba8", DXGI_FORMAT_R8G8B8A8_UNORM}, @@ -100,7 +96,7 @@ struct priv { struct ra_tex *backbuffer; ID3D11Device *device; IDXGISwapChain *swapchain; - struct mp_colorspace swapchain_csp; + struct pl_color_space swapchain_csp; int64_t perf_freq; unsigned sync_refresh_count; @@ -109,37 +105,6 @@ struct priv { int64_t last_submit_qpc; }; -static int d3d11_validate_adapter(struct mp_log *log, - const struct m_option *opt, - 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 }; - - if (bstr_equals0(param, "")) { - return 0; - } - - adapter_matched = mp_d3d11_list_or_verify_adapters(log, - help ? bstr0(NULL) : param, - help ? &listing : NULL); - - if (help) { - mp_info(log, "Available D3D11 adapters:\n%.*s", - BSTR_P(listing)); - talloc_free(listing.start); - return M_OPT_EXIT; - } - - if (!adapter_matched) { - mp_err(log, "No adapter matching '%.*s'!\n", BSTR_P(param)); - } - - return adapter_matched ? 0 : M_OPT_INVALID; -} - static struct ra_tex *get_backbuffer(struct ra_ctx *ctx) { struct priv *p = ctx->priv; @@ -189,6 +154,11 @@ static bool d3d11_reconfig(struct ra_ctx *ctx) static int d3d11_color_depth(struct ra_swapchain *sw) { struct priv *p = sw->priv; + + DXGI_OUTPUT_DESC1 desc1; + if (mp_get_dxgi_output_desc(p->swapchain, &desc1)) + return desc1.BitsPerColor; + DXGI_SWAP_CHAIN_DESC desc; HRESULT hr = IDXGISwapChain_GetDesc(p->swapchain, &desc); @@ -514,6 +484,9 @@ static bool d3d11_init(struct ra_ctx *ctx) if (!vo_w32_init(ctx->vo)) goto error; + if (ctx->opts.want_alpha) + vo_w32_set_transparency(ctx->vo, ctx->opts.want_alpha); + UINT usage = DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_SHADER_INPUT; if (ID3D11Device_GetFeatureLevel(p->device) >= D3D_FEATURE_LEVEL_11_0 && p->opts->output_format != DXGI_FORMAT_B8G8R8A8_UNORM) @@ -544,6 +517,11 @@ error: return false; } +static void d3d11_update_render_opts(struct ra_ctx *ctx) +{ + vo_w32_set_transparency(ctx->vo, ctx->opts.want_alpha); +} + IDXGISwapChain *ra_d3d11_ctx_get_swapchain(struct ra_ctx *ra) { if (ra->swapchain->fns != &d3d11_swapchain) @@ -556,11 +534,22 @@ IDXGISwapChain *ra_d3d11_ctx_get_swapchain(struct ra_ctx *ra) return p->swapchain; } +bool ra_d3d11_ctx_prefer_8bit_output_format(struct ra_ctx *ra) +{ + if (ra->swapchain->fns != &d3d11_swapchain) + return false; + + struct priv *p = ra->priv; + + return p->opts->output_format == DXGI_FORMAT_R8G8B8A8_UNORM; +} + const struct ra_ctx_fns ra_ctx_d3d11 = { - .type = "d3d11", - .name = "d3d11", - .reconfig = d3d11_reconfig, - .control = d3d11_control, - .init = d3d11_init, - .uninit = d3d11_uninit, + .type = "d3d11", + .name = "d3d11", + .reconfig = d3d11_reconfig, + .control = d3d11_control, + .update_render_opts = d3d11_update_render_opts, + .init = d3d11_init, + .uninit = d3d11_uninit, }; diff --git a/video/out/d3d11/context.h b/video/out/d3d11/context.h index 8a9ef4c..25488f2 100644 --- a/video/out/d3d11/context.h +++ b/video/out/d3d11/context.h @@ -7,3 +7,7 @@ // Get the underlying D3D11 swap chain from an RA context. The returned swap chain is // refcounted and must be released by the caller. IDXGISwapChain *ra_d3d11_ctx_get_swapchain(struct ra_ctx *ra); + +// Returns true if an 8-bit output format is explicitly requested for +// d3d11-output-format for an RA context. +bool ra_d3d11_ctx_prefer_8bit_output_format(struct ra_ctx *ra); diff --git a/video/out/drm_common.c b/video/out/drm_common.c index da45ca2..e47de7d 100644 --- a/video/out/drm_common.c +++ b/video/out/drm_common.c @@ -65,8 +65,7 @@ static int drm_connector_opt_help(struct mp_log *log, const struct m_option *opt 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, const char **value); +static OPT_STRING_VALIDATE_FUNC(drm_validate_mode_opt); static void drm_show_available_modes(struct mp_log *log, const drmModeConnector *connector); @@ -96,7 +95,8 @@ const struct m_sub_options drm_conf = { {"xrgb8888", DRM_OPTS_FORMAT_XRGB8888}, {"xrgb2101010", DRM_OPTS_FORMAT_XRGB2101010}, {"xbgr8888", DRM_OPTS_FORMAT_XBGR8888}, - {"xbgr2101010", DRM_OPTS_FORMAT_XBGR2101010})}, + {"xbgr2101010", DRM_OPTS_FORMAT_XBGR2101010}, + {"yuyv", DRM_OPTS_FORMAT_YUYV})}, {"drm-draw-surface-size", OPT_SIZE_BOX(draw_surface_size)}, {"drm-vrr-enabled", OPT_CHOICE(vrr_enabled, {"no", 0}, {"yes", 1}, {"auto", -1})}, @@ -107,6 +107,7 @@ const struct m_sub_options drm_conf = { .drm_atomic = 1, .draw_plane = DRM_OPTS_PRIMARY_PLANE, .drmprime_video_plane = DRM_OPTS_OVERLAY_PLANE, + .drm_format = DRM_OPTS_FORMAT_XRGB8888, }, .size = sizeof(struct drm_opts), }; @@ -387,7 +388,7 @@ bool vo_drm_acquire_crtc(struct vo_drm_state *drm) drm_object_set_property(request, atomic_ctx->draw_plane, "CRTC_H", drm->mode.mode.vdisplay); if (drmModeAtomicCommit(drm->fd, request, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL)) { - MP_ERR(drm, "Failed to commit ModeSetting atomic request: %s\n", strerror(errno)); + MP_ERR(drm, "Failed to commit ModeSetting atomic request: %s\n", mp_strerror(errno)); goto err; } diff --git a/video/out/drm_common.h b/video/out/drm_common.h index 581151f..9418b3f 100644 --- a/video/out/drm_common.h +++ b/video/out/drm_common.h @@ -23,10 +23,13 @@ #include <xf86drmMode.h> #include "vo.h" -#define DRM_OPTS_FORMAT_XRGB8888 0 -#define DRM_OPTS_FORMAT_XRGB2101010 1 -#define DRM_OPTS_FORMAT_XBGR8888 2 -#define DRM_OPTS_FORMAT_XBGR2101010 3 +enum { + DRM_OPTS_FORMAT_XRGB8888, + DRM_OPTS_FORMAT_XRGB2101010, + DRM_OPTS_FORMAT_XBGR8888, + DRM_OPTS_FORMAT_XBGR2101010, + DRM_OPTS_FORMAT_YUYV, +}; struct framebuffer { int fd; diff --git a/video/out/filter_kernels.c b/video/out/filter_kernels.c index 95d99ff..d5df985 100644 --- a/video/out/filter_kernels.c +++ b/video/out/filter_kernels.c @@ -5,10 +5,10 @@ * (https://github.com/glumpy/glumpy/blob/master/glumpy/library/build-spatial-filters.py) * * Also see: - * - http://vector-agg.cvs.sourceforge.net/viewvc/vector-agg/agg-2.5/include/agg_image_filters.h + * - https://sourceforge.net/p/agg/svn/HEAD/tree/agg-2.4/include/agg_image_filters.h * - Vapoursynth plugin fmtconv (WTFPL Licensed), which is based on * dither plugin for avisynth from the same author: - * https://github.com/vapoursynth/fmtconv/tree/master/src/fmtc + * https://gitlab.com/EleonoreMizo/fmtconv/-/tree/master/src/fmtc * - Paul Heckbert's "zoom" * - XBMC: ConvolutionKernels.cpp etc. * diff --git a/video/out/gpu/context.c b/video/out/gpu/context.c index 5ce18af..88d4f42 100644 --- a/video/out/gpu/context.c +++ b/video/out/gpu/context.c @@ -41,7 +41,6 @@ extern const struct ra_ctx_fns ra_ctx_wayland_egl; extern const struct ra_ctx_fns ra_ctx_wgl; extern const struct ra_ctx_fns ra_ctx_angle; extern const struct ra_ctx_fns ra_ctx_dxgl; -extern const struct ra_ctx_fns ra_ctx_rpi; extern const struct ra_ctx_fns ra_ctx_android; /* Vulkan */ @@ -67,9 +66,6 @@ static const struct ra_ctx_fns *contexts[] = { #if HAVE_EGL_ANDROID &ra_ctx_android, #endif -#if HAVE_RPI - &ra_ctx_rpi, -#endif #if HAVE_EGL_ANGLE_WIN32 &ra_ctx_angle, #endif @@ -133,8 +129,7 @@ static int ra_ctx_api_help(struct mp_log *log, const struct m_option *opt, return M_OPT_EXIT; } -static int ra_ctx_validate_api(struct mp_log *log, const struct m_option *opt, - struct bstr name, const char **value) +static inline OPT_STRING_VALIDATE_FUNC(ra_ctx_validate_api) { struct bstr param = bstr0(*value); if (bstr_equals0(param, "auto")) @@ -158,8 +153,7 @@ static int ra_ctx_context_help(struct mp_log *log, const struct m_option *opt, return M_OPT_EXIT; } -static int ra_ctx_validate_context(struct mp_log *log, const struct m_option *opt, - struct bstr name, const char **value) +static inline OPT_STRING_VALIDATE_FUNC(ra_ctx_validate_context) { struct bstr param = bstr0(*value); if (bstr_equals0(param, "auto")) @@ -208,6 +202,7 @@ struct ra_ctx *ra_ctx_create(struct vo *vo, struct ra_ctx_opts opts) MP_VERBOSE(ctx, "Initializing GPU context '%s'\n", ctx->fns->name); if (contexts[i]->init(ctx)) { vo->probing = old_probing; + vo->context_name = ctx->fns->name; return ctx; } diff --git a/video/out/gpu/context.h b/video/out/gpu/context.h index 6788e6f..447f40b 100644 --- a/video/out/gpu/context.h +++ b/video/out/gpu/context.h @@ -72,7 +72,7 @@ struct ra_fbo { // Host system's colorspace that it will be interpreting // the frame buffer as. - struct mp_colorspace color_space; + struct pl_color_space color_space; }; struct ra_swapchain_fns { diff --git a/video/out/gpu/d3d11_helpers.c b/video/out/gpu/d3d11_helpers.c index 30d9eae..d45c038 100644 --- a/video/out/gpu/d3d11_helpers.c +++ b/video/out/gpu/d3d11_helpers.c @@ -228,9 +228,9 @@ static const char *d3d11_get_csp_name(DXGI_COLOR_SPACE_TYPE csp) } static bool d3d11_get_mp_csp(DXGI_COLOR_SPACE_TYPE csp, - struct mp_colorspace *mp_csp) + struct pl_color_space *pl_color_system) { - if (!mp_csp) + if (!pl_color_system) return false; // Colorspaces utilizing gamma 2.2 (G22) are set to @@ -243,27 +243,27 @@ static bool d3d11_get_mp_csp(DXGI_COLOR_SPACE_TYPE csp, // regarding not doing conversion from BT.601 to BT.709. switch (csp) { case DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709: - *mp_csp = (struct mp_colorspace){ - .gamma = MP_CSP_TRC_AUTO, - .primaries = MP_CSP_PRIM_AUTO, + *pl_color_system = (struct pl_color_space){ + .transfer = PL_COLOR_TRC_UNKNOWN, + .primaries = PL_COLOR_PRIM_UNKNOWN, }; break; case DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709: - *mp_csp = (struct mp_colorspace) { - .gamma = MP_CSP_TRC_LINEAR, - .primaries = MP_CSP_PRIM_AUTO, + *pl_color_system = (struct pl_color_space) { + .transfer = PL_COLOR_TRC_LINEAR, + .primaries = PL_COLOR_PRIM_UNKNOWN, }; break; case DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020: - *mp_csp = (struct mp_colorspace) { - .gamma = MP_CSP_TRC_PQ, - .primaries = MP_CSP_PRIM_BT_2020, + *pl_color_system = (struct pl_color_space) { + .transfer = PL_COLOR_TRC_PQ, + .primaries = PL_COLOR_PRIM_BT_2020, }; break; case DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P2020: - *mp_csp = (struct mp_colorspace) { - .gamma = MP_CSP_TRC_AUTO, - .primaries = MP_CSP_PRIM_BT_2020, + *pl_color_system = (struct pl_color_space) { + .transfer = PL_COLOR_TRC_UNKNOWN, + .primaries = PL_COLOR_PRIM_BT_2020, }; break; default: @@ -287,28 +287,8 @@ static bool query_output_format_and_colorspace(struct mp_log *log, if (!out_fmt || !out_cspace) return false; - HRESULT hr = IDXGISwapChain_GetContainingOutput(swapchain, &output); - if (FAILED(hr)) { - mp_err(log, "Failed to get swap chain's containing output: %s!\n", - mp_HRESULT_to_str(hr)); - goto done; - } - - hr = IDXGIOutput_QueryInterface(output, &IID_IDXGIOutput6, - (void**)&output6); - if (FAILED(hr)) { - // point where systems older than Windows 10 would fail, - // thus utilizing error log level only with windows 10+ - mp_msg(log, IsWindows10OrGreater() ? MSGL_ERR : MSGL_V, - "Failed to create a DXGI 1.6 output interface: %s\n", - mp_HRESULT_to_str(hr)); - goto done; - } - - hr = IDXGIOutput6_GetDesc1(output6, &desc); - if (FAILED(hr)) { - mp_err(log, "Failed to query swap chain's output information: %s\n", - mp_HRESULT_to_str(hr)); + if (!mp_get_dxgi_output_desc(swapchain, &desc)) { + mp_err(log, "Failed to query swap chain's output information\n"); goto done; } @@ -371,9 +351,9 @@ static int get_feature_levels(int max_fl, int min_fl, return len; } -static IDXGIAdapter1 *get_d3d11_adapter(struct mp_log *log, - struct bstr requested_adapter_name, - struct bstr *listing) +IDXGIAdapter1 *mp_get_dxgi_adapter(struct mp_log *log, + bstr requested_adapter_name, + bstr *listing) { HRESULT hr = S_OK; IDXGIFactory1 *factory; @@ -437,6 +417,37 @@ static IDXGIAdapter1 *get_d3d11_adapter(struct mp_log *log, return picked_adapter; } +int mp_dxgi_validate_adapter(struct mp_log *log, + const struct m_option *opt, + 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 }; + + if (bstr_equals0(param, "")) { + return 0; + } + + adapter_matched = mp_dxgi_list_or_verify_adapters(log, + help ? bstr0(NULL) : param, + help ? &listing : NULL); + + if (help) { + mp_info(log, "Available DXGI adapters:\n%.*s", + BSTR_P(listing)); + talloc_free(listing.start); + return M_OPT_EXIT; + } + + if (!adapter_matched) { + mp_err(log, "No adapter matching '%.*s'!\n", BSTR_P(param)); + } + + return adapter_matched ? 0 : M_OPT_INVALID; +} + static HRESULT create_device(struct mp_log *log, IDXGIAdapter1 *adapter, bool warp, bool debug, int max_fl, int min_fl, ID3D11Device **dev) @@ -455,9 +466,9 @@ static HRESULT create_device(struct mp_log *log, IDXGIAdapter1 *adapter, NULL, flags, levels, levels_len, D3D11_SDK_VERSION, dev, NULL, NULL); } -bool mp_d3d11_list_or_verify_adapters(struct mp_log *log, - bstr adapter_name, - bstr *listing) +bool mp_dxgi_list_or_verify_adapters(struct mp_log *log, + bstr adapter_name, + bstr *listing) { IDXGIAdapter1 *picked_adapter = NULL; @@ -465,7 +476,7 @@ bool mp_d3d11_list_or_verify_adapters(struct mp_log *log, return false; } - if ((picked_adapter = get_d3d11_adapter(log, adapter_name, listing))) { + if ((picked_adapter = mp_get_dxgi_adapter(log, adapter_name, listing))) { SAFE_RELEASE(picked_adapter); return true; } @@ -497,7 +508,7 @@ bool mp_d3d11_create_present_device(struct mp_log *log, goto done; } - adapter = get_d3d11_adapter(log, bstr0(adapter_name), NULL); + adapter = mp_get_dxgi_adapter(log, bstr0(adapter_name), NULL); if (adapter_name && !adapter) { mp_warn(log, "Adapter matching '%s' was not found in the system! " @@ -793,7 +804,7 @@ static bool configure_created_swapchain(struct mp_log *log, IDXGISwapChain *swapchain, DXGI_FORMAT requested_format, DXGI_COLOR_SPACE_TYPE requested_csp, - struct mp_colorspace *configured_csp) + struct pl_color_space *configured_csp) { DXGI_FORMAT probed_format = DXGI_FORMAT_UNKNOWN; DXGI_FORMAT selected_format = DXGI_FORMAT_UNKNOWN; @@ -801,7 +812,7 @@ static bool configure_created_swapchain(struct mp_log *log, DXGI_COLOR_SPACE_TYPE selected_colorspace; const char *format_name = NULL; const char *csp_name = NULL; - struct mp_colorspace mp_csp = { 0 }; + struct pl_color_space pl_color_system = { 0 }; bool mp_csp_mapped = false; query_output_format_and_colorspace(log, swapchain, @@ -817,7 +828,7 @@ static bool configure_created_swapchain(struct mp_log *log, requested_csp : probed_colorspace; format_name = d3d11_get_format_name(selected_format); csp_name = d3d11_get_csp_name(selected_colorspace); - mp_csp_mapped = d3d11_get_mp_csp(selected_colorspace, &mp_csp); + mp_csp_mapped = d3d11_get_mp_csp(selected_colorspace, &pl_color_system); mp_verbose(log, "Selected swapchain format %s (%d), attempting " "to utilize it.\n", @@ -848,7 +859,7 @@ static bool configure_created_swapchain(struct mp_log *log, "mapping! Overriding to standard sRGB!\n", csp_name, selected_colorspace); selected_colorspace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709; - d3d11_get_mp_csp(selected_colorspace, &mp_csp); + d3d11_get_mp_csp(selected_colorspace, &pl_color_system); } mp_verbose(log, "Selected swapchain color space %s (%d), attempting to " @@ -860,7 +871,7 @@ static bool configure_created_swapchain(struct mp_log *log, } if (configured_csp) { - *configured_csp = mp_csp; + *configured_csp = pl_color_system; } return true; @@ -964,3 +975,23 @@ done: SAFE_RELEASE(dxgi_dev); return success; } + +bool mp_get_dxgi_output_desc(IDXGISwapChain *swapchain, DXGI_OUTPUT_DESC1 *desc) +{ + bool ret = false; + IDXGIOutput *output = NULL; + IDXGIOutput6 *output6 = NULL; + + if (FAILED(IDXGISwapChain_GetContainingOutput(swapchain, &output))) + goto done; + + if (FAILED(IDXGIOutput_QueryInterface(output, &IID_IDXGIOutput6, (void**)&output6))) + goto done; + + ret = SUCCEEDED(IDXGIOutput6_GetDesc1(output6, desc)); + +done: + SAFE_RELEASE(output); + SAFE_RELEASE(output6); + return ret; +} diff --git a/video/out/gpu/d3d11_helpers.h b/video/out/gpu/d3d11_helpers.h index c115d33..6cc6818 100644 --- a/video/out/gpu/d3d11_helpers.h +++ b/video/out/gpu/d3d11_helpers.h @@ -22,6 +22,7 @@ #include <windows.h> #include <d3d11.h> #include <dxgi1_2.h> +#include <dxgi1_6.h> #include "video/mp_image.h" @@ -65,9 +66,17 @@ struct d3d11_device_opts { char *adapter_name; }; -bool mp_d3d11_list_or_verify_adapters(struct mp_log *log, - bstr adapter_name, - bstr *listing); +IDXGIAdapter1 *mp_get_dxgi_adapter(struct mp_log *log, + bstr requested_adapter_name, + bstr *listing); + +bool mp_get_dxgi_output_desc(IDXGISwapChain *swapchain, DXGI_OUTPUT_DESC1 *desc); + +OPT_STRING_VALIDATE_FUNC(mp_dxgi_validate_adapter); + +bool mp_dxgi_list_or_verify_adapters(struct mp_log *log, + bstr adapter_name, + bstr *listing); bool mp_d3d11_create_present_device(struct mp_log *log, struct d3d11_device_opts *opts, @@ -80,10 +89,10 @@ struct d3d11_swapchain_opts { DXGI_FORMAT format; DXGI_COLOR_SPACE_TYPE color_space; - // mp_colorspace mapping of the configured swapchain colorspace + // pl_color_space mapping of the configured swapchain colorspace // shall be written into this memory location if configuration // succeeds. Will be ignored if NULL. - struct mp_colorspace *configured_csp; + struct pl_color_space *configured_csp; // Use DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL if possible bool flip; diff --git a/video/out/gpu/error_diffusion.c b/video/out/gpu/error_diffusion.c index c1ea542..72063c3 100644 --- a/video/out/gpu/error_diffusion.c +++ b/video/out/gpu/error_diffusion.c @@ -227,7 +227,8 @@ void pass_error_diffusion(struct gl_shader_cache *sc, } // Different kernels for error diffusion. -// Patterns are from http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT +// Patterns are from <https://web.archive.org/web/20181031005427/ +// http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT> const struct error_diffusion_kernel mp_error_diffusion_kernels[] = { { .name = "simple", diff --git a/video/out/gpu/hwdec.c b/video/out/gpu/hwdec.c index c8098f3..c7d817c 100644 --- a/video/out/gpu/hwdec.c +++ b/video/out/gpu/hwdec.c @@ -34,7 +34,6 @@ extern const struct ra_hwdec_driver ra_hwdec_dxva2gldx; extern const struct ra_hwdec_driver ra_hwdec_d3d11va; extern const struct ra_hwdec_driver ra_hwdec_dxva2dxgi; extern const struct ra_hwdec_driver ra_hwdec_cuda; -extern const struct ra_hwdec_driver ra_hwdec_rpi_overlay; extern const struct ra_hwdec_driver ra_hwdec_drmprime; extern const struct ra_hwdec_driver ra_hwdec_drmprime_overlay; extern const struct ra_hwdec_driver ra_hwdec_aimagereader; @@ -70,9 +69,6 @@ const struct ra_hwdec_driver *const ra_hwdec_drivers[] = { #if HAVE_VDPAU_GL_X11 &ra_hwdec_vdpau, #endif -#if HAVE_RPI_MMAL - &ra_hwdec_rpi_overlay, -#endif #if HAVE_DRM &ra_hwdec_drmprime, &ra_hwdec_drmprime_overlay, diff --git a/video/out/gpu/hwdec.h b/video/out/gpu/hwdec.h index 7766073..f195606 100644 --- a/video/out/gpu/hwdec.h +++ b/video/out/gpu/hwdec.h @@ -18,12 +18,8 @@ struct ra_hwdec_ctx { int num_hwdecs; }; -int ra_hwdec_validate_opt(struct mp_log *log, const m_option_t *opt, - struct bstr name, const char **value); - -int ra_hwdec_validate_drivers_only_opt(struct mp_log *log, - const m_option_t *opt, - struct bstr name, const char **value); +OPT_STRING_VALIDATE_FUNC(ra_hwdec_validate_opt); +OPT_STRING_VALIDATE_FUNC(ra_hwdec_validate_drivers_only_opt); void ra_hwdec_ctx_init(struct ra_hwdec_ctx *ctx, struct mp_hwdec_devices *devs, const char *opt, bool load_all_by_default); diff --git a/video/out/gpu/lcms.c b/video/out/gpu/lcms.c index 7006a96..c197acf 100644 --- a/video/out/gpu/lcms.c +++ b/video/out/gpu/lcms.c @@ -46,8 +46,8 @@ struct gl_lcms { char *current_profile; bool using_memory_profile; bool changed; - enum mp_csp_prim current_prim; - enum mp_csp_trc current_trc; + enum pl_color_primaries current_prim; + enum pl_color_transfer current_trc; struct mp_log *log; struct mpv_global *global; @@ -162,8 +162,8 @@ static bool vid_profile_eq(struct AVBufferRef *a, struct AVBufferRef *b) // Return whether the profile or config has changed since the last time it was // retrieved. If it has changed, gl_lcms_get_lut3d() should be called. -bool gl_lcms_has_changed(struct gl_lcms *p, enum mp_csp_prim prim, - enum mp_csp_trc trc, struct AVBufferRef *vid_profile) +bool gl_lcms_has_changed(struct gl_lcms *p, enum pl_color_primaries prim, + enum pl_color_transfer trc, struct AVBufferRef *vid_profile) { if (p->changed || p->current_prim != prim || p->current_trc != trc) return true; @@ -180,7 +180,7 @@ bool gl_lcms_has_profile(struct gl_lcms *p) static cmsHPROFILE get_vid_profile(struct gl_lcms *p, cmsContext cms, cmsHPROFILE disp_profile, - enum mp_csp_prim prim, enum mp_csp_trc trc) + enum pl_color_primaries prim, enum pl_color_transfer trc) { if (p->opts->use_embedded && p->vid_profile) { // Try using the embedded ICC profile @@ -197,36 +197,41 @@ static cmsHPROFILE get_vid_profile(struct gl_lcms *p, cmsContext cms, // The input profile for the transformation is dependent on the video // primaries and transfer characteristics - struct mp_csp_primaries csp = mp_get_csp_primaries(prim); - cmsCIExyY wp_xyY = {csp.white.x, csp.white.y, 1.0}; + const struct pl_raw_primaries *csp = pl_raw_primaries_get(prim); + cmsCIExyY wp_xyY = {csp->white.x, csp->white.y, 1.0}; cmsCIExyYTRIPLE prim_xyY = { - .Red = {csp.red.x, csp.red.y, 1.0}, - .Green = {csp.green.x, csp.green.y, 1.0}, - .Blue = {csp.blue.x, csp.blue.y, 1.0}, + .Red = {csp->red.x, csp->red.y, 1.0}, + .Green = {csp->green.x, csp->green.y, 1.0}, + .Blue = {csp->blue.x, csp->blue.y, 1.0}, }; cmsToneCurve *tonecurve[3] = {0}; switch (trc) { - case MP_CSP_TRC_LINEAR: tonecurve[0] = cmsBuildGamma(cms, 1.0); break; - case MP_CSP_TRC_GAMMA18: tonecurve[0] = cmsBuildGamma(cms, 1.8); break; - case MP_CSP_TRC_GAMMA20: tonecurve[0] = cmsBuildGamma(cms, 2.0); break; - case MP_CSP_TRC_GAMMA22: tonecurve[0] = cmsBuildGamma(cms, 2.2); break; - case MP_CSP_TRC_GAMMA24: tonecurve[0] = cmsBuildGamma(cms, 2.4); break; - case MP_CSP_TRC_GAMMA26: tonecurve[0] = cmsBuildGamma(cms, 2.6); break; - case MP_CSP_TRC_GAMMA28: tonecurve[0] = cmsBuildGamma(cms, 2.8); break; - - case MP_CSP_TRC_SRGB: + case PL_COLOR_TRC_LINEAR: tonecurve[0] = cmsBuildGamma(cms, 1.0); break; + case PL_COLOR_TRC_GAMMA18: tonecurve[0] = cmsBuildGamma(cms, 1.8); break; + case PL_COLOR_TRC_GAMMA20: tonecurve[0] = cmsBuildGamma(cms, 2.0); break; + case PL_COLOR_TRC_GAMMA22: tonecurve[0] = cmsBuildGamma(cms, 2.2); break; + case PL_COLOR_TRC_GAMMA24: tonecurve[0] = cmsBuildGamma(cms, 2.4); break; + case PL_COLOR_TRC_GAMMA26: tonecurve[0] = cmsBuildGamma(cms, 2.6); break; + case PL_COLOR_TRC_GAMMA28: tonecurve[0] = cmsBuildGamma(cms, 2.8); break; + + case PL_COLOR_TRC_ST428: + tonecurve[0] = cmsBuildParametricToneCurve(cms, 2, + (double[3]){2.6, pow(52.37/48.0, 1/2.6), 0.0}); + break; + + case PL_COLOR_TRC_SRGB: // Values copied from Little-CMS tonecurve[0] = cmsBuildParametricToneCurve(cms, 4, (double[5]){2.40, 1/1.055, 0.055/1.055, 1/12.92, 0.04045}); break; - case MP_CSP_TRC_PRO_PHOTO: + case PL_COLOR_TRC_PRO_PHOTO: tonecurve[0] = cmsBuildParametricToneCurve(cms, 4, (double[5]){1.8, 1.0, 0.0, 1/16.0, 0.03125}); break; - case MP_CSP_TRC_BT_1886: { + case PL_COLOR_TRC_BT_1886: { double src_black[3]; if (p->opts->contrast < 0) { // User requested infinite contrast, return 2.4 profile @@ -242,7 +247,7 @@ static cmsHPROFILE get_vid_profile(struct gl_lcms *p, cmsContext cms, // function. Relative colorimetric is used since we want to // approximate the BT.1886 to the target device's actual black // point even in e.g. perceptual mode - const int intent = MP_INTENT_RELATIVE_COLORIMETRIC; + const int intent = PL_INTENT_RELATIVE_COLORIMETRIC; cmsCIEXYZ bp_XYZ; if (!cmsDetectBlackPoint(&bp_XYZ, disp_profile, intent, 0)) return false; @@ -300,7 +305,7 @@ static cmsHPROFILE get_vid_profile(struct gl_lcms *p, cmsContext cms, } bool gl_lcms_get_lut3d(struct gl_lcms *p, struct lut3d **result_lut3d, - enum mp_csp_prim prim, enum mp_csp_trc trc, + enum pl_color_primaries prim, enum pl_color_transfer trc, struct AVBufferRef *vid_profile) { int s_r, s_g, s_b; @@ -474,8 +479,8 @@ struct gl_lcms *gl_lcms_init(void *talloc_ctx, struct mp_log *log, void gl_lcms_update_options(struct gl_lcms *p) { } bool gl_lcms_set_memory_profile(struct gl_lcms *p, bstr profile) {return false;} -bool gl_lcms_has_changed(struct gl_lcms *p, enum mp_csp_prim prim, - enum mp_csp_trc trc, struct AVBufferRef *vid_profile) +bool gl_lcms_has_changed(struct gl_lcms *p, enum pl_color_primaries prim, + enum pl_color_transfer trc, struct AVBufferRef *vid_profile) { return false; } @@ -486,7 +491,7 @@ bool gl_lcms_has_profile(struct gl_lcms *p) } bool gl_lcms_get_lut3d(struct gl_lcms *p, struct lut3d **result_lut3d, - enum mp_csp_prim prim, enum mp_csp_trc trc, + enum pl_color_primaries prim, enum pl_color_transfer trc, struct AVBufferRef *vid_profile) { return false; @@ -494,8 +499,7 @@ bool gl_lcms_get_lut3d(struct gl_lcms *p, struct lut3d **result_lut3d, #endif -static int validate_3dlut_size_opt(struct mp_log *log, const m_option_t *opt, - struct bstr name, const char **value) +static inline OPT_STRING_VALIDATE_FUNC(validate_3dlut_size_opt) { int p1, p2, p3; return gl_parse_3dlut_size(*value, &p1, &p2, &p3) ? 0 : M_OPT_INVALID; @@ -519,7 +523,7 @@ const struct m_sub_options mp_icc_conf = { .size = sizeof(struct mp_icc_opts), .defaults = &(const struct mp_icc_opts) { .size_str = "auto", - .intent = MP_INTENT_RELATIVE_COLORIMETRIC, + .intent = PL_INTENT_RELATIVE_COLORIMETRIC, .use_embedded = true, .cache = true, }, diff --git a/video/out/gpu/lcms.h b/video/out/gpu/lcms.h index 607353a..d0b0fe5 100644 --- a/video/out/gpu/lcms.h +++ b/video/out/gpu/lcms.h @@ -37,10 +37,10 @@ void gl_lcms_update_options(struct gl_lcms *p); bool gl_lcms_set_memory_profile(struct gl_lcms *p, bstr profile); bool gl_lcms_has_profile(struct gl_lcms *p); bool gl_lcms_get_lut3d(struct gl_lcms *p, struct lut3d **, - enum mp_csp_prim prim, enum mp_csp_trc trc, + enum pl_color_primaries prim, enum pl_color_transfer trc, struct AVBufferRef *vid_profile); -bool gl_lcms_has_changed(struct gl_lcms *p, enum mp_csp_prim prim, - enum mp_csp_trc trc, struct AVBufferRef *vid_profile); +bool gl_lcms_has_changed(struct gl_lcms *p, enum pl_color_primaries prim, + enum pl_color_transfer trc, struct AVBufferRef *vid_profile); static inline bool gl_parse_3dlut_size(const char *arg, int *p1, int *p2, int *p3) { diff --git a/video/out/gpu/libmpv_gpu.c b/video/out/gpu/libmpv_gpu.c index aae1d18..542db7f 100644 --- a/video/out/gpu/libmpv_gpu.c +++ b/video/out/gpu/libmpv_gpu.c @@ -185,7 +185,7 @@ static int render(struct render_backend *ctx, mpv_render_param *params, &(int){0}); struct ra_fbo target = {.tex = tex, .flip = flip}; - gl_video_render_frame(p->renderer, frame, target, RENDER_FRAME_DEF); + gl_video_render_frame(p->renderer, frame, &target, RENDER_FRAME_DEF); p->context->fns->done_frame(p->context, frame->display_synced); return 0; diff --git a/video/out/gpu/osd.c b/video/out/gpu/osd.c index 91505a9..7892904 100644 --- a/video/out/gpu/osd.c +++ b/video/out/gpu/osd.c @@ -286,7 +286,7 @@ static void get_3d_side_by_side(int stereo_mode, int div[2]) } void mpgl_osd_draw_finish(struct mpgl_osd *ctx, int index, - struct gl_shader_cache *sc, struct ra_fbo fbo) + struct gl_shader_cache *sc, const struct ra_fbo *fbo) { struct mpgl_osd_part *part = ctx->parts[index]; @@ -312,7 +312,7 @@ void mpgl_osd_draw_finish(struct mpgl_osd *ctx, int index, const int *factors = &blend_factors[part->format][0]; gl_sc_blend(sc, factors[0], factors[1], factors[2], factors[3]); - gl_sc_dispatch_draw(sc, fbo.tex, false, vertex_vao, MP_ARRAY_SIZE(vertex_vao), + gl_sc_dispatch_draw(sc, fbo->tex, false, vertex_vao, MP_ARRAY_SIZE(vertex_vao), sizeof(struct vertex), part->vertices, part->num_vertices); } diff --git a/video/out/gpu/osd.h b/video/out/gpu/osd.h index 00fbc49..1b05e25 100644 --- a/video/out/gpu/osd.h +++ b/video/out/gpu/osd.h @@ -18,7 +18,7 @@ void mpgl_osd_resize(struct mpgl_osd *ctx, struct mp_osd_res res, int stereo_mod bool mpgl_osd_draw_prepare(struct mpgl_osd *ctx, int index, struct gl_shader_cache *sc); void mpgl_osd_draw_finish(struct mpgl_osd *ctx, int index, - struct gl_shader_cache *sc, struct ra_fbo fbo); + struct gl_shader_cache *sc, const struct ra_fbo *fbo); bool mpgl_osd_check_change(struct mpgl_osd *ctx, struct mp_osd_res *res, double pts); diff --git a/video/out/gpu/ra.h b/video/out/gpu/ra.h index 5f229f8..c0c58ac 100644 --- a/video/out/gpu/ra.h +++ b/video/out/gpu/ra.h @@ -143,8 +143,8 @@ struct ra_tex_params { // be true depends on ra_format.linear_filter) bool src_repeat; // if false, clamp texture coordinates to edge // if true, repeat texture coordinates - bool non_normalized; // hack for GL_TEXTURE_RECTANGLE OSX idiocy - // always set to false, except in OSX code + bool non_normalized; // hack for GL_TEXTURE_RECTANGLE macOS idiocy + // always set to false, except in macOS code bool external_oes; // hack for GL_TEXTURE_EXTERNAL_OES idiocy // If non-NULL, the texture will be created with these contents. Using // this does *not* require setting host_mutable. Otherwise, the initial diff --git a/video/out/gpu/spirv.c b/video/out/gpu/spirv.c index 67088bc..6910049 100644 --- a/video/out/gpu/spirv.c +++ b/video/out/gpu/spirv.c @@ -16,6 +16,7 @@ static const struct spirv_compiler_fns *compilers[] = { #if HAVE_SHADERC [SPIRV_SHADERC] = &spirv_shaderc, #endif + NULL }; static const struct m_opt_choice_alternatives compiler_choices[] = { diff --git a/video/out/gpu/user_shaders.c b/video/out/gpu/user_shaders.c index 708de87..f2507b6 100644 --- a/video/out/gpu/user_shaders.c +++ b/video/out/gpu/user_shaders.c @@ -431,7 +431,7 @@ static bool parse_tex(struct mp_log *log, struct ra *ra, struct bstr *body, void parse_user_shader(struct mp_log *log, struct ra *ra, struct bstr shader, void *priv, - bool (*dohook)(void *p, struct gl_user_shader_hook hook), + bool (*dohook)(void *p, const struct gl_user_shader_hook *hook), bool (*dotex)(void *p, struct gl_user_shader_tex tex)) { if (!dohook || !dotex || !shader.len) @@ -457,7 +457,7 @@ void parse_user_shader(struct mp_log *log, struct ra *ra, struct bstr shader, } struct gl_user_shader_hook h; - if (!parse_hook(log, &shader, &h) || !dohook(priv, h)) + if (!parse_hook(log, &shader, &h) || !dohook(priv, &h)) return; } } diff --git a/video/out/gpu/user_shaders.h b/video/out/gpu/user_shaders.h index 4bb7c22..d3405a8 100644 --- a/video/out/gpu/user_shaders.h +++ b/video/out/gpu/user_shaders.h @@ -88,7 +88,7 @@ struct gl_user_shader_tex { // valid shader block parsed. void parse_user_shader(struct mp_log *log, struct ra *ra, struct bstr shader, void *priv, - bool (*dohook)(void *p, struct gl_user_shader_hook hook), + bool (*dohook)(void *p, const struct gl_user_shader_hook *hook), bool (*dotex)(void *p, struct gl_user_shader_tex tex)); // Evaluate a szexp, given a lookup function for named textures diff --git a/video/out/gpu/utils.c b/video/out/gpu/utils.c index 8a1aacf..d18cf6e 100644 --- a/video/out/gpu/utils.c +++ b/video/out/gpu/utils.c @@ -33,10 +33,10 @@ void gl_transform_trans(struct gl_transform t, struct gl_transform *x) gl_transform_vec(t, &x->t[0], &x->t[1]); } -void gl_transform_ortho_fbo(struct gl_transform *t, struct ra_fbo fbo) +void gl_transform_ortho_fbo(struct gl_transform *t, const struct ra_fbo *fbo) { - int y_dir = fbo.flip ? -1 : 1; - gl_transform_ortho(t, 0, fbo.tex->params.w, 0, fbo.tex->params.h * y_dir); + int y_dir = fbo->flip ? -1 : 1; + gl_transform_ortho(t, 0, fbo->tex->params.w, 0, fbo->tex->params.h * y_dir); } float gl_video_scale_ambient_lux(float lmin, float lmax, diff --git a/video/out/gpu/utils.h b/video/out/gpu/utils.h index 215873e..dd52c38 100644 --- a/video/out/gpu/utils.h +++ b/video/out/gpu/utils.h @@ -63,7 +63,7 @@ static inline bool gl_transform_eq(struct gl_transform a, struct gl_transform b) void gl_transform_trans(struct gl_transform t, struct gl_transform *x); -void gl_transform_ortho_fbo(struct gl_transform *t, struct ra_fbo fbo); +void gl_transform_ortho_fbo(struct gl_transform *t, const struct ra_fbo *fbo); float gl_video_scale_ambient_lux(float lmin, float lmax, float rmin, float rmax, float lux); diff --git a/video/out/gpu/video.c b/video/out/gpu/video.c index 852ee78..1478ec4 100644 --- a/video/out/gpu/video.c +++ b/video/out/gpu/video.c @@ -183,6 +183,7 @@ struct gl_video { struct mp_image_params real_image_params; // configured format struct mp_image_params image_params; // texture format (mind hwdec case) + struct mp_image_params target_params; // target format struct ra_imgfmt_desc ra_format; // texture format int plane_count; @@ -212,6 +213,7 @@ struct gl_video { struct ra_tex *merge_tex[4]; struct ra_tex *scale_tex[4]; struct ra_tex *integer_tex[4]; + struct ra_tex *chroma_tex[4]; struct ra_tex *indirect_tex; struct ra_tex *blend_subs_tex; struct ra_tex *error_diffusion_tex[2]; @@ -312,8 +314,8 @@ static const struct gl_video_opts gl_video_opts_def = { .linear_downscaling = true, .sigmoid_upscaling = true, .interpolation_threshold = 0.01, - .alpha_mode = ALPHA_BLEND_TILES, - .background = {0, 0, 0, 255}, + .background = BACKGROUND_TILES, + .background_color = {0, 0, 0, 255}, .gamma = 1.0f, .tone_map = { .curve = TONE_MAPPING_AUTO, @@ -329,14 +331,9 @@ static const struct gl_video_opts gl_video_opts_def = { .hwdec_interop = "auto", }; -static int validate_scaler_opt(struct mp_log *log, const m_option_t *opt, - struct bstr name, const char **value); - -static int validate_window_opt(struct mp_log *log, const m_option_t *opt, - struct bstr name, const char **value); - -static int validate_error_diffusion_opt(struct mp_log *log, const m_option_t *opt, - struct bstr name, const char **value); +static OPT_STRING_VALIDATE_FUNC(validate_scaler_opt); +static OPT_STRING_VALIDATE_FUNC(validate_window_opt); +static OPT_STRING_VALIDATE_FUNC(validate_error_diffusion_opt); #define OPT_BASE_STRUCT struct gl_video_opts @@ -368,13 +365,13 @@ const struct m_sub_options gl_video_conf = { .deprecation_message = "no replacement"}, {"gamma-auto", OPT_BOOL(gamma_auto), .deprecation_message = "no replacement"}, - {"target-prim", OPT_CHOICE_C(target_prim, mp_csp_prim_names)}, - {"target-trc", OPT_CHOICE_C(target_trc, mp_csp_trc_names)}, + {"target-prim", OPT_CHOICE_C(target_prim, pl_csp_prim_names)}, + {"target-trc", OPT_CHOICE_C(target_trc, pl_csp_trc_names)}, {"target-peak", OPT_CHOICE(target_peak, {"auto", 0}), M_RANGE(10, 10000)}, {"target-contrast", OPT_CHOICE(target_contrast, {"auto", 0}, {"inf", -1}), M_RANGE(10, 1000000)}, - {"target-gamut", OPT_CHOICE_C(target_gamut, mp_csp_prim_names)}, + {"target-gamut", OPT_CHOICE_C(target_gamut, pl_csp_prim_names)}, {"tone-mapping", OPT_CHOICE(tone_map.curve, {"auto", TONE_MAPPING_AUTO}, {"clip", TONE_MAPPING_CLIP}, @@ -447,13 +444,12 @@ const struct m_sub_options gl_video_conf = { M_RANGE(1, 128)}, {"error-diffusion", OPT_STRING_VALIDATE(error_diffusion, validate_error_diffusion_opt)}, - {"alpha", OPT_CHOICE(alpha_mode, - {"no", ALPHA_NO}, - {"yes", ALPHA_YES}, - {"blend", ALPHA_BLEND}, - {"blend-tiles", ALPHA_BLEND_TILES})}, + {"background", OPT_CHOICE(background, + {"none", BACKGROUND_NONE}, + {"color", BACKGROUND_COLOR}, + {"tiles", BACKGROUND_TILES})}, {"opengl-rectangle-textures", OPT_BOOL(use_rectangle)}, - {"background", OPT_COLOR(background)}, + {"background-color", OPT_COLOR(background_color)}, {"interpolation", OPT_BOOL(interpolation)}, {"interpolation-threshold", OPT_FLOAT(interpolation_threshold)}, {"blend-subtitles", OPT_CHOICE(blend_subs, @@ -483,6 +479,7 @@ const struct m_sub_options gl_video_conf = { }, .size = sizeof(struct gl_video_opts), .defaults = &gl_video_opts_def, + .change_flags = UPDATE_VIDEO, }; static void uninit_rendering(struct gl_video *p); @@ -578,6 +575,7 @@ static void uninit_rendering(struct gl_video *p) ra_tex_free(p->ra, &p->merge_tex[n]); ra_tex_free(p->ra, &p->scale_tex[n]); ra_tex_free(p->ra, &p->integer_tex[n]); + ra_tex_free(p->ra, &p->chroma_tex[n]); } ra_tex_free(p->ra, &p->indirect_tex); @@ -605,15 +603,6 @@ bool gl_video_gamma_auto_enabled(struct gl_video *p) return p->opts.gamma_auto; } -struct mp_colorspace gl_video_get_output_colorspace(struct gl_video *p) -{ - return (struct mp_colorspace) { - .primaries = p->opts.target_prim, - .gamma = p->opts.target_trc, - .hdr.max_luma = p->opts.target_peak, - }; -} - // Warning: profile.start must point to a ta allocation, and the function // takes over ownership. void gl_video_set_icc_profile(struct gl_video *p, bstr icc_data) @@ -627,8 +616,8 @@ bool gl_video_icc_auto_enabled(struct gl_video *p) return p->opts.icc_opts ? p->opts.icc_opts->profile_auto : false; } -static bool gl_video_get_lut3d(struct gl_video *p, enum mp_csp_prim prim, - enum mp_csp_trc trc) +static bool gl_video_get_lut3d(struct gl_video *p, enum pl_color_primaries prim, + enum pl_color_transfer trc) { if (!p->use_lut_3d) return false; @@ -771,16 +760,16 @@ static void pass_get_images(struct gl_video *p, struct video_image *vimg, struct gl_transform chroma = {{{ls_w, 0.0}, {0.0, ls_h}}}; - if (p->image_params.chroma_location != MP_CHROMA_CENTER) { - int cx, cy; - mp_get_chroma_location(p->image_params.chroma_location, &cx, &cy); + if (p->image_params.chroma_location != PL_CHROMA_CENTER) { + float cx, cy; + pl_chroma_location_offset(p->image_params.chroma_location, &cx, &cy); // By default texture coordinates are such that chroma is centered with // any chroma subsampling. If a specific direction is given, make it // so that the luma and chroma sample line up exactly. // For 4:4:4, setting chroma location should have no effect at all. // luma sample size (in chroma coord. space) - chroma.t[0] = ls_w < 1 ? ls_w * -cx / 2 : 0; - chroma.t[1] = ls_h < 1 ? ls_h * -cy / 2 : 0; + chroma.t[0] = ls_w < 1 ? ls_w * -cx : 0; + chroma.t[1] = ls_h < 1 ? ls_h * -cy : 0; } memset(img, 0, 4 * sizeof(img[0])); @@ -796,9 +785,9 @@ static void pass_get_images(struct gl_video *p, struct video_image *vimg, ctype = PLANE_NONE; } else if (c == 4) { ctype = PLANE_ALPHA; - } else if (p->image_params.color.space == MP_CSP_RGB) { + } else if (p->image_params.repr.sys == PL_COLOR_SYSTEM_RGB) { ctype = PLANE_RGB; - } else if (p->image_params.color.space == MP_CSP_XYZ) { + } else if (p->image_params.repr.sys == PL_COLOR_SYSTEM_XYZ) { ctype = PLANE_XYZ; } else { ctype = c == 1 ? PLANE_LUMA : PLANE_CHROMA; @@ -810,7 +799,7 @@ static void pass_get_images(struct gl_video *p, struct video_image *vimg, int msb_valid_bits = p->ra_format.component_bits + MPMIN(p->ra_format.component_pad, 0); - int csp = type == PLANE_ALPHA ? MP_CSP_RGB : p->image_params.color.space; + int csp = type == PLANE_ALPHA ? PL_COLOR_SYSTEM_RGB : p->image_params.repr.sys; float tex_mul = 1.0 / mp_get_csp_mul(csp, msb_valid_bits, p->ra_format.component_bits); if (p->ra_format.component_type == RA_CTYPE_FLOAT) @@ -1065,13 +1054,13 @@ static void uninit_video(struct gl_video *p) ra_hwdec_mapper_free(&p->hwdec_mapper); } -static void pass_record(struct gl_video *p, struct mp_pass_perf perf) +static void pass_record(struct gl_video *p, const struct mp_pass_perf *perf) { if (!p->pass || p->pass_idx == VO_PASS_PERF_MAX) return; struct pass_info *pass = &p->pass[p->pass_idx]; - pass->perf = perf; + pass->perf = *perf; if (pass->desc.len == 0) bstr_xappend(p, &pass->desc, bstr0("(unknown)")); @@ -1211,12 +1200,13 @@ static void dispatch_compute(struct gl_video *p, int w, int h, if (!(p->ra->caps & RA_CAP_NUM_GROUPS)) PRELUDE("#define gl_NumWorkGroups uvec3(%d, %d, 1)\n", num_x, num_y); - pass_record(p, gl_sc_dispatch_compute(p->sc, num_x, num_y, 1)); + struct mp_pass_perf perf = gl_sc_dispatch_compute(p->sc, num_x, num_y, 1); + pass_record(p, &perf); cleanup_binds(p); } static struct mp_pass_perf render_pass_quad(struct gl_video *p, - struct ra_fbo fbo, bool discard, + const struct ra_fbo *fbo, bool discard, const struct mp_rect *dst) { // The first element is reserved for `vec2 position` @@ -1274,15 +1264,16 @@ static struct mp_pass_perf render_pass_quad(struct gl_video *p, &p->tmp_vertex[num_vertex_attribs * 1], vertex_stride); - return gl_sc_dispatch_draw(p->sc, fbo.tex, discard, p->vao, num_vertex_attribs, + return gl_sc_dispatch_draw(p->sc, fbo->tex, discard, p->vao, num_vertex_attribs, vertex_stride, p->tmp_vertex, num_vertices); } -static void finish_pass_fbo(struct gl_video *p, struct ra_fbo fbo, +static void finish_pass_fbo(struct gl_video *p, const struct ra_fbo *fbo, bool discard, const struct mp_rect *dst) { pass_prepare_src_tex(p); - pass_record(p, render_pass_quad(p, fbo, discard, dst)); + struct mp_pass_perf perf = render_pass_quad(p, fbo, discard, dst); + pass_record(p, &perf); debug_check_gl(p, "after rendering"); cleanup_binds(p); } @@ -1319,7 +1310,7 @@ static void finish_pass_tex(struct gl_video *p, struct ra_tex **dst_tex, debug_check_gl(p, "after dispatching compute shader"); } else { struct ra_fbo fbo = { .tex = *dst_tex, }; - finish_pass_fbo(p, fbo, true, &(struct mp_rect){0, 0, w, h}); + finish_pass_fbo(p, &fbo, true, &(struct mp_rect){0, 0, w, h}); } } @@ -1955,7 +1946,7 @@ static void deband_hook(struct gl_video *p, struct image img, { pass_describe(p, "debanding (%s)", plane_names[img.type]); pass_sample_deband(p->sc, p->opts.deband_opts, &p->lfg, - p->image_params.color.gamma); + p->image_params.color.transfer); } static void unsharp_hook(struct gl_video *p, struct image img, @@ -2046,25 +2037,23 @@ static void user_hook(struct gl_video *p, struct image img, gl_transform_trans(shader->offset, trans); } -static bool add_user_hook(void *priv, struct gl_user_shader_hook hook) +static bool add_user_hook(void *priv, const struct gl_user_shader_hook *hook) { struct gl_video *p = priv; - struct gl_user_shader_hook *copy = talloc_ptrtype(p, copy); - *copy = hook; - + struct gl_user_shader_hook *copy = talloc_dup(p, (struct gl_user_shader_hook *)hook); struct tex_hook texhook = { - .save_tex = bstrdup0(copy, hook.save_tex), - .components = hook.components, - .align_offset = hook.align_offset, + .save_tex = bstrdup0(copy, copy->save_tex), + .components = copy->components, + .align_offset = copy->align_offset, .hook = user_hook, .cond = user_hook_cond, .priv = copy, }; for (int h = 0; h < SHADER_MAX_HOOKS; h++) - texhook.hook_tex[h] = bstrdup0(copy, hook.hook_tex[h]); + texhook.hook_tex[h] = bstrdup0(copy, copy->hook_tex[h]); for (int h = 0; h < SHADER_MAX_BINDS; h++) - texhook.bind_tex[h] = bstrdup0(copy, hook.bind_tex[h]); + texhook.bind_tex[h] = bstrdup0(copy, copy->bind_tex[h]); MP_TARRAY_APPEND(p, p->tex_hooks, p->num_tex_hooks, texhook); return true; @@ -2213,6 +2202,23 @@ static void pass_read_video(struct gl_video *p) } } + // If chroma textures are in a subsampled semi-planar format and rotated, + // introduce an explicit conversion pass to avoid breaking chroma scalers. + for (int n = 0; n < 4; n++) { + if (img[n].tex && img[n].type == PLANE_CHROMA && + img[n].tex->params.format->num_components == 2 && + p->image_params.rotate % 180 == 90 && + p->ra_format.chroma_w != 1) + { + GLSLF("// chroma fix for rotated plane %d\n", n); + copy_image(p, &(int){0}, img[n]); + pass_describe(p, "chroma fix for rotated plane"); + finish_pass_tex(p, &p->chroma_tex[n], img[n].w, img[n].h); + img[n] = image_wrap(p->chroma_tex[n], img[n].type, + img[n].components); + } + } + // At this point all planes are finalized but they may not be at the // required size yet. Furthermore, they may have texture offsets that // require realignment. @@ -2345,29 +2351,29 @@ static void pass_convert_yuv(struct gl_video *p) GLSLF("color = color.%s;\n", p->color_swizzle); // Pre-colormatrix input gamma correction - if (cparams.color.space == MP_CSP_XYZ) - pass_linearize(p->sc, p->image_params.color.gamma); + if (cparams.repr.sys == PL_COLOR_SYSTEM_XYZ) + pass_linearize(p->sc, p->image_params.color.transfer); // We always explicitly normalize the range in pass_read_video cparams.input_bits = cparams.texture_bits = 0; // Conversion to RGB. For RGB itself, this still applies e.g. brightness // and contrast controls, or expansion of e.g. LSB-packed 10 bit data. - struct mp_cmat m = {{{0}}}; + struct pl_transform3x3 m = {0}; mp_get_csp_matrix(&cparams, &m); - gl_sc_uniform_mat3(sc, "colormatrix", true, &m.m[0][0]); + gl_sc_uniform_mat3(sc, "colormatrix", true, &m.mat.m[0][0]); gl_sc_uniform_vec3(sc, "colormatrix_c", m.c); GLSL(color.rgb = mat3(colormatrix) * color.rgb + colormatrix_c;) - if (cparams.color.space == MP_CSP_XYZ) { - pass_delinearize(p->sc, p->image_params.color.gamma); + if (cparams.repr.sys == PL_COLOR_SYSTEM_XYZ) { + pass_delinearize(p->sc, p->image_params.color.transfer); // mp_get_csp_matrix implicitly converts XYZ to DCI-P3 - p->image_params.color.space = MP_CSP_RGB; - p->image_params.color.primaries = MP_CSP_PRIM_DCI_P3; + p->image_params.repr.sys = PL_COLOR_SYSTEM_RGB; + p->image_params.color.primaries = PL_COLOR_PRIM_DCI_P3; } - if (p->image_params.color.space == MP_CSP_BT_2020_C) { + if (p->image_params.repr.sys == PL_COLOR_SYSTEM_BT_2020_C) { // Conversion for C'rcY'cC'bc via the BT.2020 CL system: // C'bc = (B'-Y'c) / 1.9404 | C'bc <= 0 // = (B'-Y'c) / 1.5816 | C'bc > 0 @@ -2404,9 +2410,9 @@ static void pass_convert_yuv(struct gl_video *p) } p->components = 3; - if (!p->has_alpha || p->opts.alpha_mode == ALPHA_NO) { + if (!p->has_alpha) { GLSL(color.a = 1.0;) - } else if (p->image_params.alpha == MP_ALPHA_PREMUL) { + } else if (p->image_params.repr.alpha == PL_ALPHA_PREMULTIPLIED) { p->components = 4; } else { p->components = 4; @@ -2491,7 +2497,7 @@ static void pass_scale_main(struct gl_video *p) // Linear light downscaling results in nasty artifacts for HDR curves // due to the potentially extreme brightness differences severely // compounding any ringing. So just scale in gamma light instead. - if (mp_trc_is_hdr(p->image_params.color.gamma)) + if (pl_color_space_is_hdr(&p->image_params.color)) use_linear = false; } else if (upscaling) { use_linear = p->opts.linear_upscaling || p->opts.sigmoid_upscaling; @@ -2499,7 +2505,7 @@ static void pass_scale_main(struct gl_video *p) if (use_linear) { p->use_linear = true; - pass_linearize(p->sc, p->image_params.color.gamma); + pass_linearize(p->sc, p->image_params.color.transfer); pass_opt_hook_point(p, "LINEAR", NULL); } @@ -2552,8 +2558,9 @@ static void pass_scale_main(struct gl_video *p) // rendering) // If OSD is true, ignore any changes that may have been made to the video // by previous passes (i.e. linear scaling) -static void pass_colormanage(struct gl_video *p, struct mp_colorspace src, - struct mp_colorspace fbo_csp, int flags, bool osd) +static void pass_colormanage(struct gl_video *p, struct pl_color_space src, + enum mp_csp_light src_light, + struct pl_color_space fbo_csp, int flags, bool osd) { struct ra *ra = p->ra; @@ -2561,18 +2568,17 @@ static void pass_colormanage(struct gl_video *p, struct mp_colorspace src, // unless specific transfer function, primaries or target peak // is set. If values are set to _AUTO, the most likely intended // values are guesstimated later in this function. - struct mp_colorspace dst = { - .gamma = p->opts.target_trc == MP_CSP_TRC_AUTO ? - fbo_csp.gamma : p->opts.target_trc, - .primaries = p->opts.target_prim == MP_CSP_PRIM_AUTO ? + struct pl_color_space dst = { + .transfer = p->opts.target_trc == PL_COLOR_TRC_UNKNOWN ? + fbo_csp.transfer : p->opts.target_trc, + .primaries = p->opts.target_prim == PL_COLOR_PRIM_UNKNOWN ? fbo_csp.primaries : p->opts.target_prim, - .light = MP_CSP_LIGHT_DISPLAY, .hdr.max_luma = !p->opts.target_peak ? fbo_csp.hdr.max_luma : p->opts.target_peak, }; if (!p->colorspace_override_warned && - ((fbo_csp.gamma && dst.gamma != fbo_csp.gamma) || + ((fbo_csp.transfer && dst.transfer != fbo_csp.transfer) || (fbo_csp.primaries && dst.primaries != fbo_csp.primaries))) { MP_WARN(p, "One or more colorspace value is being overridden " @@ -2580,44 +2586,44 @@ static void pass_colormanage(struct gl_video *p, struct mp_colorspace src, "transfer function: (dst: %s, fbo: %s), " "primaries: (dst: %s, fbo: %s). " "Rendering can lead to incorrect results!\n", - m_opt_choice_str(mp_csp_trc_names, dst.gamma), - m_opt_choice_str(mp_csp_trc_names, fbo_csp.gamma), - m_opt_choice_str(mp_csp_prim_names, dst.primaries), - m_opt_choice_str(mp_csp_prim_names, fbo_csp.primaries)); + m_opt_choice_str(pl_csp_trc_names, dst.transfer), + m_opt_choice_str(pl_csp_trc_names, fbo_csp.transfer), + m_opt_choice_str(pl_csp_prim_names, dst.primaries), + m_opt_choice_str(pl_csp_prim_names, fbo_csp.primaries)); p->colorspace_override_warned = true; } - if (dst.gamma == MP_CSP_TRC_HLG) - dst.light = MP_CSP_LIGHT_SCENE_HLG; + enum mp_csp_light dst_light = dst.transfer == PL_COLOR_TRC_HLG ? + MP_CSP_LIGHT_SCENE_HLG : MP_CSP_LIGHT_DISPLAY; if (p->use_lut_3d && (flags & RENDER_SCREEN_COLOR)) { // The 3DLUT is always generated against the video's original source // space, *not* the reference space. (To avoid having to regenerate // the 3DLUT for the OSD on every frame) - enum mp_csp_prim prim_orig = p->image_params.color.primaries; - enum mp_csp_trc trc_orig = p->image_params.color.gamma; + enum pl_color_primaries prim_orig = p->image_params.color.primaries; + enum pl_color_transfer trc_orig = p->image_params.color.transfer; // One exception: HDR is not implemented by LittleCMS for technical // limitation reasons, so we use a gamma 2.2 input curve here instead. // We could pick any value we want here, the difference is just coding // efficiency. - if (mp_trc_is_hdr(trc_orig)) - trc_orig = MP_CSP_TRC_GAMMA22; + if (pl_color_space_is_hdr(&p->image_params.color)) + trc_orig = PL_COLOR_TRC_GAMMA22; if (gl_video_get_lut3d(p, prim_orig, trc_orig)) { dst.primaries = prim_orig; - dst.gamma = trc_orig; - assert(dst.primaries && dst.gamma); + dst.transfer = trc_orig; + assert(dst.primaries && dst.transfer); } } - if (dst.primaries == MP_CSP_PRIM_AUTO) { + if (dst.primaries == PL_COLOR_PRIM_UNKNOWN) { // The vast majority of people are on sRGB or BT.709 displays, so pick // this as the default output color space. - dst.primaries = MP_CSP_PRIM_BT_709; + dst.primaries = PL_COLOR_PRIM_BT_709; - if (src.primaries == MP_CSP_PRIM_BT_601_525 || - src.primaries == MP_CSP_PRIM_BT_601_625) + if (src.primaries == PL_COLOR_PRIM_BT_601_525 || + src.primaries == PL_COLOR_PRIM_BT_601_625) { // Since we auto-pick BT.601 and BT.709 based on the dimensions, // combined with the fact that they're very similar to begin with, @@ -2627,28 +2633,28 @@ static void pass_colormanage(struct gl_video *p, struct mp_colorspace src, } } - if (dst.gamma == MP_CSP_TRC_AUTO) { + if (dst.transfer == PL_COLOR_TRC_UNKNOWN) { // Most people seem to complain when the image is darker or brighter // than what they're "used to", so just avoid changing the gamma // altogether by default. The only exceptions to this rule apply to // very unusual TRCs, which even hardcode technoluddites would probably // not enjoy viewing unaltered. - dst.gamma = src.gamma; + dst.transfer = src.transfer; // Avoid outputting linear light or HDR content "by default". For these // just pick gamma 2.2 as a default, since it's a good estimate for // the response of typical displays - if (dst.gamma == MP_CSP_TRC_LINEAR || mp_trc_is_hdr(dst.gamma)) - dst.gamma = MP_CSP_TRC_GAMMA22; + if (dst.transfer == PL_COLOR_TRC_LINEAR || pl_color_space_is_hdr(&dst)) + dst.transfer = PL_COLOR_TRC_GAMMA22; } // If there's no specific signal peak known for the output display, infer // it from the chosen transfer function. Also normalize the src peak, in // case it was unknown if (!dst.hdr.max_luma) - dst.hdr.max_luma = mp_trc_nom_peak(dst.gamma) * MP_REF_WHITE; + dst.hdr.max_luma = pl_color_transfer_nominal_peak(dst.transfer) * MP_REF_WHITE; if (!src.hdr.max_luma) - src.hdr.max_luma = mp_trc_nom_peak(src.gamma) * MP_REF_WHITE; + src.hdr.max_luma = pl_color_transfer_nominal_peak(src.transfer) * MP_REF_WHITE; // Whitelist supported modes switch (p->opts.tone_map.curve) { @@ -2680,7 +2686,7 @@ static void pass_colormanage(struct gl_video *p, struct mp_colorspace src, } struct gl_tone_map_opts tone_map = p->opts.tone_map; - bool detect_peak = tone_map.compute_peak >= 0 && mp_trc_is_hdr(src.gamma) + bool detect_peak = tone_map.compute_peak >= 0 && pl_color_space_is_hdr(&src) && src.hdr.max_luma > dst.hdr.max_luma; if (detect_peak && !p->hdr_peak_ssbo) { @@ -2719,7 +2725,22 @@ static void pass_colormanage(struct gl_video *p, struct mp_colorspace src, } // Adapt from src to dst as necessary - pass_color_map(p->sc, p->use_linear && !osd, src, dst, &tone_map); + pass_color_map(p->sc, p->use_linear && !osd, src, dst, src_light, dst_light, &tone_map); + + if (!osd) { + struct mp_csp_params cparams = MP_CSP_PARAMS_DEFAULTS; + mp_csp_equalizer_state_get(p->video_eq, &cparams); + if (cparams.levels_out == PL_COLOR_LEVELS_UNKNOWN) + cparams.levels_out = PL_COLOR_LEVELS_FULL; + p->target_params = (struct mp_image_params){ + .imgfmt_name = p->fbo_format ? p->fbo_format->name : "unknown", + .w = p->texture_w, + .h = p->texture_h, + .color = dst, + .repr = {.sys = PL_COLOR_SYSTEM_RGB, .levels = cparams.levels_out}, + .rotate = p->image_params.rotate, + }; + } if (p->use_lut_3d && (flags & RENDER_SCREEN_COLOR)) { gl_sc_uniform_texture(p->sc, "lut_3d", p->lut_3d_texture); @@ -2735,7 +2756,7 @@ void gl_video_set_fb_depth(struct gl_video *p, int fb_depth) p->fb_depth = fb_depth; } -static void pass_dither(struct gl_video *p) +static void pass_dither(struct gl_video *p, const struct ra_fbo *fbo) { // Assume 8 bits per component if unknown. int dst_depth = p->fb_depth > 0 ? p->fb_depth : 8; @@ -2868,7 +2889,9 @@ static void pass_dither(struct gl_video *p) gl_sc_uniform_texture(p->sc, "dither", p->dither_texture); - GLSLF("vec2 dither_pos = gl_FragCoord.xy * 1.0/%d.0;\n", dither_size); + GLSLF("vec2 dither_coord = vec2(gl_FragCoord.x, %d.0 + %f * gl_FragCoord.y);", + fbo->flip ? fbo->tex->params.h : 0, fbo->flip ? -1.0 : 1.0); + GLSLF("vec2 dither_pos = dither_coord * 1.0/%d.0;\n", dither_size); if (p->opts.temporal_dither) { int phase = (p->frames_rendered / p->opts.temporal_dither_period) % 8u; @@ -2891,7 +2914,7 @@ static void pass_dither(struct gl_video *p) // Draws the OSD, in scene-referred colors.. If cms is true, subtitles are // instead adapted to the display's gamut. static void pass_draw_osd(struct gl_video *p, int osd_flags, int frame_flags, - double pts, struct mp_osd_res rect, struct ra_fbo fbo, + double pts, struct mp_osd_res rect, const struct ra_fbo *fbo, bool cms) { if (frame_flags & RENDER_FRAME_VF_SUBS) @@ -2910,20 +2933,21 @@ static void pass_draw_osd(struct gl_video *p, int osd_flags, int frame_flags, // When subtitles need to be color managed, assume they're in sRGB // (for lack of anything saner to do) if (cms) { - static const struct mp_colorspace csp_srgb = { - .primaries = MP_CSP_PRIM_BT_709, - .gamma = MP_CSP_TRC_SRGB, - .light = MP_CSP_LIGHT_DISPLAY, + static const struct pl_color_space csp_srgb = { + .primaries = PL_COLOR_PRIM_BT_709, + .transfer = PL_COLOR_TRC_SRGB, }; - pass_colormanage(p, csp_srgb, fbo.color_space, frame_flags, true); + pass_colormanage(p, csp_srgb, MP_CSP_LIGHT_DISPLAY, fbo->color_space, + frame_flags, true); } mpgl_osd_draw_finish(p->osd, n, p->sc, fbo); } timer_pool_stop(p->osd_timer); pass_describe(p, "drawing osd"); - pass_record(p, timer_pool_measure(p->osd_timer)); + struct mp_pass_perf perf = timer_pool_measure(p->osd_timer); + pass_record(p, &perf); } static float chroma_realign(int size, int pixel) @@ -3013,7 +3037,7 @@ static bool pass_render_frame(struct gl_video *p, struct mp_image *mpi, }; finish_pass_tex(p, &p->blend_subs_tex, rect.w, rect.h); struct ra_fbo fbo = { p->blend_subs_tex }; - pass_draw_osd(p, OSD_DRAW_SUB_ONLY, flags, vpts, rect, fbo, false); + pass_draw_osd(p, OSD_DRAW_SUB_ONLY, flags, vpts, rect, &fbo, false); pass_read_tex(p, p->blend_subs_tex); pass_describe(p, "blend subs video"); } @@ -3040,12 +3064,12 @@ static bool pass_render_frame(struct gl_video *p, struct mp_image *mpi, rect.mt *= scale[1]; rect.mb *= scale[1]; // We should always blend subtitles in non-linear light if (p->use_linear) { - pass_delinearize(p->sc, p->image_params.color.gamma); + pass_delinearize(p->sc, p->image_params.color.transfer); p->use_linear = false; } finish_pass_tex(p, &p->blend_subs_tex, p->texture_w, p->texture_h); struct ra_fbo fbo = { p->blend_subs_tex }; - pass_draw_osd(p, OSD_DRAW_SUB_ONLY, flags, vpts, rect, fbo, false); + pass_draw_osd(p, OSD_DRAW_SUB_ONLY, flags, vpts, rect, &fbo, false); pass_read_tex(p, p->blend_subs_tex); pass_describe(p, "blend subs"); } @@ -3055,7 +3079,7 @@ static bool pass_render_frame(struct gl_video *p, struct mp_image *mpi, return true; } -static void pass_draw_to_screen(struct gl_video *p, struct ra_fbo fbo, int flags) +static void pass_draw_to_screen(struct gl_video *p, const struct ra_fbo *fbo, int flags) { if (p->dumb_mode) pass_render_frame_dumb(p); @@ -3067,7 +3091,8 @@ static void pass_draw_to_screen(struct gl_video *p, struct ra_fbo fbo, int flags GLSL(color.rgb = pow(color.rgb, vec3(user_gamma));) } - pass_colormanage(p, p->image_params.color, fbo.color_space, flags, false); + pass_colormanage(p, p->image_params.color, p->image_params.light, + fbo->color_space, flags, false); // Since finish_pass_fbo doesn't work with compute shaders, and neither // does the checkerboard/dither code, we may need an indirection via @@ -3080,28 +3105,30 @@ static void pass_draw_to_screen(struct gl_video *p, struct ra_fbo fbo, int flags copy_image(p, &(int){0}, tmp); } - if (p->has_alpha){ - if (p->opts.alpha_mode == ALPHA_BLEND_TILES) { + if (p->has_alpha) { + if (p->opts.background == BACKGROUND_TILES) { // Draw checkerboard pattern to indicate transparency GLSLF("// transparency checkerboard\n"); - GLSL(bvec2 tile = lessThan(fract(gl_FragCoord.xy * 1.0/32.0), vec2(0.5));) + GLSLF("vec2 tile_coord = vec2(gl_FragCoord.x, %d.0 + %f * gl_FragCoord.y);", + fbo->flip ? fbo->tex->params.h : 0, fbo->flip ? -1.0 : 1.0); + GLSL(bvec2 tile = lessThan(fract(tile_coord * 1.0 / 32.0), vec2(0.5));) GLSL(vec3 background = vec3(tile.x == tile.y ? 0.93 : 0.87);) GLSL(color.rgb += background.rgb * (1.0 - color.a);) GLSL(color.a = 1.0;) - } else if (p->opts.alpha_mode == ALPHA_BLEND) { + } else if (p->opts.background == BACKGROUND_COLOR) { // Blend into background color (usually black) - struct m_color c = p->opts.background; + struct m_color c = p->opts.background_color; GLSLF("vec4 background = vec4(%f, %f, %f, %f);\n", c.r / 255.0, c.g / 255.0, c.b / 255.0, c.a / 255.0); - GLSL(color.rgb += background.rgb * (1.0 - color.a);) - GLSL(color.a = background.a;) + GLSL(color += background * (1.0 - color.a);) + GLSL(color.rgb *= vec3(color.a);); } } pass_opt_hook_point(p, "OUTPUT", NULL); if (flags & RENDER_SCREEN_COLOR) - pass_dither(p); + pass_dither(p, fbo); pass_describe(p, "output to screen"); finish_pass_fbo(p, fbo, false, &p->dst_rect); } @@ -3122,7 +3149,7 @@ static bool update_surface(struct gl_video *p, struct mp_image *mpi, // because mixing in compressed light artificially darkens the results if (!p->use_linear) { p->use_linear = true; - pass_linearize(p->sc, p->image_params.color.gamma); + pass_linearize(p->sc, p->image_params.color.transfer); } finish_pass_tex(p, &surf->tex, vp_w, vp_h); @@ -3134,7 +3161,7 @@ static bool update_surface(struct gl_video *p, struct mp_image *mpi, // Draws an interpolate frame to fbo, based on the frame timing in t // flags: bit set of RENDER_FRAME_* flags static void gl_video_interpolate_frame(struct gl_video *p, struct vo_frame *t, - struct ra_fbo fbo, int flags) + const struct ra_fbo *fbo, int flags) { bool is_new = false; @@ -3201,7 +3228,7 @@ static void gl_video_interpolate_frame(struct gl_video *p, struct vo_frame *t, struct mp_image *f = t->frames[i]; uint64_t f_id = t->frame_id + i; - if (!mp_image_params_equal(&f->params, &p->real_image_params)) + if (!mp_image_params_static_equal(&f->params, &p->real_image_params)) continue; if (f_id > p->surfaces[p->surface_idx].id) { @@ -3306,11 +3333,11 @@ static void gl_video_interpolate_frame(struct gl_video *p, struct vo_frame *t, } void gl_video_render_frame(struct gl_video *p, struct vo_frame *frame, - struct ra_fbo fbo, int flags) + const struct ra_fbo *fbo, int flags) { gl_video_update_options(p); - struct mp_rect target_rc = {0, 0, fbo.tex->params.w, fbo.tex->params.h}; + struct mp_rect target_rc = {0, 0, fbo->tex->params.w, fbo->tex->params.h}; p->broken_frame = false; @@ -3318,12 +3345,15 @@ void gl_video_render_frame(struct gl_video *p, struct vo_frame *frame, struct m_color c = p->clear_color; float clear_color[4] = {c.r / 255.0, c.g / 255.0, c.b / 255.0, c.a / 255.0}; - p->ra->fns->clear(p->ra, fbo.tex, clear_color, &target_rc); + clear_color[0] *= clear_color[3]; + clear_color[1] *= clear_color[3]; + clear_color[2] *= clear_color[3]; + p->ra->fns->clear(p->ra, fbo->tex, clear_color, &target_rc); if (p->hwdec_overlay) { if (has_frame) { float *color = p->hwdec_overlay->overlay_colorkey; - p->ra->fns->clear(p->ra, fbo.tex, color, &p->dst_rect); + p->ra->fns->clear(p->ra, fbo->tex, color, &p->dst_rect); } p->hwdec_overlay->driver->overlay_frame(p->hwdec_overlay, frame->current, @@ -3364,43 +3394,41 @@ void gl_video_render_frame(struct gl_video *p, struct vo_frame *frame, // For the non-interpolation case, we draw to a single "cache" // texture to speed up subsequent re-draws (if any exist) - struct ra_fbo dest_fbo = fbo; bool repeats = frame->num_vsyncs > 1 && frame->display_synced; + bool r = false; if ((repeats || frame->still) && !p->dumb_mode && - (p->ra->caps & RA_CAP_BLIT) && fbo.tex->params.blit_dst) + (p->ra->caps & RA_CAP_BLIT) && fbo->tex->params.blit_dst) { // Attempt to use the same format as the destination FBO // if possible. Some RAs use a wrapped dummy format here, // so fall back to the fbo_format in that case. - const struct ra_format *fmt = fbo.tex->params.format; + const struct ra_format *fmt = fbo->tex->params.format; if (fmt->dummy_format) fmt = p->fbo_format; - - bool r = ra_tex_resize(p->ra, p->log, &p->output_tex, - fbo.tex->params.w, fbo.tex->params.h, - fmt); - if (r) { - dest_fbo = (struct ra_fbo) { p->output_tex }; - p->output_tex_valid = true; - } + r = ra_tex_resize(p->ra, p->log, &p->output_tex, + fbo->tex->params.w, fbo->tex->params.h, + fmt); } + const struct ra_fbo *dest_fbo = r ? &(struct ra_fbo) { p->output_tex } : fbo; + p->output_tex_valid = r; pass_draw_to_screen(p, dest_fbo, flags); } // "output tex valid" and "output tex needed" are equivalent - if (p->output_tex_valid && fbo.tex->params.blit_dst) { + if (p->output_tex_valid && fbo->tex->params.blit_dst) { pass_info_reset(p, true); pass_describe(p, "redraw cached frame"); struct mp_rect src = p->dst_rect; struct mp_rect dst = src; - if (fbo.flip) { - dst.y0 = fbo.tex->params.h - src.y0; - dst.y1 = fbo.tex->params.h - src.y1; + if (fbo->flip) { + dst.y0 = fbo->tex->params.h - src.y0; + dst.y1 = fbo->tex->params.h - src.y1; } timer_pool_start(p->blit_timer); - p->ra->fns->blit(p->ra, fbo.tex, p->output_tex, &dst, &src); + p->ra->fns->blit(p->ra, fbo->tex, p->output_tex, &dst, &src); timer_pool_stop(p->blit_timer); - pass_record(p, timer_pool_measure(p->blit_timer)); + struct mp_pass_perf perf = timer_pool_measure(p->blit_timer); + pass_record(p, &perf); } } } @@ -3431,7 +3459,7 @@ done: // Make the screen solid blue to make it visually clear that an // error has occurred float color[4] = {0.0, 0.05, 0.5, 1.0}; - p->ra->fns->clear(p->ra, fbo.tex, color, &target_rc); + p->ra->fns->clear(p->ra, fbo->tex, color, &target_rc); } p->frames_rendered++; @@ -3522,7 +3550,7 @@ void gl_video_screenshot(struct gl_video *p, struct vo_frame *frame, flags |= RENDER_FRAME_OSD; if (args->scaled) flags |= RENDER_SCREEN_COLOR; - gl_video_render_frame(p, nframe, (struct ra_fbo){target}, flags); + gl_video_render_frame(p, nframe, &(struct ra_fbo){target}, flags); res = mp_image_alloc(mpfmt, params.w, params.h); if (!res) @@ -3636,7 +3664,8 @@ static bool pass_upload_image(struct gl_video *p, struct mp_image *mpi, uint64_t timer_pool_start(p->upload_timer); bool ok = ra_hwdec_mapper_map(p->hwdec_mapper, vimg->mpi) >= 0; timer_pool_stop(p->upload_timer); - pass_record(p, timer_pool_measure(p->upload_timer)); + struct mp_pass_perf perf = timer_pool_measure(p->upload_timer); + pass_record(p, &perf); vimg->hwdec_mapped = true; if (ok) { @@ -3708,7 +3737,8 @@ static bool pass_upload_image(struct gl_video *p, struct mp_image *mpi, uint64_t bool using_pbo = p->ra->use_pbo || !(p->ra->caps & RA_CAP_DIRECT_UPLOAD); const char *mode = p->using_dr_path ? "DR" : using_pbo ? "PBO" : "naive"; pass_describe(p, "upload frame (%s)", mode); - pass_record(p, timer_pool_measure(p->upload_timer)); + struct mp_pass_perf perf = timer_pool_measure(p->upload_timer); + pass_record(p, &perf); return true; @@ -3806,9 +3836,8 @@ static void check_gl_features(struct gl_video *p) p->opts.dither_algo = DITHER_NONE; MP_WARN(p, "Disabling dithering (no gl_FragCoord).\n"); } - if (!have_fragcoord && p->opts.alpha_mode == ALPHA_BLEND_TILES) { - p->opts.alpha_mode = ALPHA_BLEND; - // Verbose, since this is the default setting + if (!have_fragcoord && p->opts.background == BACKGROUND_TILES) { + p->opts.background = BACKGROUND_COLOR; MP_VERBOSE(p, "Disabling alpha checkerboard (no gl_FragCoord).\n"); } if (!have_fbo && have_compute) { @@ -3864,9 +3893,9 @@ static void check_gl_features(struct gl_video *p) .gamma_auto = p->opts.gamma_auto, .pbo = p->opts.pbo, .fbo_format = p->opts.fbo_format, - .alpha_mode = p->opts.alpha_mode, - .use_rectangle = p->opts.use_rectangle, .background = p->opts.background, + .use_rectangle = p->opts.use_rectangle, + .background_color = p->opts.background_color, .dither_algo = p->opts.dither_algo, .dither_depth = p->opts.dither_depth, .dither_size = p->opts.dither_size, @@ -3913,8 +3942,8 @@ static void check_gl_features(struct gl_video *p) } } - int use_cms = p->opts.target_prim != MP_CSP_PRIM_AUTO || - p->opts.target_trc != MP_CSP_TRC_AUTO || p->use_lut_3d; + int use_cms = p->opts.target_prim != PL_COLOR_PRIM_UNKNOWN || + p->opts.target_trc != PL_COLOR_TRC_UNKNOWN || p->use_lut_3d; // mix() is needed for some gamma functions if (!have_mglsl && (p->opts.linear_downscaling || @@ -3926,8 +3955,8 @@ static void check_gl_features(struct gl_video *p) MP_WARN(p, "Disabling linear/sigmoid scaling (GLSL version too old).\n"); } if (!have_mglsl && use_cms) { - p->opts.target_prim = MP_CSP_PRIM_AUTO; - p->opts.target_trc = MP_CSP_TRC_AUTO; + p->opts.target_prim = PL_COLOR_PRIM_UNKNOWN; + p->opts.target_trc = PL_COLOR_TRC_UNKNOWN; p->use_lut_3d = false; MP_WARN(p, "Disabling color management (GLSL version too old).\n"); } @@ -4022,7 +4051,7 @@ void gl_video_config(struct gl_video *p, struct mp_image_params *params) unmap_overlay(p); unref_current_image(p); - if (!mp_image_params_equal(&p->real_image_params, params)) { + if (!mp_image_params_static_equal(&p->real_image_params, params)) { uninit_video(p); p->real_image_params = *params; p->image_params = *params; @@ -4117,7 +4146,7 @@ static void reinit_from_options(struct gl_video *p) p->opts = *(struct gl_video_opts *)p->opts_cache->opts; if (!p->force_clear_color) - p->clear_color = p->opts.background; + p->clear_color = p->opts.background_color; check_gl_features(p); uninit_rendering(p); @@ -4362,3 +4391,8 @@ void gl_video_load_hwdecs_for_img_fmt(struct gl_video *p, struct mp_hwdec_device assert(p->hwdec_ctx.ra_ctx); ra_hwdec_ctx_load_fmt(&p->hwdec_ctx, devs, params); } + +struct mp_image_params *gl_video_get_target_params_ptr(struct gl_video *p) +{ + return &p->target_params; +} diff --git a/video/out/gpu/video.h b/video/out/gpu/video.h index 411d336..66ccd9c 100644 --- a/video/out/gpu/video.h +++ b/video/out/gpu/video.h @@ -72,11 +72,10 @@ enum dither_algo { DITHER_ERROR_DIFFUSION, }; -enum alpha_mode { - ALPHA_NO = 0, - ALPHA_YES, - ALPHA_BLEND, - ALPHA_BLEND_TILES, +enum background_type { + BACKGROUND_NONE = 0, + BACKGROUND_COLOR, + BACKGROUND_TILES, }; enum blend_subs_mode { @@ -155,9 +154,9 @@ struct gl_video_opts { int temporal_dither_period; char *error_diffusion; char *fbo_format; - int alpha_mode; + int background; bool use_rectangle; - struct m_color background; + struct m_color background_color; bool interpolation; float interpolation_threshold; int blend_subs; @@ -195,7 +194,7 @@ void gl_video_set_osd_source(struct gl_video *p, struct osd_state *osd); bool gl_video_check_format(struct gl_video *p, int mp_format); void gl_video_config(struct gl_video *p, struct mp_image_params *params); void gl_video_render_frame(struct gl_video *p, struct vo_frame *frame, - struct ra_fbo fbo, int flags); + const struct ra_fbo *fbo, int flags); void gl_video_resize(struct gl_video *p, struct mp_rect *src, struct mp_rect *dst, struct mp_osd_res *osd); @@ -215,7 +214,6 @@ void gl_video_set_ambient_lux(struct gl_video *p, int lux); void gl_video_set_icc_profile(struct gl_video *p, bstr icc_data); bool gl_video_icc_auto_enabled(struct gl_video *p); bool gl_video_gamma_auto_enabled(struct gl_video *p); -struct mp_colorspace gl_video_get_output_colorspace(struct gl_video *p); void gl_video_reset(struct gl_video *p); bool gl_video_showing_interpolated_frame(struct gl_video *p); @@ -234,5 +232,6 @@ void gl_video_configure_queue(struct gl_video *p, struct vo *vo); struct mp_image *gl_video_get_image(struct gl_video *p, int imgfmt, int w, int h, int stride_align, int flags); +struct mp_image_params *gl_video_get_target_params_ptr(struct gl_video *p); #endif diff --git a/video/out/gpu/video_shaders.c b/video/out/gpu/video_shaders.c index 6c0e8a8..e202818 100644 --- a/video/out/gpu/video_shaders.c +++ b/video/out/gpu/video_shaders.c @@ -17,6 +17,8 @@ #include <math.h> +#include <libplacebo/colorspace.h> + #include "video_shaders.h" #include "video.h" @@ -252,7 +254,7 @@ void pass_compute_polar(struct gl_shader_cache *sc, struct scaler *scaler, static void bicubic_calcweights(struct gl_shader_cache *sc, const char *t, const char *s) { // Explanation of how bicubic scaling with only 4 texel fetches is done: - // http://www.mate.tue.nl/mate/pdfs/10318.pdf + // <https://web.archive.org/web/20180720154854/http://www.mate.tue.nl/mate/pdfs/10318.pdf> // 'Efficient GPU-Based Texture Interpolation using Uniform B-Splines' // Explanation why this algorithm normally always blurs, even with unit // scaling: @@ -337,10 +339,10 @@ static const float SLOG_A = 0.432699, // // These functions always output to a normalized scale of [0,1], for // convenience of the video.c code that calls it. To get the values in an -// absolute scale, multiply the result by `mp_trc_nom_peak(trc)` -void pass_linearize(struct gl_shader_cache *sc, enum mp_csp_trc trc) +// absolute scale, multiply the result by `pl_color_transfer_nominal_peak(trc)` +void pass_linearize(struct gl_shader_cache *sc, enum pl_color_transfer trc) { - if (trc == MP_CSP_TRC_LINEAR) + if (trc == PL_COLOR_TRC_LINEAR) return; GLSLF("// linearize\n"); @@ -353,40 +355,40 @@ void pass_linearize(struct gl_shader_cache *sc, enum mp_csp_trc trc) GLSL(color.rgb = clamp(color.rgb, 0.0, 1.0);) switch (trc) { - case MP_CSP_TRC_SRGB: + case PL_COLOR_TRC_SRGB: GLSLF("color.rgb = mix(color.rgb * vec3(1.0/12.92), \n" " pow((color.rgb + vec3(0.055))/vec3(1.055), vec3(2.4)), \n" " %s(lessThan(vec3(0.04045), color.rgb))); \n", gl_sc_bvec(sc, 3)); break; - case MP_CSP_TRC_BT_1886: + case PL_COLOR_TRC_BT_1886: GLSL(color.rgb = pow(color.rgb, vec3(2.4));) break; - case MP_CSP_TRC_GAMMA18: + case PL_COLOR_TRC_GAMMA18: GLSL(color.rgb = pow(color.rgb, vec3(1.8));) break; - case MP_CSP_TRC_GAMMA20: + case PL_COLOR_TRC_GAMMA20: GLSL(color.rgb = pow(color.rgb, vec3(2.0));) break; - case MP_CSP_TRC_GAMMA22: + case PL_COLOR_TRC_GAMMA22: GLSL(color.rgb = pow(color.rgb, vec3(2.2));) break; - case MP_CSP_TRC_GAMMA24: + case PL_COLOR_TRC_GAMMA24: GLSL(color.rgb = pow(color.rgb, vec3(2.4));) break; - case MP_CSP_TRC_GAMMA26: + case PL_COLOR_TRC_GAMMA26: GLSL(color.rgb = pow(color.rgb, vec3(2.6));) break; - case MP_CSP_TRC_GAMMA28: + case PL_COLOR_TRC_GAMMA28: GLSL(color.rgb = pow(color.rgb, vec3(2.8));) break; - case MP_CSP_TRC_PRO_PHOTO: + case PL_COLOR_TRC_PRO_PHOTO: GLSLF("color.rgb = mix(color.rgb * vec3(1.0/16.0), \n" " pow(color.rgb, vec3(1.8)), \n" " %s(lessThan(vec3(0.03125), color.rgb))); \n", gl_sc_bvec(sc, 3)); break; - case MP_CSP_TRC_PQ: + case PL_COLOR_TRC_PQ: GLSLF("color.rgb = pow(color.rgb, vec3(1.0/%f));\n", PQ_M2); GLSLF("color.rgb = max(color.rgb - vec3(%f), vec3(0.0)) \n" " / (vec3(%f) - vec3(%f) * color.rgb);\n", @@ -396,33 +398,33 @@ void pass_linearize(struct gl_shader_cache *sc, enum mp_csp_trc trc) // MP_REF_WHITE instead, so rescale GLSLF("color.rgb *= vec3(%f);\n", 10000 / MP_REF_WHITE); break; - case MP_CSP_TRC_HLG: + case PL_COLOR_TRC_HLG: GLSLF("color.rgb = mix(vec3(4.0) * color.rgb * color.rgb,\n" " exp((color.rgb - vec3(%f)) * vec3(1.0/%f)) + vec3(%f),\n" " %s(lessThan(vec3(0.5), color.rgb)));\n", HLG_C, HLG_A, HLG_B, gl_sc_bvec(sc, 3)); GLSLF("color.rgb *= vec3(1.0/%f);\n", MP_REF_WHITE_HLG); break; - case MP_CSP_TRC_V_LOG: + case PL_COLOR_TRC_V_LOG: GLSLF("color.rgb = mix((color.rgb - vec3(0.125)) * vec3(1.0/5.6), \n" " pow(vec3(10.0), (color.rgb - vec3(%f)) * vec3(1.0/%f)) \n" " - vec3(%f), \n" " %s(lessThanEqual(vec3(0.181), color.rgb))); \n", VLOG_D, VLOG_C, VLOG_B, gl_sc_bvec(sc, 3)); break; - case MP_CSP_TRC_S_LOG1: + case PL_COLOR_TRC_S_LOG1: GLSLF("color.rgb = pow(vec3(10.0), (color.rgb - vec3(%f)) * vec3(1.0/%f))\n" " - vec3(%f);\n", SLOG_C, SLOG_A, SLOG_B); break; - case MP_CSP_TRC_S_LOG2: + case PL_COLOR_TRC_S_LOG2: GLSLF("color.rgb = mix((color.rgb - vec3(%f)) * vec3(1.0/%f), \n" " (pow(vec3(10.0), (color.rgb - vec3(%f)) * vec3(1.0/%f)) \n" " - vec3(%f)) * vec3(1.0/%f), \n" " %s(lessThanEqual(vec3(%f), color.rgb))); \n", SLOG_Q, SLOG_P, SLOG_C, SLOG_A, SLOG_B, SLOG_K2, gl_sc_bvec(sc, 3), SLOG_Q); break; - case MP_CSP_TRC_ST428: + case PL_COLOR_TRC_ST428: GLSL(color.rgb = vec3(52.37/48.0) * pow(color.rgb, vec3(2.6));); break; default: @@ -430,7 +432,7 @@ void pass_linearize(struct gl_shader_cache *sc, enum mp_csp_trc trc) } // Rescale to prevent clipping on non-float textures - GLSLF("color.rgb *= vec3(1.0/%f);\n", mp_trc_nom_peak(trc)); + GLSLF("color.rgb *= vec3(1.0/%f);\n", pl_color_transfer_nominal_peak(trc)); } // Delinearize (compress), given a TRC as output. This corresponds to the @@ -438,51 +440,51 @@ void pass_linearize(struct gl_shader_cache *sc, enum mp_csp_trc trc) // reference monitor. // // Like pass_linearize, this functions ingests values on an normalized scale -void pass_delinearize(struct gl_shader_cache *sc, enum mp_csp_trc trc) +void pass_delinearize(struct gl_shader_cache *sc, enum pl_color_transfer trc) { - if (trc == MP_CSP_TRC_LINEAR) + if (trc == PL_COLOR_TRC_LINEAR) return; GLSLF("// delinearize\n"); GLSL(color.rgb = clamp(color.rgb, 0.0, 1.0);) - GLSLF("color.rgb *= vec3(%f);\n", mp_trc_nom_peak(trc)); + GLSLF("color.rgb *= vec3(%f);\n", pl_color_transfer_nominal_peak(trc)); switch (trc) { - case MP_CSP_TRC_SRGB: + case PL_COLOR_TRC_SRGB: GLSLF("color.rgb = mix(color.rgb * vec3(12.92), \n" " vec3(1.055) * pow(color.rgb, vec3(1.0/2.4)) \n" " - vec3(0.055), \n" " %s(lessThanEqual(vec3(0.0031308), color.rgb))); \n", gl_sc_bvec(sc, 3)); break; - case MP_CSP_TRC_BT_1886: + case PL_COLOR_TRC_BT_1886: GLSL(color.rgb = pow(color.rgb, vec3(1.0/2.4));) break; - case MP_CSP_TRC_GAMMA18: + case PL_COLOR_TRC_GAMMA18: GLSL(color.rgb = pow(color.rgb, vec3(1.0/1.8));) break; - case MP_CSP_TRC_GAMMA20: + case PL_COLOR_TRC_GAMMA20: GLSL(color.rgb = pow(color.rgb, vec3(1.0/2.0));) break; - case MP_CSP_TRC_GAMMA22: + case PL_COLOR_TRC_GAMMA22: GLSL(color.rgb = pow(color.rgb, vec3(1.0/2.2));) break; - case MP_CSP_TRC_GAMMA24: + case PL_COLOR_TRC_GAMMA24: GLSL(color.rgb = pow(color.rgb, vec3(1.0/2.4));) break; - case MP_CSP_TRC_GAMMA26: + case PL_COLOR_TRC_GAMMA26: GLSL(color.rgb = pow(color.rgb, vec3(1.0/2.6));) break; - case MP_CSP_TRC_GAMMA28: + case PL_COLOR_TRC_GAMMA28: GLSL(color.rgb = pow(color.rgb, vec3(1.0/2.8));) break; - case MP_CSP_TRC_PRO_PHOTO: + case PL_COLOR_TRC_PRO_PHOTO: GLSLF("color.rgb = mix(color.rgb * vec3(16.0), \n" " pow(color.rgb, vec3(1.0/1.8)), \n" " %s(lessThanEqual(vec3(0.001953), color.rgb))); \n", gl_sc_bvec(sc, 3)); break; - case MP_CSP_TRC_PQ: + case PL_COLOR_TRC_PQ: GLSLF("color.rgb *= vec3(1.0/%f);\n", 10000 / MP_REF_WHITE); GLSLF("color.rgb = pow(color.rgb, vec3(%f));\n", PQ_M1); GLSLF("color.rgb = (vec3(%f) + vec3(%f) * color.rgb) \n" @@ -490,32 +492,32 @@ void pass_delinearize(struct gl_shader_cache *sc, enum mp_csp_trc trc) PQ_C1, PQ_C2, PQ_C3); GLSLF("color.rgb = pow(color.rgb, vec3(%f));\n", PQ_M2); break; - case MP_CSP_TRC_HLG: + case PL_COLOR_TRC_HLG: GLSLF("color.rgb *= vec3(%f);\n", MP_REF_WHITE_HLG); GLSLF("color.rgb = mix(vec3(0.5) * sqrt(color.rgb),\n" " vec3(%f) * log(color.rgb - vec3(%f)) + vec3(%f),\n" " %s(lessThan(vec3(1.0), color.rgb)));\n", HLG_A, HLG_B, HLG_C, gl_sc_bvec(sc, 3)); break; - case MP_CSP_TRC_V_LOG: + case PL_COLOR_TRC_V_LOG: GLSLF("color.rgb = mix(vec3(5.6) * color.rgb + vec3(0.125), \n" " vec3(%f) * log(color.rgb + vec3(%f)) \n" " + vec3(%f), \n" " %s(lessThanEqual(vec3(0.01), color.rgb))); \n", VLOG_C / M_LN10, VLOG_B, VLOG_D, gl_sc_bvec(sc, 3)); break; - case MP_CSP_TRC_S_LOG1: + case PL_COLOR_TRC_S_LOG1: GLSLF("color.rgb = vec3(%f) * log(color.rgb + vec3(%f)) + vec3(%f);\n", SLOG_A / M_LN10, SLOG_B, SLOG_C); break; - case MP_CSP_TRC_S_LOG2: + case PL_COLOR_TRC_S_LOG2: GLSLF("color.rgb = mix(vec3(%f) * color.rgb + vec3(%f), \n" " vec3(%f) * log(vec3(%f) * color.rgb + vec3(%f)) \n" " + vec3(%f), \n" " %s(lessThanEqual(vec3(0.0), color.rgb))); \n", SLOG_P, SLOG_Q, SLOG_A / M_LN10, SLOG_K2, SLOG_B, SLOG_C, gl_sc_bvec(sc, 3)); break; - case MP_CSP_TRC_ST428: + case PL_COLOR_TRC_ST428: GLSL(color.rgb = pow(color.rgb * vec3(48.0/52.37), vec3(1.0/2.6));); break; default: @@ -834,42 +836,42 @@ static void pass_tone_map(struct gl_shader_cache *sc, // the caller to have already bound the appropriate SSBO and set up the compute // shader metadata void pass_color_map(struct gl_shader_cache *sc, bool is_linear, - struct mp_colorspace src, struct mp_colorspace dst, + struct pl_color_space src, struct pl_color_space dst, + enum mp_csp_light src_light, enum mp_csp_light dst_light, const struct gl_tone_map_opts *opts) { GLSLF("// color mapping\n"); // Some operations need access to the video's luma coefficients, so make // them available - float rgb2xyz[3][3]; - mp_get_rgb2xyz_matrix(mp_get_csp_primaries(src.primaries), rgb2xyz); - gl_sc_uniform_vec3(sc, "src_luma", rgb2xyz[1]); - mp_get_rgb2xyz_matrix(mp_get_csp_primaries(dst.primaries), rgb2xyz); - gl_sc_uniform_vec3(sc, "dst_luma", rgb2xyz[1]); - - bool need_ootf = src.light != dst.light; - if (src.light == MP_CSP_LIGHT_SCENE_HLG && src.hdr.max_luma != dst.hdr.max_luma) + pl_matrix3x3 rgb2xyz = pl_get_rgb2xyz_matrix(pl_raw_primaries_get(src.primaries)); + gl_sc_uniform_vec3(sc, "src_luma", rgb2xyz.m[1]); + rgb2xyz = pl_get_rgb2xyz_matrix(pl_raw_primaries_get(dst.primaries)); + gl_sc_uniform_vec3(sc, "dst_luma", rgb2xyz.m[1]); + + bool need_ootf = src_light != dst_light; + if (src_light == MP_CSP_LIGHT_SCENE_HLG && src.hdr.max_luma != dst.hdr.max_luma) need_ootf = true; // All operations from here on require linear light as a starting point, - // so we linearize even if src.gamma == dst.gamma when one of the other + // so we linearize even if src.gamma == dst.transfer when one of the other // operations needs it - bool need_linear = src.gamma != dst.gamma || + bool need_linear = src.transfer != dst.transfer || src.primaries != dst.primaries || src.hdr.max_luma != dst.hdr.max_luma || need_ootf; if (need_linear && !is_linear) { // We also pull it up so that 1.0 is the reference white - pass_linearize(sc, src.gamma); + pass_linearize(sc, src.transfer); is_linear = true; } // Pre-scale the incoming values into an absolute scale - GLSLF("color.rgb *= vec3(%f);\n", mp_trc_nom_peak(src.gamma)); + GLSLF("color.rgb *= vec3(%f);\n", pl_color_transfer_nominal_peak(src.transfer)); if (need_ootf) - pass_ootf(sc, src.light, src.hdr.max_luma / MP_REF_WHITE); + pass_ootf(sc, src_light, src.hdr.max_luma / MP_REF_WHITE); // Tone map to prevent clipping due to excessive brightness if (src.hdr.max_luma > dst.hdr.max_luma) { @@ -879,11 +881,11 @@ void pass_color_map(struct gl_shader_cache *sc, bool is_linear, // Adapt to the right colorspace if necessary if (src.primaries != dst.primaries) { - struct mp_csp_primaries csp_src = mp_get_csp_primaries(src.primaries), - csp_dst = mp_get_csp_primaries(dst.primaries); - float m[3][3] = {{0}}; - mp_get_cms_matrix(csp_src, csp_dst, MP_INTENT_RELATIVE_COLORIMETRIC, m); - gl_sc_uniform_mat3(sc, "cms_matrix", true, &m[0][0]); + const struct pl_raw_primaries *csp_src = pl_raw_primaries_get(src.primaries), + *csp_dst = pl_raw_primaries_get(dst.primaries); + pl_matrix3x3 m = pl_get_color_mapping_matrix(csp_src, csp_dst, + PL_INTENT_RELATIVE_COLORIMETRIC); + gl_sc_uniform_mat3(sc, "cms_matrix", true, &m.m[0][0]); GLSL(color.rgb = cms_matrix * color.rgb;) if (!opts->gamut_mode || opts->gamut_mode == GAMUT_DESATURATE) { @@ -900,14 +902,14 @@ void pass_color_map(struct gl_shader_cache *sc, bool is_linear, } if (need_ootf) - pass_inverse_ootf(sc, dst.light, dst.hdr.max_luma / MP_REF_WHITE); + pass_inverse_ootf(sc, dst_light, dst.hdr.max_luma / MP_REF_WHITE); // Post-scale the outgoing values from absolute scale to normalized. // For SDR, we normalize to the chosen signal peak. For HDR, we normalize // to the encoding range of the transfer function. float dst_range = dst.hdr.max_luma / MP_REF_WHITE; - if (mp_trc_is_hdr(dst.gamma)) - dst_range = mp_trc_nom_peak(dst.gamma); + if (pl_color_space_is_hdr(&dst)) + dst_range = pl_color_transfer_nominal_peak(dst.transfer); GLSLF("color.rgb *= vec3(%f);\n", 1.0 / dst_range); @@ -919,7 +921,7 @@ void pass_color_map(struct gl_shader_cache *sc, bool is_linear, } if (is_linear) - pass_delinearize(sc, dst.gamma); + pass_delinearize(sc, dst.transfer); } // Wide usage friendly PRNG, shamelessly stolen from a GLSL tricks forum post. @@ -964,7 +966,7 @@ const struct m_sub_options deband_conf = { // Stochastically sample a debanded result from a hooked texture. void pass_sample_deband(struct gl_shader_cache *sc, struct deband_opts *opts, - AVLFG *lfg, enum mp_csp_trc trc) + AVLFG *lfg, enum pl_color_transfer trc) { // Initialize the PRNG GLSLF("{\n"); @@ -1008,7 +1010,7 @@ void pass_sample_deband(struct gl_shader_cache *sc, struct deband_opts *opts, GLSL(noise.z = rand(h); h = permute(h);) // Noise is scaled to the signal level to prevent extreme noise for HDR - float gain = opts->grain/8192.0 / mp_trc_nom_peak(trc); + float gain = opts->grain/8192.0 / pl_color_transfer_nominal_peak(trc); GLSLF("color.xyz += %f * (noise - vec3(0.5));\n", gain); GLSLF("}\n"); } diff --git a/video/out/gpu/video_shaders.h b/video/out/gpu/video_shaders.h index 27e7874..7547df6 100644 --- a/video/out/gpu/video_shaders.h +++ b/video/out/gpu/video_shaders.h @@ -44,15 +44,16 @@ void pass_sample_bicubic_fast(struct gl_shader_cache *sc); void pass_sample_oversample(struct gl_shader_cache *sc, struct scaler *scaler, int w, int h); -void pass_linearize(struct gl_shader_cache *sc, enum mp_csp_trc trc); -void pass_delinearize(struct gl_shader_cache *sc, enum mp_csp_trc trc); +void pass_linearize(struct gl_shader_cache *sc, enum pl_color_transfer trc); +void pass_delinearize(struct gl_shader_cache *sc, enum pl_color_transfer trc); void pass_color_map(struct gl_shader_cache *sc, bool is_linear, - struct mp_colorspace src, struct mp_colorspace dst, + struct pl_color_space src, struct pl_color_space dst, + enum mp_csp_light src_light, enum mp_csp_light dst_light, const struct gl_tone_map_opts *opts); void pass_sample_deband(struct gl_shader_cache *sc, struct deband_opts *opts, - AVLFG *lfg, enum mp_csp_trc trc); + AVLFG *lfg, enum pl_color_transfer trc); void pass_sample_unsharp(struct gl_shader_cache *sc, float param); diff --git a/video/out/gpu_next/context.c b/video/out/gpu_next/context.c index 2887cff..2c7c9fa 100644 --- a/video/out/gpu_next/context.c +++ b/video/out/gpu_next/context.c @@ -88,6 +88,7 @@ static bool d3d11_pl_init(struct vo *vo, struct gpu_ctx *ctx, ctx->swapchain = pl_d3d11_create_swapchain(d3d11, pl_d3d11_swapchain_params( .swapchain = swapchain, + .disable_10bit_sdr = ra_d3d11_ctx_prefer_8bit_output_format(ctx->ra_ctx), ) ); if (!ctx->swapchain) { @@ -106,13 +107,10 @@ err_out: } #endif // HAVE_D3D11 -struct gpu_ctx *gpu_ctx_create(struct vo *vo, struct gl_video_opts *gl_opts) +struct gpu_ctx *gpu_ctx_create(struct vo *vo, struct ra_ctx_opts *ctx_opts) { struct gpu_ctx *ctx = talloc_zero(NULL, struct gpu_ctx); ctx->log = vo->log; - - struct ra_ctx_opts *ctx_opts = mp_get_config_group(ctx, vo->global, &ra_ctx_conf); - ctx_opts->want_alpha = gl_opts->alpha_mode == ALPHA_YES; ctx->ra_ctx = ra_ctx_create(vo, *ctx_opts); if (!ctx->ra_ctx) goto err_out; @@ -145,18 +143,17 @@ struct gpu_ctx *gpu_ctx_create(struct vo *vo, struct gl_video_opts *gl_opts) #if HAVE_GL && defined(PL_HAVE_OPENGL) if (ra_is_gl(ctx->ra_ctx->ra)) { struct GL *gl = ra_gl_get(ctx->ra_ctx->ra); - pl_opengl opengl = pl_opengl_create(ctx->pllog, - pl_opengl_params( - .debug = ctx_opts->debug, - .allow_software = ctx_opts->allow_sw, - .get_proc_addr_ex = (void *) gl->get_fn, - .proc_ctx = gl->fn_ctx, + struct pl_opengl_params params = *pl_opengl_params( + .debug = ctx_opts->debug, + .allow_software = ctx_opts->allow_sw, + .get_proc_addr_ex = (void *) gl->get_fn, + .proc_ctx = gl->fn_ctx, + ); # if HAVE_EGL - .egl_display = eglGetCurrentDisplay(), - .egl_context = eglGetCurrentContext(), + params.egl_display = eglGetCurrentDisplay(); + params.egl_context = eglGetCurrentContext(); # endif - ) - ); + pl_opengl opengl = pl_opengl_create(ctx->pllog, ¶ms); if (!opengl) goto err_out; ctx->gpu = opengl->gpu; diff --git a/video/out/gpu_next/context.h b/video/out/gpu_next/context.h index b98b9e7..aa44196 100644 --- a/video/out/gpu_next/context.h +++ b/video/out/gpu_next/context.h @@ -21,8 +21,8 @@ struct mp_log; struct ra_ctx; +struct ra_ctx_opts; struct vo; -struct gl_video_opts; struct gpu_ctx { struct mp_log *log; @@ -35,6 +35,6 @@ struct gpu_ctx { void *priv; }; -struct gpu_ctx *gpu_ctx_create(struct vo *vo, struct gl_video_opts *gl_opts); +struct gpu_ctx *gpu_ctx_create(struct vo *vo, struct ra_ctx_opts *ctx_opts); bool gpu_ctx_resize(struct gpu_ctx *ctx, int w, int h); void gpu_ctx_destroy(struct gpu_ctx **ctxp); diff --git a/video/out/hwdec/dmabuf_interop.h b/video/out/hwdec/dmabuf_interop.h index e9b3e8e..3bf01a0 100644 --- a/video/out/hwdec/dmabuf_interop.h +++ b/video/out/hwdec/dmabuf_interop.h @@ -38,7 +38,7 @@ struct dmabuf_interop { struct dmabuf_interop_priv { int num_planes; struct mp_image layout; - struct ra_tex *tex[4]; + struct ra_tex *tex[AV_DRM_MAX_PLANES]; AVDRMFrameDescriptor desc; bool surface_acquired; diff --git a/video/out/hwdec/dmabuf_interop_gl.c b/video/out/hwdec/dmabuf_interop_gl.c index e7fb103..0f6fb89 100644 --- a/video/out/hwdec/dmabuf_interop_gl.c +++ b/video/out/hwdec/dmabuf_interop_gl.c @@ -52,39 +52,28 @@ typedef void *EGLImageKHR; #define EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT 0x344A struct vaapi_gl_mapper_priv { - GLuint gl_textures[4]; - EGLImageKHR images[4]; + GLuint gl_textures[AV_DRM_MAX_PLANES]; + EGLImageKHR images[AV_DRM_MAX_PLANES]; + + const struct ra_format *planes[AV_DRM_MAX_PLANES]; EGLImageKHR (EGLAPIENTRY *CreateImageKHR)(EGLDisplay, EGLContext, EGLenum, EGLClientBuffer, const EGLint *); EGLBoolean (EGLAPIENTRY *DestroyImageKHR)(EGLDisplay, EGLImageKHR); void (EGLAPIENTRY *EGLImageTargetTexture2DOES)(GLenum, GLeglImageOES); + void (EGLAPIENTRY *EGLImageTargetTexStorageEXT)(GLenum, GLeglImageOES, + const GLint *); }; -static bool vaapi_gl_mapper_init(struct ra_hwdec_mapper *mapper, - const struct ra_imgfmt_desc *desc) +static bool gl_create_textures(struct ra_hwdec_mapper *mapper) { struct dmabuf_interop_priv *p_mapper = mapper->priv; - struct vaapi_gl_mapper_priv *p = talloc_ptrtype(NULL, p); - p_mapper->interop_mapper_priv = p; - - *p = (struct vaapi_gl_mapper_priv) { - // EGL_KHR_image_base - .CreateImageKHR = (void *)eglGetProcAddress("eglCreateImageKHR"), - .DestroyImageKHR = (void *)eglGetProcAddress("eglDestroyImageKHR"), - // GL_OES_EGL_image - .EGLImageTargetTexture2DOES = - (void *)eglGetProcAddress("glEGLImageTargetTexture2DOES"), - }; - - if (!p->CreateImageKHR || !p->DestroyImageKHR || - !p->EGLImageTargetTexture2DOES) - return false; + struct vaapi_gl_mapper_priv *p = p_mapper->interop_mapper_priv; GL *gl = ra_gl_get(mapper->ra); - gl->GenTextures(4, p->gl_textures); - for (int n = 0; n < desc->num_planes; n++) { + gl->GenTextures(AV_DRM_MAX_PLANES, p->gl_textures); + for (int n = 0; n < p_mapper->num_planes; n++) { gl->BindTexture(GL_TEXTURE_2D, p->gl_textures[n]); gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); @@ -97,7 +86,7 @@ static bool vaapi_gl_mapper_init(struct ra_hwdec_mapper *mapper, .w = mp_image_plane_w(&p_mapper->layout, n), .h = mp_image_plane_h(&p_mapper->layout, n), .d = 1, - .format = desc->planes[n], + .format = p->planes[n], .render_src = true, .src_linear = true, }; @@ -114,44 +103,99 @@ static bool vaapi_gl_mapper_init(struct ra_hwdec_mapper *mapper, return true; } +static void gl_delete_textures(const struct ra_hwdec_mapper *mapper) +{ + struct dmabuf_interop_priv *p_mapper = mapper->priv; + struct vaapi_gl_mapper_priv *p = p_mapper->interop_mapper_priv; + + GL *gl = ra_gl_get(mapper->ra); + gl->DeleteTextures(AV_DRM_MAX_PLANES, p->gl_textures); + for (int n = 0; n < AV_DRM_MAX_PLANES; n++) { + p->gl_textures[n] = 0; + ra_tex_free(mapper->ra, &p_mapper->tex[n]); + } +} + +static bool vaapi_gl_mapper_init(struct ra_hwdec_mapper *mapper, + const struct ra_imgfmt_desc *desc) +{ + struct dmabuf_interop_priv *p_mapper = mapper->priv; + struct vaapi_gl_mapper_priv *p = talloc_ptrtype(NULL, p); + p_mapper->interop_mapper_priv = p; + + *p = (struct vaapi_gl_mapper_priv) { + // EGL_KHR_image_base + .CreateImageKHR = (void *)eglGetProcAddress("eglCreateImageKHR"), + .DestroyImageKHR = (void *)eglGetProcAddress("eglDestroyImageKHR"), + }; + if (ra_gl_get(mapper->ra)->es) { + // GL_OES_EGL_image + p->EGLImageTargetTexture2DOES = + (void *)eglGetProcAddress("glEGLImageTargetTexture2DOES"); + } else { + // GL_EXT_EGL_image_storage + p->EGLImageTargetTexStorageEXT = + (void *)eglGetProcAddress("glEGLImageTargetTexStorageEXT"); + } + + if (!p->CreateImageKHR || !p->DestroyImageKHR || + (!p->EGLImageTargetTexture2DOES && !p->EGLImageTargetTexStorageEXT)) { + return false; + } + + static_assert(MP_ARRAY_SIZE(desc->planes) == AV_DRM_MAX_PLANES, ""); + static_assert(MP_ARRAY_SIZE(mapper->tex) == AV_DRM_MAX_PLANES, ""); + + // remember format to allow texture recreation + for (int n = 0; n < desc->num_planes; n++) { + p->planes[n] = desc->planes[n]; + } + if (p->EGLImageTargetTexture2DOES) { + // created only once + if (!gl_create_textures(mapper)) + return false; + } + + return true; +} + static void vaapi_gl_mapper_uninit(const struct ra_hwdec_mapper *mapper) { struct dmabuf_interop_priv *p_mapper = mapper->priv; struct vaapi_gl_mapper_priv *p = p_mapper->interop_mapper_priv; if (p) { - GL *gl = ra_gl_get(mapper->ra); - gl->DeleteTextures(4, p->gl_textures); - for (int n = 0; n < 4; n++) { - p->gl_textures[n] = 0; - ra_tex_free(mapper->ra, &p_mapper->tex[n]); - } + gl_delete_textures(mapper); talloc_free(p); p_mapper->interop_mapper_priv = NULL; } } -#define ADD_ATTRIB(name, value) \ - do { \ - assert(num_attribs + 3 < MP_ARRAY_SIZE(attribs)); \ - attribs[num_attribs++] = (name); \ - attribs[num_attribs++] = (value); \ - attribs[num_attribs] = EGL_NONE; \ +#define ADD_ATTRIB(name, value) \ + do { \ + assert(num_attribs + 3 < MP_ARRAY_SIZE(attribs)); \ + attribs[num_attribs++] = (name); \ + attribs[num_attribs++] = (value); \ + attribs[num_attribs] = EGL_NONE; \ } while(0) -#define ADD_PLANE_ATTRIBS(plane) do { \ - uint64_t drm_format_modifier = p_mapper->desc.objects[p_mapper->desc.layers[i].planes[j].object_index].format_modifier; \ - ADD_ATTRIB(EGL_DMA_BUF_PLANE ## plane ## _FD_EXT, \ - p_mapper->desc.objects[p_mapper->desc.layers[i].planes[j].object_index].fd); \ - ADD_ATTRIB(EGL_DMA_BUF_PLANE ## plane ## _OFFSET_EXT, \ - p_mapper->desc.layers[i].planes[j].offset); \ - ADD_ATTRIB(EGL_DMA_BUF_PLANE ## plane ## _PITCH_EXT, \ - p_mapper->desc.layers[i].planes[j].pitch); \ - if (dmabuf_interop->use_modifiers && drm_format_modifier != DRM_FORMAT_MOD_INVALID) { \ - ADD_ATTRIB(EGL_DMA_BUF_PLANE ## plane ## _MODIFIER_LO_EXT, drm_format_modifier & 0xfffffffful); \ - ADD_ATTRIB(EGL_DMA_BUF_PLANE ## plane ## _MODIFIER_HI_EXT, drm_format_modifier >> 32); \ - } \ - } while (0) +#define ADD_PLANE_ATTRIBS(nplane) \ + do { \ + const AVDRMPlaneDescriptor *plane = &p_mapper->desc.layers[i].planes[j]; \ + const AVDRMObjectDescriptor *object = \ + &p_mapper->desc.objects[plane->object_index]; \ + ADD_ATTRIB(EGL_DMA_BUF_PLANE ## nplane ## _FD_EXT, object->fd); \ + ADD_ATTRIB(EGL_DMA_BUF_PLANE ## nplane ## _OFFSET_EXT, plane->offset); \ + ADD_ATTRIB(EGL_DMA_BUF_PLANE ## nplane ## _PITCH_EXT, plane->pitch); \ + uint64_t drm_format_modifier = object->format_modifier; \ + if (dmabuf_interop->use_modifiers && \ + drm_format_modifier != DRM_FORMAT_MOD_INVALID) { \ + ADD_ATTRIB(EGL_DMA_BUF_PLANE ## nplane ## _MODIFIER_LO_EXT, \ + drm_format_modifier & 0xfffffffful); \ + ADD_ATTRIB(EGL_DMA_BUF_PLANE ## nplane ## _MODIFIER_HI_EXT, \ + drm_format_modifier >> 32); \ + } \ + } while (0) static bool vaapi_gl_map(struct ra_hwdec_mapper *mapper, struct dmabuf_interop *dmabuf_interop, @@ -162,6 +206,11 @@ static bool vaapi_gl_map(struct ra_hwdec_mapper *mapper, GL *gl = ra_gl_get(mapper->ra); + if (p->EGLImageTargetTexStorageEXT) { + if (!gl_create_textures(mapper)) + return false; + } + for (int i = 0, n = 0; i < p_mapper->desc.nb_layers; i++) { /* * As we must map surfaces as one texture per plane, we can only support @@ -186,6 +235,7 @@ static bool vaapi_gl_map(struct ra_hwdec_mapper *mapper, format[2] = DRM_FORMAT_R8; break; case DRM_FORMAT_P010: + case DRM_FORMAT_P210: #ifdef DRM_FORMAT_P030 /* Format added in a newer libdrm version than minimum */ case DRM_FORMAT_P030: #endif @@ -251,7 +301,11 @@ static bool vaapi_gl_map(struct ra_hwdec_mapper *mapper, } gl->BindTexture(GL_TEXTURE_2D, p->gl_textures[n]); - p->EGLImageTargetTexture2DOES(GL_TEXTURE_2D, p->images[n]); + if (p->EGLImageTargetTexStorageEXT) { + p->EGLImageTargetTexStorageEXT(GL_TEXTURE_2D, p->images[n], NULL); + } else { + p->EGLImageTargetTexture2DOES(GL_TEXTURE_2D, p->images[n]); + } mapper->tex[n] = p_mapper->tex[n]; } @@ -266,12 +320,18 @@ static void vaapi_gl_unmap(struct ra_hwdec_mapper *mapper) struct dmabuf_interop_priv *p_mapper = mapper->priv; struct vaapi_gl_mapper_priv *p = p_mapper->interop_mapper_priv; - if (p) { - for (int n = 0; n < 4; n++) { - if (p->images[n]) - p->DestroyImageKHR(eglGetCurrentDisplay(), p->images[n]); - p->images[n] = 0; - } + if (!p) + return; + + if (p->EGLImageTargetTexStorageEXT) { + // textures are immutable, can't reuse + gl_delete_textures(mapper); + } + + for (int n = 0; n < AV_DRM_MAX_PLANES; n++) { + if (p->images[n]) + p->DestroyImageKHR(eglGetCurrentDisplay(), p->images[n]); + p->images[n] = 0; } } @@ -291,16 +351,18 @@ bool dmabuf_interop_gl_init(const struct ra_hwdec *hw, return false; GL *gl = ra_gl_get(hw->ra_ctx->ra); + const char *imageext = gl->es ? "GL_OES_EGL_image" : "GL_EXT_EGL_image_storage"; if (!gl_check_extension(exts, "EGL_EXT_image_dma_buf_import") || !gl_check_extension(exts, "EGL_KHR_image_base") || - !gl_check_extension(gl->extensions, "GL_OES_EGL_image") || - !(gl->mpgl_caps & MPGL_CAP_TEX_RG)) + !gl_check_extension(gl->extensions, imageext) || + !(gl->mpgl_caps & MPGL_CAP_TEX_RG)) { return false; + } dmabuf_interop->use_modifiers = gl_check_extension(exts, "EGL_EXT_image_dma_buf_import_modifiers"); - MP_VERBOSE(hw, "using EGL dmabuf interop\n"); + MP_VERBOSE(hw, "Using EGL dmabuf interop via %s\n", imageext); dmabuf_interop->interop_init = vaapi_gl_mapper_init; dmabuf_interop->interop_uninit = vaapi_gl_mapper_uninit; diff --git a/video/out/hwdec/dmabuf_interop_pl.c b/video/out/hwdec/dmabuf_interop_pl.c index 0a8ec5b..1f036e3 100644 --- a/video/out/hwdec/dmabuf_interop_pl.c +++ b/video/out/hwdec/dmabuf_interop_pl.c @@ -110,7 +110,7 @@ static bool vaapi_pl_map(struct ra_hwdec_mapper *mapper, static void vaapi_pl_unmap(struct ra_hwdec_mapper *mapper) { - for (int n = 0; n < 4; n++) + for (int n = 0; n < MP_ARRAY_SIZE(mapper->tex); n++) ra_tex_free(mapper->ra, &mapper->tex[n]); } diff --git a/video/out/hwdec/hwdec_aimagereader.c b/video/out/hwdec/hwdec_aimagereader.c index 0dd5497..1aa92ee 100644 --- a/video/out/hwdec/hwdec_aimagereader.c +++ b/video/out/hwdec/hwdec_aimagereader.c @@ -75,7 +75,7 @@ struct priv { void (EGLAPIENTRY *EGLImageTargetTexture2DOES)(GLenum, GLeglImageOES); }; -const static struct { const char *symbol; int offset; } lib_functions[] = { +static const struct { const char *symbol; int offset; } lib_functions[] = { { "AImageReader_newWithUsage", offsetof(struct priv_owner, AImageReader_newWithUsage) }, { "AImageReader_getWindow", offsetof(struct priv_owner, AImageReader_getWindow) }, { "AImageReader_setImageListener", offsetof(struct priv_owner, AImageReader_setImageListener) }, @@ -138,6 +138,10 @@ static int init(struct ra_hwdec *hw) if (!gl_check_extension(exts, "EGL_ANDROID_image_native_buffer")) return -1; + JNIEnv *env = MP_JNI_GET_ENV(hw); + if (!env) + return -1; + if (!load_lib_functions(p, hw->log)) return -1; @@ -167,8 +171,6 @@ static int init(struct ra_hwdec *hw) } assert(window); - JNIEnv *env = MP_JNI_GET_ENV(hw); - assert(env); jobject surface = p->ANativeWindow_toSurface(env, window); p->surface = (*env)->NewGlobalRef(env, surface); (*env)->DeleteLocalRef(env, surface); @@ -192,10 +194,10 @@ static int init(struct ra_hwdec *hw) static void uninit(struct ra_hwdec *hw) { struct priv_owner *p = hw->priv; - JNIEnv *env = MP_JNI_GET_ENV(hw); - assert(env); if (p->surface) { + JNIEnv *env = MP_JNI_GET_ENV(hw); + assert(env); (*env)->DeleteGlobalRef(env, p->surface); p->surface = NULL; } diff --git a/video/out/hwdec/hwdec_cuda.c b/video/out/hwdec/hwdec_cuda.c index 68ad60d..57e4fb4 100644 --- a/video/out/hwdec/hwdec_cuda.c +++ b/video/out/hwdec/hwdec_cuda.c @@ -57,7 +57,7 @@ int check_cu(const struct ra_hwdec *hw, CUresult err, const char *func) #define CHECK_CU(x) check_cu(hw, (x), #x) -const static cuda_interop_init interop_inits[] = { +static const cuda_interop_init interop_inits[] = { #if HAVE_GL cuda_gl_init, #endif diff --git a/video/out/hwdec/hwdec_drmprime.c b/video/out/hwdec/hwdec_drmprime.c index f7c6250..bf60405 100644 --- a/video/out/hwdec/hwdec_drmprime.c +++ b/video/out/hwdec/hwdec_drmprime.c @@ -23,6 +23,7 @@ #include <libavutil/hwcontext.h> #include <libavutil/hwcontext_drm.h> +#include <libavutil/pixdesc.h> #include <xf86drm.h> #include "config.h" @@ -51,7 +52,7 @@ static void uninit(struct ra_hwdec *hw) av_buffer_unref(&p->hwctx.av_device_ref); } -const static dmabuf_interop_init interop_inits[] = { +static const dmabuf_interop_init interop_inits[] = { #if HAVE_DMABUF_INTEROP_GL dmabuf_interop_gl_init, #endif @@ -64,6 +65,18 @@ const static dmabuf_interop_init interop_inits[] = { NULL }; +/** + * Due to the fact that Raspberry Pi support only exists in forked ffmpegs and + * also requires custom pixel formats, we need some way to work with those formats + * without introducing any build time dependencies. We do this by looking up the + * pixel formats by name. As rpi is an important target platform for this hwdec + * we don't really have the luxury of ignoring these forks. + */ +static const char *forked_pix_fmt_names[] = { + "rpi4_8", + "rpi4_10", +}; + static int init(struct ra_hwdec *hw) { struct priv_owner *p = hw->priv; @@ -119,6 +132,18 @@ static int init(struct ra_hwdec *hw) MP_TARRAY_APPEND(p, p->formats, num_formats, IMGFMT_NV12); MP_TARRAY_APPEND(p, p->formats, num_formats, IMGFMT_420P); MP_TARRAY_APPEND(p, p->formats, num_formats, pixfmt2imgfmt(AV_PIX_FMT_NV16)); + MP_TARRAY_APPEND(p, p->formats, num_formats, IMGFMT_P010); +#ifdef AV_PIX_FMT_P210 + MP_TARRAY_APPEND(p, p->formats, num_formats, pixfmt2imgfmt(AV_PIX_FMT_P210)); +#endif + + for (int i = 0; i < MP_ARRAY_SIZE(forked_pix_fmt_names); i++) { + enum AVPixelFormat fmt = av_get_pix_fmt(forked_pix_fmt_names[i]); + if (fmt != AV_PIX_FMT_NONE) { + MP_TARRAY_APPEND(p, p->formats, num_formats, pixfmt2imgfmt(fmt)); + } + } + MP_TARRAY_APPEND(p, p->formats, num_formats, 0); // terminate it p->hwctx.hw_imgfmt = IMGFMT_DRMPRIME; diff --git a/video/out/hwdec/hwdec_vaapi.c b/video/out/hwdec/hwdec_vaapi.c index d8a4517..34b6e52 100644 --- a/video/out/hwdec/hwdec_vaapi.c +++ b/video/out/hwdec/hwdec_vaapi.c @@ -124,7 +124,7 @@ static void uninit(struct ra_hwdec *hw) va_destroy(p->ctx); } -const static dmabuf_interop_init interop_inits[] = { +static const dmabuf_interop_init interop_inits[] = { #if HAVE_DMABUF_INTEROP_GL dmabuf_interop_gl_init, #endif @@ -261,10 +261,10 @@ static int mapper_init(struct ra_hwdec_mapper *mapper) return 0; } -static void close_file_descriptors(VADRMPRIMESurfaceDescriptor desc) +static void close_file_descriptors(const VADRMPRIMESurfaceDescriptor *desc) { - for (int i = 0; i < desc.num_objects; i++) - close(desc.objects[i].fd); + for (int i = 0; i < desc->num_objects; i++) + close(desc->objects[i].fd); } static int mapper_map(struct ra_hwdec_mapper *mapper) @@ -285,7 +285,7 @@ static int mapper_map(struct ra_hwdec_mapper *mapper) if (!CHECK_VA_STATUS_LEVEL(mapper, "vaExportSurfaceHandle()", p_owner->probing_formats ? MSGL_DEBUG : MSGL_ERR)) { - close_file_descriptors(desc); + close_file_descriptors(&desc); goto err; } vaSyncSurface(display, va_surface_id(mapper->src)); diff --git a/video/out/hwdec/hwdec_vt.c b/video/out/hwdec/hwdec_vt.c index ab41d02..643ff90 100644 --- a/video/out/hwdec/hwdec_vt.c +++ b/video/out/hwdec/hwdec_vt.c @@ -36,7 +36,7 @@ static void uninit(struct ra_hwdec *hw) av_buffer_unref(&p->hwctx.av_device_ref); } -const static vt_interop_init interop_inits[] = { +static const vt_interop_init interop_inits[] = { #if HAVE_VIDEOTOOLBOX_GL || HAVE_IOS_GL vt_gl_init, #endif diff --git a/video/out/mac/common.swift b/video/out/mac/common.swift index aac7050..594a4b8 100644 --- a/video/out/mac/common.swift +++ b/video/out/mac/common.swift @@ -19,11 +19,13 @@ import Cocoa import IOKit.pwr_mgt class Common: NSObject { - var mpv: MPVHelper? + var option: OptionHelper + var input: InputHelper? var log: LogHelper + var vo: UnsafeMutablePointer<vo>? let queue: DispatchQueue = DispatchQueue(label: "io.mpv.queue") - var window: Window? + @objc var window: Window? var view: View? var titleBar: TitleBar? @@ -46,39 +48,26 @@ class Common: NSObject { didSet { if let window = window { window.title = title } } } - init(_ mpLog: OpaquePointer?) { - log = LogHelper(mpLog) + init(_ option: OptionHelper, _ log: LogHelper) { + self.option = option + self.log = log } func initMisc(_ vo: UnsafeMutablePointer<vo>) { - guard let mpv = mpv else { - log.sendError("Something went wrong, no MPVHelper was initialized") - exit(1) - } - startDisplayLink(vo) initLightSensor() addDisplayReconfigureObserver() addAppNotifications() - mpv.setMacOptionCallback(macOptsWakeupCallback, context: self) + option.setMacOptionCallback(macOptsWakeupCallback, context: self) } func initApp() { - guard let mpv = mpv else { - log.sendError("Something went wrong, no MPVHelper was initialized") - exit(1) - } - var policy: NSApplication.ActivationPolicy = .regular - switch mpv.macOpts.macos_app_activation_policy { - case 0: - policy = .regular - case 1: - policy = .accessory - case 2: - policy = .prohibited - default: - break + switch option.mac.macos_app_activation_policy { + case 0: policy = .regular + case 1: policy = .accessory + case 2: policy = .prohibited + default: break } NSApp.setActivationPolicy(policy) @@ -86,63 +75,67 @@ class Common: NSObject { } func initWindow(_ vo: UnsafeMutablePointer<vo>, _ previousActiveApp: NSRunningApplication?) { - let (mpv, targetScreen, wr) = getInitProperties(vo) + let (targetScreen, wr) = getInitProperties(vo) guard let view = self.view else { - log.sendError("Something went wrong, no View was initialized") + log.error("Something went wrong, no View was initialized") exit(1) } window = Window(contentRect: wr, screen: targetScreen, view: view, common: self) guard let window = self.window else { - log.sendError("Something went wrong, no Window was initialized") + log.error("Something went wrong, no Window was initialized") exit(1) } - 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.setOnTop(Bool(option.vo.ontop), Int(option.vo.ontop_level)) + window.setOnAllWorkspaces(Bool(option.vo.all_workspaces)) + window.keepAspect = Bool(option.vo.keepaspect_window) window.title = title - window.border = Bool(mpv.opts.border) + window.border = Bool(option.vo.border) titleBar = TitleBar(frame: wr, window: window, common: self) - let minimized = Bool(mpv.opts.window_minimized) + let maximized = Bool(option.vo.window_maximized) + let minimized = Bool(option.vo.window_minimized) window.isRestorable = false window.isReleasedWhenClosed = false - window.setMaximized(minimized ? false : Bool(mpv.opts.window_maximized)) + window.setMaximized((minimized || !maximized) ? window.isZoomed : maximized) window.setMinimized(minimized) window.makeMain() window.makeKey() + view.layer?.contentsScale = window.backingScaleFactor + if !minimized { window.orderFront(nil) } - NSApp.activate(ignoringOtherApps: mpv.opts.focus_on_open) + NSApp.activate(ignoringOtherApps: option.vo.focus_on >= 1) // workaround for macOS 10.15 to refocus the previous App - if (!mpv.opts.focus_on_open) { - previousActiveApp?.activate(options: .activateAllWindows) + if option.vo.focus_on == 0 { + previousActiveApp?.activate() } } func initView(_ vo: UnsafeMutablePointer<vo>, _ layer: CALayer) { - let (_, _, wr) = getInitProperties(vo) + let (_, wr) = getInitProperties(vo) view = View(frame: wr, common: self) guard let view = self.view else { - log.sendError("Something went wrong, no View was initialized") + log.error("Something went wrong, no View was initialized") exit(1) } view.layer = layer view.wantsLayer = true view.layerContentsPlacement = .scaleProportionallyToFit + layer.delegate = view } func initWindowState() { - if mpv?.opts.fullscreen ?? false { + if option.vo.fullscreen { DispatchQueue.main.async { self.window?.toggleFullScreen(nil) } @@ -179,7 +172,7 @@ class Common: NSObject { guard let screen = getTargetScreen(forFullscreen: false) ?? NSScreen.main, let link = self.link else { - log.sendWarning("Couldn't start DisplayLink, no MPVHelper, Screen or DisplayLink available") + log.warning("Couldn't start DisplayLink, no Screen or DisplayLink available") return } @@ -198,7 +191,7 @@ class Common: NSObject { func updateDisplaylink() { guard let screen = window?.screen, let link = self.link else { - log.sendWarning("Couldn't update DisplayLink, no Screen or DisplayLink available") + log.warning("Couldn't update DisplayLink, no Screen or DisplayLink available") return } @@ -221,17 +214,17 @@ class Common: NSObject { } if fabs(actualFps - nominalFps) > 0.1 { - log.sendVerbose("Falling back to nominal display refresh rate: \(nominalFps)") + log.verbose("Falling back to nominal display refresh rate: \(nominalFps)") return nominalFps } else { return actualFps } } } else { - log.sendWarning("No DisplayLink available") + log.warning("No DisplayLink available") } - log.sendWarning("Falling back to standard display refresh rate: 60Hz") + log.warning("Falling back to standard display refresh rate: 60Hz") return 60.0 } @@ -285,28 +278,28 @@ class Common: NSObject { } func lightSensorUpdate() { - log.sendWarning("lightSensorUpdate not implemented") + log.warning("lightSensorUpdate not implemented") } func initLightSensor() { let srv = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("AppleLMUController")) if srv == IO_OBJECT_NULL { - log.sendVerbose("Can't find an ambient light sensor") + log.verbose("Can't find an ambient light sensor") return } lightSensorIOPort = IONotificationPortCreate(kIOMasterPortDefault) IONotificationPortSetDispatchQueue(lightSensorIOPort, queue) var n = io_object_t() - IOServiceAddInterestNotification(lightSensorIOPort, srv, kIOGeneralInterest, lightSensorCallback, MPVHelper.bridge(obj: self), &n) + IOServiceAddInterestNotification(lightSensorIOPort, srv, kIOGeneralInterest, lightSensorCallback, TypeHelper.bridge(obj: self), &n) let kr = IOServiceOpen(srv, mach_task_self_, 0, &lightSensor) IOObjectRelease(srv) if kr != KERN_SUCCESS { - log.sendVerbose("Can't start ambient light sensor connection") + log.verbose("Can't start ambient light sensor connection") return } - lightSensorCallback(MPVHelper.bridge(obj: self), 0, 0, nil) + lightSensorCallback(TypeHelper.bridge(obj: self), 0, 0, nil) } func uninitLightSensor() { @@ -322,18 +315,18 @@ class Common: NSObject { let displayID = com.window?.screen?.displayID ?? display if displayID == display { - com.log.sendVerbose("Detected display mode change, updating screen refresh rate") + com.log.verbose("Detected display mode change, updating screen refresh rate") com.flagEvents(VO_EVENT_WIN_STATE) } } } func addDisplayReconfigureObserver() { - CGDisplayRegisterReconfigurationCallback(reconfigureCallback, MPVHelper.bridge(obj: self)) + CGDisplayRegisterReconfigurationCallback(reconfigureCallback, TypeHelper.bridge(obj: self)) } func removeDisplayReconfigureObserver() { - CGDisplayRemoveReconfigurationCallback(reconfigureCallback, MPVHelper.bridge(obj: self)) + CGDisplayRemoveReconfigurationCallback(reconfigureCallback, TypeHelper.bridge(obj: self)) } func addAppNotifications() { @@ -365,10 +358,8 @@ class Common: NSObject { } func setAppIcon() { - if let app = NSApp as? Application, - ProcessInfo.processInfo.environment["MPVBUNDLE"] != "true" - { - NSApp.applicationIconImage = app.getMPVIcon() + if ProcessInfo.processInfo.environment["MPVBUNDLE"] != "true" { + NSApp.applicationIconImage = AppHub.shared.getIcon() } } @@ -381,12 +372,12 @@ class Common: NSObject { } func updateICCProfile() { - log.sendWarning("updateICCProfile not implemented") + log.warning("updateICCProfile not implemented") } func getScreenBy(id screenID: Int) -> NSScreen? { if screenID >= NSScreen.screens.count { - log.sendInfo("Screen ID \(screenID) does not exist, falling back to current device") + log.info("Screen ID \(screenID) does not exist, falling back to current device") return nil } else if screenID < 0 { return nil @@ -404,14 +395,9 @@ class Common: NSObject { } func getTargetScreen(forFullscreen fs: Bool) -> NSScreen? { - guard let mpv = mpv else { - log.sendWarning("Unexpected nil value in getTargetScreen") - return nil - } - - let screenID = fs ? mpv.opts.fsscreen_id : mpv.opts.screen_id + let screenID = fs ? option.vo.fsscreen_id : option.vo.screen_id var name: String? - if let screenName = fs ? mpv.opts.fsscreen_name : mpv.opts.screen_name { + if let screenName = fs ? option.vo.fsscreen_name : option.vo.screen_name { name = String(cString: screenName) } return getScreenBy(id: Int(screenID)) ?? getScreenBy(name: name) @@ -426,7 +412,7 @@ class Common: NSObject { func getWindowGeometry(forScreen screen: NSScreen, videoOut vo: UnsafeMutablePointer<vo>) -> NSRect { let r = screen.convertRectToBacking(screen.frame) - let targetFrame = (mpv?.macOpts.macos_geometry_calculation ?? Int32(FRAME_VISIBLE)) == FRAME_VISIBLE + let targetFrame = option.mac.macos_geometry_calculation == FRAME_VISIBLE ? screen.visibleFrame : screen.frame let rv = screen.convertRectToBacking(targetFrame) @@ -453,19 +439,15 @@ class Common: NSObject { return screen.convertRectFromBacking(NSMakeRect(x, y, width, height)) } - func getInitProperties(_ vo: UnsafeMutablePointer<vo>) -> (MPVHelper, NSScreen, NSRect) { - guard let mpv = mpv else { - log.sendError("Something went wrong, no MPVHelper was initialized") - exit(1) - } + func getInitProperties(_ vo: UnsafeMutablePointer<vo>) -> (NSScreen, NSRect) { guard let targetScreen = getTargetScreen(forFullscreen: false) ?? NSScreen.main else { - log.sendError("Something went wrong, no Screen was found") + log.error("Something went wrong, no Screen was found") exit(1) } let wr = getWindowGeometry(forScreen: targetScreen, videoOut: vo) - return (mpv, targetScreen, wr) + return (targetScreen, wr) } // call before initApp, because on macOS +10.15 it changes the active App @@ -478,11 +460,11 @@ class Common: NSObject { events |= ev eventsLock.unlock() - guard let vout = mpv?.vo else { - log.sendWarning("vo nil in flagEvents") + guard let vo = vo else { + log.warning("vo nil in flagEvents") return } - vo_wakeup(vout) + vo_wakeup(vo) } func checkEvents() -> Int { @@ -510,47 +492,54 @@ class Common: NSObject { request: UInt32, data: UnsafeMutableRawPointer?) -> Int32 { - guard let mpv = mpv else { - log.sendWarning("Unexpected nil value in Control Callback") - return VO_FALSE - } - switch mp_voctrl(request) { case VOCTRL_CHECK_EVENTS: events.pointee |= Int32(checkEvents()) return VO_TRUE case VOCTRL_VO_OPTS_CHANGED: var opt: UnsafeMutableRawPointer? - while mpv.nextChangedOption(property: &opt) { + while option.nextChangedOption(property: &opt) { switch opt { - case MPVHelper.getPointer(&mpv.optsPtr.pointee.border): + case TypeHelper.toPointer(&option.voPtr.pointee.border): DispatchQueue.main.async { - self.window?.border = Bool(mpv.opts.border) + self.window?.border = Bool(self.option.vo.border) } - case MPVHelper.getPointer(&mpv.optsPtr.pointee.fullscreen): + case TypeHelper.toPointer(&option.voPtr.pointee.fullscreen): DispatchQueue.main.async { self.window?.toggleFullScreen(nil) } - case MPVHelper.getPointer(&mpv.optsPtr.pointee.ontop): fallthrough - case MPVHelper.getPointer(&mpv.optsPtr.pointee.ontop_level): + case TypeHelper.toPointer(&option.voPtr.pointee.ontop): fallthrough + case TypeHelper.toPointer(&option.voPtr.pointee.ontop_level): + DispatchQueue.main.async { + self.window?.setOnTop(Bool(self.option.vo.ontop), Int(self.option.vo.ontop_level)) + } + case TypeHelper.toPointer(&option.voPtr.pointee.all_workspaces): DispatchQueue.main.async { - self.window?.setOnTop(Bool(mpv.opts.ontop), Int(mpv.opts.ontop_level)) + self.window?.setOnAllWorkspaces(Bool(self.option.vo.all_workspaces)) } - case MPVHelper.getPointer(&mpv.optsPtr.pointee.all_workspaces): + case TypeHelper.toPointer(&option.voPtr.pointee.keepaspect_window): DispatchQueue.main.async { - self.window?.setOnAllWorkspaces(Bool(mpv.opts.all_workspaces)) + self.window?.keepAspect = Bool(self.option.vo.keepaspect_window) } - case MPVHelper.getPointer(&mpv.optsPtr.pointee.keepaspect_window): + case TypeHelper.toPointer(&option.voPtr.pointee.window_minimized): DispatchQueue.main.async { - self.window?.keepAspect = Bool(mpv.opts.keepaspect_window) + self.window?.setMinimized(Bool(self.option.vo.window_minimized)) } - case MPVHelper.getPointer(&mpv.optsPtr.pointee.window_minimized): + case TypeHelper.toPointer(&option.voPtr.pointee.window_maximized): DispatchQueue.main.async { - self.window?.setMinimized(Bool(mpv.opts.window_minimized)) + self.window?.setMaximized(Bool(self.option.vo.window_maximized)) } - case MPVHelper.getPointer(&mpv.optsPtr.pointee.window_maximized): + case TypeHelper.toPointer(&option.voPtr.pointee.cursor_passthrough): DispatchQueue.main.async { - self.window?.setMaximized(Bool(mpv.opts.window_maximized)) + self.window?.ignoresMouseEvents = self.option.vo.cursor_passthrough + } + case TypeHelper.toPointer(&option.voPtr.pointee.geometry): fallthrough + case TypeHelper.toPointer(&option.voPtr.pointee.autofit): fallthrough + case TypeHelper.toPointer(&option.voPtr.pointee.autofit_smaller): fallthrough + case TypeHelper.toPointer(&option.voPtr.pointee.autofit_larger): + DispatchQueue.main.async { + let (_, wr) = self.getInitProperties(vo) + self.window?.updateFrame(wr) } default: break @@ -561,6 +550,13 @@ class Common: NSObject { let fps = data!.assumingMemoryBound(to: CDouble.self) fps.pointee = currentFps() return VO_TRUE + case VOCTRL_GET_WINDOW_ID: + guard let window = window else { + return VO_NOTAVAIL + } + let wid = data!.assumingMemoryBound(to: Int64.self) + wid.pointee = unsafeBitCast(window, to: Int64.self) + return VO_TRUE case VOCTRL_GET_HIDPI_SCALE: let scaleFactor = data!.assumingMemoryBound(to: CDouble.self) let screen = getCurrentScreen() @@ -584,7 +580,7 @@ class Common: NSObject { case VOCTRL_GET_ICC_PROFILE: let screen = getCurrentScreen() guard var iccData = screen?.colorSpace?.iccProfileData else { - log.sendWarning("No Screen available to retrieve ICC profile") + log.warning("No Screen available to retrieve ICC profile") return VO_TRUE } @@ -605,10 +601,8 @@ class Common: NSObject { case VOCTRL_GET_UNFS_WINDOW_SIZE: let sizeData = data!.assumingMemoryBound(to: Int32.self) let size = UnsafeMutableBufferPointer(start: sizeData, count: 2) - var rect = window?.unfsContentFrame ?? NSRect(x: 0, y: 0, width: 1280, height: 720) - if let screen = window?.currentScreen, !Bool(mpv.opts.hidpi_window_scale) { - rect = screen.convertRectToBacking(rect) - } + let rect = (Bool(option.vo.hidpi_window_scale) ? window?.unfsContentFrame + : window?.unfsContentFramePixel) ?? NSRect(x: 0, y: 0, width: 1280, height: 720) size[0] = Int32(rect.size.width) size[1] = Int32(rect.size.height) @@ -618,7 +612,7 @@ class Common: NSObject { let size = UnsafeBufferPointer(start: sizeData, count: 2) var rect = NSMakeRect(0, 0, CGFloat(size[0]), CGFloat(size[1])) DispatchQueue.main.async { - if let screen = self.window?.currentScreen, !Bool(self.mpv?.opts.hidpi_window_scale ?? true) { + if let screen = self.window?.currentScreen, !Bool(self.option.vo.hidpi_window_scale) { rect = screen.convertRectFromBacking(rect) } self.window?.updateSize(rect.size) @@ -630,13 +624,13 @@ class Common: NSObject { var count: Int32 = 0 let displayName = getCurrentScreen()?.localizedName ?? "Unknown" - SWIFT_TARRAY_STRING_APPEND(nil, &array, &count, ta_xstrdup(nil, displayName)) - SWIFT_TARRAY_STRING_APPEND(nil, &array, &count, nil) + app_bridge_tarray_append(nil, &array, &count, ta_xstrdup(nil, displayName)) + app_bridge_tarray_append(nil, &array, &count, nil) dnames.pointee = array return VO_TRUE case VOCTRL_GET_DISPLAY_RES: guard let screen = getCurrentScreen() else { - log.sendWarning("No Screen available to retrieve frame") + log.warning("No Screen available to retrieve frame") return VO_NOTAVAIL } let sizeData = data!.assumingMemoryBound(to: Int32.self) @@ -650,10 +644,9 @@ class Common: NSObject { focus.pointee = NSApp.isActive return VO_TRUE case VOCTRL_UPDATE_WINDOW_TITLE: - let titleData = data!.assumingMemoryBound(to: Int8.self) + let title = String(cString: data!.assumingMemoryBound(to: CChar.self)) DispatchQueue.main.async { - let title = NSString(utf8String: titleData) as String? - self.title = title ?? "Unknown Title" + self.title = title } return VO_TRUE default: @@ -669,20 +662,15 @@ class Common: NSObject { } func macOptsUpdate() { - guard let mpv = mpv else { - log.sendWarning("Unexpected nil value in mac opts update") - return - } - var opt: UnsafeMutableRawPointer? - while mpv.nextChangedMacOption(property: &opt) { + while option.nextChangedMacOption(property: &opt) { switch opt { - case MPVHelper.getPointer(&mpv.macOptsPtr.pointee.macos_title_bar_appearance): - titleBar?.set(appearance: Int(mpv.macOpts.macos_title_bar_appearance)) - case MPVHelper.getPointer(&mpv.macOptsPtr.pointee.macos_title_bar_material): - titleBar?.set(material: Int(mpv.macOpts.macos_title_bar_material)) - case MPVHelper.getPointer(&mpv.macOptsPtr.pointee.macos_title_bar_color): - titleBar?.set(color: mpv.macOpts.macos_title_bar_color) + case TypeHelper.toPointer(&option.macPtr.pointee.macos_title_bar_appearance): + titleBar?.set(appearance: Int(option.mac.macos_title_bar_appearance)) + case TypeHelper.toPointer(&option.macPtr.pointee.macos_title_bar_material): + titleBar?.set(material: Int(option.mac.macos_title_bar_material)) + case TypeHelper.toPointer(&option.macPtr.pointee.macos_title_bar_color): + titleBar?.set(color: option.mac.macos_title_bar_color) default: break } diff --git a/video/out/mac/gl_layer.swift b/video/out/mac/gl_layer.swift index dd96af7..38320bc 100644 --- a/video/out/mac/gl_layer.swift +++ b/video/out/mac/gl_layer.swift @@ -82,8 +82,6 @@ class GLLayer: CAOpenGLLayer { enum Draw: Int { case normal = 1, atomic, atomicEnd } var draw: Draw = .normal - let queue: DispatchQueue = DispatchQueue(label: "io.mpv.queue.draw") - var needsICCUpdate: Bool = false { didSet { if needsICCUpdate == true { @@ -199,6 +197,14 @@ class GLLayer: CAOpenGLLayer { } } + func lockCglContext() { + CGLLockContext(cglContext) + } + + func unlockCglContext() { + CGLUnlockContext(cglContext) + } + override func copyCGLPixelFormat(forDisplayMask mask: UInt32) -> CGLPixelFormatObj { return cglPixelFormat } @@ -219,17 +225,19 @@ class GLLayer: CAOpenGLLayer { super.display() CATransaction.flush() if isUpdate && needsFlip { + lockCglContext() CGLSetCurrentContext(cglContext) if libmpv.isRenderUpdateFrame() { libmpv.drawRender(NSZeroSize, bufferDepth, cglContext, skip: true) } + unlockCglContext() } displayLock.unlock() } func update(force: Bool = false) { if force { forceDraw = true } - queue.async { + DispatchQueue.main.async { if self.forceDraw || !self.inLiveResize { self.needsFlip = true self.display() @@ -241,7 +249,7 @@ class GLLayer: CAOpenGLLayer { var pix: CGLPixelFormatObj? var depth: GLint = 8 var err: CGLError = CGLError(rawValue: 0) - let swRender = ccb.libmpv.macOpts.cocoa_cb_sw_renderer + let swRender = ccb.option.mac.cocoa_cb_sw_renderer if swRender != 1 { (pix, depth, err) = GLLayer.findPixelFormat(ccb) @@ -252,7 +260,7 @@ class GLLayer: CAOpenGLLayer { } guard let pixelFormat = pix, err == kCGLNoError else { - ccb.log.sendError("Couldn't create any CGL pixel format") + ccb.log.error("Couldn't create any CGL pixel format") exit(1) } @@ -269,12 +277,12 @@ class GLLayer: CAOpenGLLayer { glBase.insert(CGLPixelFormatAttribute(ver.rawValue), at: 1) var glFormat = [glBase] - if ccb.libmpv.macOpts.cocoa_cb_10bit_context { + if ccb.option.mac.cocoa_cb_10bit_context { glFormat += [glFormat10Bit] } glFormat += glFormatOptional - if !ccb.libmpv.macOpts.macos_force_dedicated_gpu { + if !ccb.option.mac.macos_force_dedicated_gpu { glFormat += [glFormatAutoGPU] } @@ -289,7 +297,7 @@ class GLLayer: CAOpenGLLayer { return attributeLookUp[value.rawValue] ?? String(value.rawValue) }) - ccb.log.sendVerbose("Created CGL pixel format with attributes: " + + ccb.log.verbose("Created CGL pixel format with attributes: " + "\(attArray.joined(separator: ", "))") return (pix, glFormat.contains(glFormat10Bit) ? 16 : 8, err) } @@ -297,11 +305,11 @@ class GLLayer: CAOpenGLLayer { } let errS = String(cString: CGLErrorString(err)) - ccb.log.sendWarning("Couldn't create a " + + ccb.log.warning("Couldn't create a " + "\(software ? "software" : "hardware accelerated") " + "CGL pixel format: \(errS) (\(err.rawValue))") - if software == false && ccb.libmpv.macOpts.cocoa_cb_sw_renderer == -1 { - ccb.log.sendWarning("Falling back to software renderer") + if software == false && ccb.option.mac.cocoa_cb_sw_renderer == -1 { + ccb.log.warning("Falling back to software renderer") } return (pix, 8, err) @@ -313,7 +321,7 @@ class GLLayer: CAOpenGLLayer { guard let cglContext = context, error == kCGLNoError else { let errS = String(cString: CGLErrorString(error)) - ccb.log.sendError("Couldn't create a CGLContext: " + errS) + ccb.log.error("Couldn't create a CGLContext: " + errS) exit(1) } diff --git a/video/out/mac/metal_layer.swift b/video/out/mac/metal_layer.swift index 7cea87c..7fc419a 100644 --- a/video/out/mac/metal_layer.swift +++ b/video/out/mac/metal_layer.swift @@ -16,10 +16,22 @@ */ import Cocoa +import QuartzCore class MetalLayer: CAMetalLayer { unowned var common: MacCommon + // workaround for a MoltenVK workaround that sets the drawableSize to 1x1 to forcefully complete + // the presentation, this causes flicker and the drawableSize possibly staying at 1x1 + override var drawableSize: CGSize { + get { return super.drawableSize } + set { + if Int(newValue.width) > 1 && Int(newValue.height) > 1 { + super.drawableSize = newValue + } + } + } + init(common com: MacCommon) { common = com super.init() diff --git a/video/out/mac/title_bar.swift b/video/out/mac/title_bar.swift index 764c1ff..b274100 100644 --- a/video/out/mac/title_bar.swift +++ b/video/out/mac/title_bar.swift @@ -19,7 +19,7 @@ import Cocoa class TitleBar: NSVisualEffectView { unowned var common: Common - var mpv: MPVHelper? { get { return common.mpv } } + var option: OptionHelper { get { return common.option } } var systemBar: NSView? { get { return common.window?.standardWindowButton(.closeButton)?.superview } @@ -64,9 +64,9 @@ class TitleBar: NSVisualEffectView { window.contentView?.addSubview(self, positioned: .above, relativeTo: nil) window.titlebarAppearsTransparent = true window.styleMask.insert(.fullSizeContentView) - set(appearance: Int(mpv?.macOpts.macos_title_bar_appearance ?? 0)) - set(material: Int(mpv?.macOpts.macos_title_bar_material ?? 0)) - set(color: mpv?.macOpts.macos_title_bar_color ?? "#00000000") + set(appearance: Int(option.mac.macos_title_bar_appearance)) + set(material: Int(option.mac.macos_title_bar_material)) + set(color: option.mac.macos_title_bar_color) } required init?(coder: NSCoder) { @@ -195,10 +195,6 @@ class TitleBar: NSVisualEffectView { default: return nil } - - - let style = UserDefaults.standard.string(forKey: "AppleInterfaceStyle") - return appearanceFrom(string: style == nil ? "aqua" : "vibrantDark") } func materialFrom(string: String) -> NSVisualEffectView.Material { @@ -221,9 +217,7 @@ class TitleBar: NSVisualEffectView { case "15", "light": return .light case "16", "mediumLight": return .mediumLight case "17", "ultraDark": return .ultraDark - default: break + default: return .titlebar } - - return .titlebar } } diff --git a/video/out/mac/view.swift b/video/out/mac/view.swift index c4776c3..047a523 100644 --- a/video/out/mac/view.swift +++ b/video/out/mac/view.swift @@ -17,9 +17,9 @@ import Cocoa -class View: NSView { +class View: NSView, CALayerDelegate { unowned var common: Common - var mpv: MPVHelper? { get { return common.mpv } } + var input: InputHelper? { get { return common.input } } var tracker: NSTrackingArea? var hasMouseDown: Bool = false @@ -52,7 +52,7 @@ class View: NSView { addTrackingArea(tracker!) if containsMouseLocation() { - cocoa_put_key_with_modifiers(SWIFT_KEY_MOUSE_LEAVE, 0) + input?.put(key: SWIFT_KEY_MOUSE_LEAVE) } } @@ -77,30 +77,24 @@ class View: NSView { override func performDragOperation(_ sender: NSDraggingInfo) -> Bool { let pb = sender.draggingPasteboard guard let types = pb.types else { return false } + var files: [String] = [] if types.contains(.fileURL) || types.contains(.URL) { - if let urls = pb.readObjects(forClasses: [NSURL.self]) as? [URL] { - let files = urls.map { $0.absoluteString } - EventsResponder.sharedInstance().handleFilesArray(files) - return true - } + guard let urls = pb.readObjects(forClasses: [NSURL.self]) as? [URL] else { return false } + files = urls.map { $0.absoluteString } } else if types.contains(.string) { guard let str = pb.string(forType: .string) else { return false } - var filesArray: [String] = [] - - for val in str.components(separatedBy: "\n") { - let url = val.trimmingCharacters(in: .whitespacesAndNewlines) + files = str.components(separatedBy: "\n").compactMap { + let url = $0.trimmingCharacters(in: .whitespacesAndNewlines) let path = (url as NSString).expandingTildeInPath - if isURL(url) { - filesArray.append(url) - } else if path.starts(with: "/") { - filesArray.append(path) - } + if isURL(url) { return url } + if path.starts(with: "/") { return path } + return nil } - EventsResponder.sharedInstance().handleFilesArray(filesArray) - return true } - return false + if files.isEmpty { return false } + input?.open(files: files) + return true } override func acceptsFirstMouse(for event: NSEvent?) -> Bool { @@ -116,94 +110,66 @@ class View: NSView { } override func mouseEntered(with event: NSEvent) { - if mpv?.mouseEnabled() ?? true { - cocoa_put_key_with_modifiers(SWIFT_KEY_MOUSE_ENTER, 0) + if input?.mouseEnabled() ?? true { + input?.put(key: SWIFT_KEY_MOUSE_ENTER) } common.updateCursorVisibility() } override func mouseExited(with event: NSEvent) { - if mpv?.mouseEnabled() ?? true { - cocoa_put_key_with_modifiers(SWIFT_KEY_MOUSE_LEAVE, 0) + if input?.mouseEnabled() ?? true { + input?.put(key: SWIFT_KEY_MOUSE_LEAVE) } common.titleBar?.hide() common.setCursorVisibility(true) } override func mouseMoved(with event: NSEvent) { - if mpv?.mouseEnabled() ?? true { - signalMouseMovement(event) - } + signalMouseMovement(event) common.titleBar?.show() } override func mouseDragged(with event: NSEvent) { - if mpv?.mouseEnabled() ?? true { - signalMouseMovement(event) - } + signalMouseMovement(event) } override func mouseDown(with event: NSEvent) { - if mpv?.mouseEnabled() ?? true { - signalMouseDown(event) - } + hasMouseDown = event.clickCount <= 1 + input?.processMouse(event: event) } override func mouseUp(with event: NSEvent) { - if mpv?.mouseEnabled() ?? true { - signalMouseUp(event) - } + hasMouseDown = false common.window?.isMoving = false + input?.processMouse(event: event) } override func rightMouseDown(with event: NSEvent) { - if mpv?.mouseEnabled() ?? true { - signalMouseDown(event) - } + hasMouseDown = event.clickCount <= 1 + input?.processMouse(event: event) } override func rightMouseUp(with event: NSEvent) { - if mpv?.mouseEnabled() ?? true { - signalMouseUp(event) - } + hasMouseDown = false + input?.processMouse(event: event) } override func otherMouseDown(with event: NSEvent) { - if mpv?.mouseEnabled() ?? true { - signalMouseDown(event) - } + hasMouseDown = event.clickCount <= 1 + input?.processMouse(event: event) } override func otherMouseUp(with event: NSEvent) { - if mpv?.mouseEnabled() ?? true { - signalMouseUp(event) - } + hasMouseDown = false + input?.processMouse(event: event) } override func magnify(with event: NSEvent) { - event.phase == .ended ? - common.windowDidEndLiveResize() : common.windowWillStartLiveResize() - + common.window?.isAnimating = event.phase != .ended + event.phase == .ended ? common.windowDidEndLiveResize() : common.windowWillStartLiveResize() common.window?.addWindowScale(Double(event.magnification)) } - func signalMouseDown(_ event: NSEvent) { - signalMouseEvent(event, MP_KEY_STATE_DOWN) - if event.clickCount > 1 { - signalMouseEvent(event, MP_KEY_STATE_UP) - } - } - - func signalMouseUp(_ event: NSEvent) { - signalMouseEvent(event, MP_KEY_STATE_UP) - } - - func signalMouseEvent(_ event: NSEvent, _ state: UInt32) { - hasMouseDown = state == MP_KEY_STATE_DOWN - let mpkey = getMpvButton(event) - cocoa_put_key_with_modifiers((mpkey | Int32(state)), Int32(event.modifierFlags.rawValue)) - } - func signalMouseMovement(_ event: NSEvent) { var point = convert(event.locationInWindow, from: nil) point = convertToBacking(point) @@ -211,46 +177,12 @@ class View: NSView { common.window?.updateMovableBackground(point) if !(common.window?.isMoving ?? false) { - mpv?.setMousePosition(point) - } - } - - func preciseScroll(_ event: NSEvent) { - var delta: Double - var cmd: Int32 - - if abs(event.deltaY) >= abs(event.deltaX) { - delta = Double(event.deltaY) * 0.1 - cmd = delta > 0 ? SWIFT_WHEEL_UP : SWIFT_WHEEL_DOWN - } else { - delta = Double(event.deltaX) * 0.1 - cmd = delta > 0 ? SWIFT_WHEEL_LEFT : SWIFT_WHEEL_RIGHT + input?.setMouse(position: point) } - - mpv?.putAxis(cmd, delta: abs(delta)) } override func scrollWheel(with event: NSEvent) { - if !(mpv?.mouseEnabled() ?? true) { - return - } - - if event.hasPreciseScrollingDeltas { - preciseScroll(event) - } else { - let modifiers = event.modifierFlags - let deltaX = modifiers.contains(.shift) ? event.scrollingDeltaY : event.scrollingDeltaX - let deltaY = modifiers.contains(.shift) ? event.scrollingDeltaX : event.scrollingDeltaY - var mpkey: Int32 - - if abs(deltaY) >= abs(deltaX) { - mpkey = deltaY > 0 ? SWIFT_WHEEL_UP : SWIFT_WHEEL_DOWN - } else { - mpkey = deltaX > 0 ? SWIFT_WHEEL_LEFT : SWIFT_WHEEL_RIGHT - } - - cocoa_put_key_with_modifiers(mpkey, Int32(modifiers.rawValue)) - } + input?.processWheel(event: event) } func containsMouseLocation() -> Bool { @@ -282,16 +214,4 @@ class View: NSView { guard let window = common.window else { return false } return !hasMouseDown && containsMouseLocation() && window.isKeyWindow } - - func getMpvButton(_ event: NSEvent) -> Int32 { - let buttonNumber = event.buttonNumber - switch (buttonNumber) { - case 0: return SWIFT_MBTN_LEFT - case 1: return SWIFT_MBTN_RIGHT - case 2: return SWIFT_MBTN_MID - case 3: return SWIFT_MBTN_BACK - case 4: return SWIFT_MBTN_FORWARD - default: return SWIFT_MBTN9 + Int32(buttonNumber - 5) - } - } } diff --git a/video/out/mac/window.swift b/video/out/mac/window.swift index 7b1a858..c5a711e 100644 --- a/video/out/mac/window.swift +++ b/video/out/mac/window.swift @@ -19,14 +19,15 @@ import Cocoa class Window: NSWindow, NSWindowDelegate { weak var common: Common! = nil - var mpv: MPVHelper? { get { return common.mpv } } + var option: OptionHelper { get { return common.option } } + var input: InputHelper? { get { return common.input } } var targetScreen: NSScreen? var previousScreen: NSScreen? var currentScreen: NSScreen? var unfScreen: NSScreen? - var unfsContentFrame: NSRect? + var unfsContentFrame: NSRect = NSRect(x: 0, y: 0, width: 160, height: 90) var isInFullscreen: Bool = false var isMoving: Bool = false var previousStyleMask: NSWindow.StyleMask = [.titled, .closable, .miniaturizable, .resizable] @@ -34,8 +35,8 @@ class Window: NSWindow, NSWindowDelegate { var isAnimating: Bool = false let animationLock: NSCondition = NSCondition() - var unfsContentFramePixel: NSRect { get { return convertToBacking(unfsContentFrame ?? NSRect(x: 0, y: 0, width: 160, height: 90)) } } - var framePixel: NSRect { get { return convertToBacking(frame) } } + var unfsContentFramePixel: NSRect { get { return convertToBacking(unfsContentFrame) } } + @objc var framePixel: NSRect { get { return convertToBacking(frame) } } var keepAspect: Bool = true { didSet { @@ -44,7 +45,7 @@ class Window: NSWindow, NSWindowDelegate { } if keepAspect { - contentAspectRatio = unfsContentFrame?.size ?? contentAspectRatio + contentAspectRatio = unfsContentFrame.size } else { resizeIncrements = NSSize(width: 1.0, height: 1.0) } @@ -91,7 +92,9 @@ class Window: NSWindow, NSWindowDelegate { title = com.title minSize = NSMakeSize(160, 90) collectionBehavior = .fullScreenPrimary + ignoresMouseEvents = option.vo.cursor_passthrough delegate = self + unfsContentFrame = contentRect if let cView = contentView { cView.addSubview(view) @@ -103,13 +106,11 @@ class Window: NSWindow, NSWindowDelegate { currentScreen = screen unfScreen = screen - if let app = NSApp as? Application { - app.menuBar.register(#selector(setHalfWindowSize), for: MPM_H_SIZE) - app.menuBar.register(#selector(setNormalWindowSize), for: MPM_N_SIZE) - app.menuBar.register(#selector(setDoubleWindowSize), for: MPM_D_SIZE) - app.menuBar.register(#selector(performMiniaturize(_:)), for: MPM_MINIMIZE) - app.menuBar.register(#selector(performZoom(_:)), for: MPM_ZOOM) - } + AppHub.shared.menu?.register(#selector(setHalfWindowSize), key: .itemHalfSize) + AppHub.shared.menu?.register(#selector(setNormalWindowSize), key: .itemNormalSize) + AppHub.shared.menu?.register(#selector(setDoubleWindowSize), key: .itemDoubleSize) + AppHub.shared.menu?.register(#selector(performMiniaturize(_:)), key: .itemMinimize) + AppHub.shared.menu?.register(#selector(performZoom(_:)), key: .itemZoom) } override func toggleFullScreen(_ sender: Any?) { @@ -141,7 +142,7 @@ class Window: NSWindow, NSWindowDelegate { setFrame(frame, display: true) } - if Bool(mpv?.opts.native_fs ?? true) { + if Bool(option.vo.native_fs) { super.toggleFullScreen(sender) } else { if !isInFullscreen { @@ -192,7 +193,7 @@ class Window: NSWindow, NSWindowDelegate { func windowDidEnterFullScreen(_ notification: Notification) { isInFullscreen = true - mpv?.setOption(fullscreen: isInFullscreen) + option.setOption(fullscreen: isInFullscreen) common.updateCursorVisibility() endAnimation(frame) common.titleBar?.show() @@ -201,7 +202,7 @@ class Window: NSWindow, NSWindowDelegate { func windowDidExitFullScreen(_ notification: Notification) { guard let tScreen = targetScreen else { return } isInFullscreen = false - mpv?.setOption(fullscreen: isInFullscreen) + option.setOption(fullscreen: isInFullscreen) endAnimation(calculateWindowPosition(for: tScreen, withoutBounds: targetScreen == screen)) common.view?.layerContentsPlacement = .scaleProportionallyToFit } @@ -249,7 +250,7 @@ class Window: NSWindow, NSWindowDelegate { setFrame(targetFrame, display: true) endAnimation() isInFullscreen = true - mpv?.setOption(fullscreen: isInFullscreen) + option.setOption(fullscreen: isInFullscreen) common.windowSetToFullScreen() } @@ -268,7 +269,7 @@ class Window: NSWindow, NSWindowDelegate { setFrame(newFrame, display: true) endAnimation() isInFullscreen = false - mpv?.setOption(fullscreen: isInFullscreen) + option.setOption(fullscreen: isInFullscreen) common.windowSetToWindow() } @@ -281,7 +282,7 @@ class Window: NSWindow, NSWindowDelegate { } func getFsAnimationDuration(_ def: Double) -> Double { - let duration = mpv?.macOpts.macos_fs_animation_duration ?? -1 + let duration = option.mac.macos_fs_animation_duration if duration < 0 { return def } else { @@ -334,7 +335,7 @@ class Window: NSWindow, NSWindowDelegate { func updateMovableBackground(_ pos: NSPoint) { if !isInFullscreen { - isMovableByWindowBackground = mpv?.canBeDraggedAt(pos) ?? true + isMovableByWindowBackground = input?.draggable(at: pos) ?? true } else { isMovableByWindowBackground = false } @@ -342,35 +343,31 @@ class Window: NSWindow, NSWindowDelegate { func updateFrame(_ rect: NSRect) { if rect != frame { - let cRect = frameRect(forContentRect: rect) unfsContentFrame = rect - setFrame(cRect, display: true) - common.windowDidUpdateFrame() + if !isInFullscreen { + let cRect = frameRect(forContentRect: rect) + setFrame(cRect, display: true) + common.windowDidUpdateFrame() + } } } func updateSize(_ size: NSSize) { if let currentSize = contentView?.frame.size, size != currentSize { let newContentFrame = centeredContentSize(for: frame, size: size) - if !isInFullscreen { - updateFrame(newContentFrame) - } else { - unfsContentFrame = newContentFrame - } + updateFrame(newContentFrame) } } override func setFrame(_ frameRect: NSRect, display flag: Bool) { if frameRect.width < minSize.width || frameRect.height < minSize.height { - common.log.sendVerbose("tried to set too small window size: \(frameRect.size)") + common.log.verbose("tried to set too small window size: \(frameRect.size)") return } super.setFrame(frameRect, display: flag) - if let size = unfsContentFrame?.size, keepAspect { - contentAspectRatio = size - } + if keepAspect { contentAspectRatio = unfsContentFrame.size } } func centeredContentSize(for rect: NSRect, size sz: NSSize) -> NSRect { @@ -391,10 +388,9 @@ class Window: NSWindow, NSWindowDelegate { } func calculateWindowPosition(for tScreen: NSScreen, withoutBounds: Bool) -> NSRect { - guard let contentFrame = unfsContentFrame, let screen = unfScreen else { - return frame - } - var newFrame = frameRect(forContentRect: contentFrame) + guard let screen = unfScreen else { return frame } + + var newFrame = frameRect(forContentRect: unfsContentFrame) let targetFrame = tScreen.frame let targetVisibleFrame = tScreen.visibleFrame let unfsScreenFrame = screen.frame @@ -504,12 +500,12 @@ class Window: NSWindow, NSWindowDelegate { @objc func setDoubleWindowSize() { setWindowScale(2.0) } func setWindowScale(_ scale: Double) { - mpv?.command("set window-scale \(scale)") + input?.command("set window-scale \(scale)") } func addWindowScale(_ scale: Double) { if !isInFullscreen { - mpv?.command("add window-scale \(scale)") + input?.command("add current-window-scale \(scale)") } } @@ -542,7 +538,7 @@ class Window: NSWindow, NSWindowDelegate { func windowDidEndLiveResize(_ notification: Notification) { common.windowDidEndLiveResize() - mpv?.setOption(maximized: isZoomed) + option.setOption(maximized: isZoomed) if let contentViewFrame = contentView?.frame, !isAnimating && !isInFullscreen @@ -552,20 +548,23 @@ class Window: NSWindow, NSWindowDelegate { } func windowDidResize(_ notification: Notification) { + if let contentViewFrame = contentView?.frame, !isAnimating && !isInFullscreen && !inLiveResize { + unfsContentFrame = convertToScreen(contentViewFrame) + } common.windowDidResize() } func windowShouldClose(_ sender: NSWindow) -> Bool { - cocoa_put_key(MP_KEY_CLOSE_WIN) + input?.put(key: MP_KEY_CLOSE_WIN) return false } func windowDidMiniaturize(_ notification: Notification) { - mpv?.setOption(minimized: true) + option.setOption(minimized: true) } func windowDidDeminiaturize(_ notification: Notification) { - mpv?.setOption(minimized: false) + option.setOption(minimized: false) } func windowDidResignKey(_ notification: Notification) { @@ -588,6 +587,6 @@ class Window: NSWindow, NSWindowDelegate { } func windowDidMove(_ notification: Notification) { - mpv?.setOption(maximized: isZoomed) + option.setOption(maximized: isZoomed) } } diff --git a/video/out/mac_common.swift b/video/out/mac_common.swift index 349712b..f29815d 100644 --- a/video/out/mac_common.swift +++ b/video/out/mac_common.swift @@ -20,16 +20,18 @@ import Cocoa class MacCommon: Common { @objc var layer: MetalLayer? + var presentation: Presentation? var timer: PreciseTimer? var swapTime: UInt64 = 0 let swapLock: NSCondition = NSCondition() - var needsICCUpdate: Bool = false - @objc init(_ vo: UnsafeMutablePointer<vo>) { - let newlog = mp_log_new(vo, vo.pointee.log, "mac") - super.init(newlog) - mpv = MPVHelper(vo, log) + let log = LogHelper(mp_log_new(vo, vo.pointee.log, "mac")) + let option = OptionHelper(vo, vo.pointee.global) + super.init(option, log) + self.vo = vo + input = InputHelper(vo.pointee.input_ctx, option) + presentation = Presentation(common: self) timer = PreciseTimer(common: self) DispatchQueue.main.sync { @@ -39,16 +41,16 @@ class MacCommon: Common { } @objc func config(_ vo: UnsafeMutablePointer<vo>) -> Bool { - mpv?.vo = vo + self.vo = vo DispatchQueue.main.sync { let previousActiveApp = getActiveApp() initApp() - let (_, _, wr) = getInitProperties(vo) + let (_, wr) = getInitProperties(vo) guard let layer = self.layer else { - log.sendError("Something went wrong, no MetalLayer was initialized") + log.error("Something went wrong, no MetalLayer was initialized") exit(1) } @@ -58,12 +60,18 @@ class MacCommon: Common { initWindowState() } - if !NSEqualSizes(window?.unfsContentFramePixel.size ?? NSZeroSize, wr.size) { + if !NSEqualSizes(window?.unfsContentFramePixel.size ?? NSZeroSize, wr.size) && + option.vo.auto_window_resize + { window?.updateSize(wr.size) } + if option.vo.focus_on == 2 { + NSApp.activate(ignoringOtherApps: true) + } + windowDidResize() - needsICCUpdate = true + updateICCProfile() } return true @@ -83,7 +91,7 @@ class MacCommon: Common { } @objc func swapBuffer() { - if mpv?.macOpts.macos_render_timer ?? Int32(RENDER_TIMER_CALLBACK) != RENDER_TIMER_SYSTEM { + if option.mac.macos_render_timer > RENDER_TIMER_SYSTEM { swapLock.lock() while(swapTime < 1) { swapLock.wait() @@ -91,18 +99,16 @@ class MacCommon: Common { swapTime = 0 swapLock.unlock() } - - if needsICCUpdate { - needsICCUpdate = false - updateICCProfile() - } } - func updateRenderSize(_ size: NSSize) { - mpv?.vo.pointee.dwidth = Int32(size.width) - mpv?.vo.pointee.dheight = Int32(size.height) - flagEvents(VO_EVENT_RESIZE | VO_EVENT_EXPOSE) - } + @objc func fillVsync(info: UnsafeMutablePointer<vo_vsync_info>) { + if option.mac.macos_render_timer != RENDER_TIMER_PRESENTATION_FEEDBACK { return } + + let next = presentation?.next() + info.pointee.vsync_duration = next?.duration ?? -1 + info.pointee.skipped_vsyncs = next?.skipped ?? -1 + info.pointee.last_queue_display_time = next?.time ?? -1 + } override func displayLinkCallback(_ displayLink: CVDisplayLink, _ inNow: UnsafePointer<CVTimeStamp>, @@ -110,7 +116,6 @@ class MacCommon: Common { _ flagsIn: CVOptionFlags, _ flagsOut: UnsafeMutablePointer<CVOptionFlags>) -> CVReturn { - let frameTimer = mpv?.macOpts.macos_render_timer ?? Int32(RENDER_TIMER_CALLBACK) let signalSwap = { self.swapLock.lock() self.swapTime += 1 @@ -118,13 +123,18 @@ class MacCommon: Common { self.swapLock.unlock() } - if frameTimer != RENDER_TIMER_SYSTEM { - if let timer = self.timer, frameTimer == RENDER_TIMER_PRECISE { + if option.mac.macos_render_timer > RENDER_TIMER_SYSTEM { + if let timer = self.timer, option.mac.macos_render_timer == RENDER_TIMER_PRECISE { timer.scheduleAt(time: inOutputTime.pointee.hostTime, closure: signalSwap) return kCVReturnSuccess } signalSwap() + return kCVReturnSuccess + } + + if option.mac.macos_render_timer == RENDER_TIMER_PRESENTATION_FEEDBACK { + presentation?.add(time: inOutputTime.pointee) } return kCVReturnSuccess @@ -144,27 +154,16 @@ class MacCommon: Common { flagEvents(VO_EVENT_AMBIENT_LIGHTING_CHANGED) } - @objc override func updateICCProfile() { - guard let colorSpace = window?.screen?.colorSpace else { - log.sendWarning("Couldn't update ICC Profile, no color space available") - return - } - - layer?.colorspace = colorSpace.cgColorSpace + override func updateICCProfile() { flagEvents(VO_EVENT_ICC_PROFILE_CHANGED) } override func windowDidResize() { - guard let window = window else { - log.sendWarning("No window available on window resize event") - return - } - - updateRenderSize(window.framePixel.size) + flagEvents(VO_EVENT_RESIZE | VO_EVENT_EXPOSE) } override func windowDidChangeScreenProfile() { - needsICCUpdate = true + updateICCProfile() } override func windowDidChangeBackingProperties() { diff --git a/video/out/opengl/common.c b/video/out/opengl/common.c index ee26508..e3fc1ba 100644 --- a/video/out/opengl/common.c +++ b/video/out/opengl/common.c @@ -398,7 +398,7 @@ static const struct gl_functions gl_functions[] = { .provides = MPGL_CAP_NESTED_ARRAY, }, // Swap control, always an OS specific extension - // The OSX code loads this manually. + // The macOS code loads this manually. { .extension = "GLX_SGI_swap_control", .functions = (const struct gl_function[]) { diff --git a/video/out/opengl/context_drm_egl.c b/video/out/opengl/context_drm_egl.c index 2db428f..ff4de18 100644 --- a/video/out/opengl/context_drm_egl.c +++ b/video/out/opengl/context_drm_egl.c @@ -616,6 +616,10 @@ static bool drm_egl_init(struct ra_ctx *ctx) xrgb_format = GBM_FORMAT_XBGR8888; break; default: + if (drm->opts->drm_format != DRM_OPTS_FORMAT_XRGB8888) { + MP_VERBOSE(ctx->vo, "Requested format not supported by context, " + "falling back to xrgb8888\n"); + } argb_format = GBM_FORMAT_ARGB8888; xrgb_format = GBM_FORMAT_XRGB8888; break; diff --git a/video/out/opengl/context_glx.c b/video/out/opengl/context_glx.c index 4062224..a2a63e1 100644 --- a/video/out/opengl/context_glx.c +++ b/video/out/opengl/context_glx.c @@ -25,7 +25,7 @@ #define GLX_CONTEXT_FLAGS_ARB 0x2094 #define GLX_CONTEXT_PROFILE_MASK_ARB 0x9126 #ifndef __APPLE__ -// These are respectively 0x00000001 and 0x00000002 on OSX +// These are respectively 0x00000001 and 0x00000002 on macOS #define GLX_CONTEXT_DEBUG_BIT_ARB 0x0001 #define GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x0002 #endif diff --git a/video/out/opengl/context_rpi.c b/video/out/opengl/context_rpi.c deleted file mode 100644 index 0b6babb..0000000 --- a/video/out/opengl/context_rpi.c +++ /dev/null @@ -1,327 +0,0 @@ -/* - * This file is part of mpv. - * - * mpv is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * mpv is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with mpv. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <assert.h> -#include <stdatomic.h> -#include <stddef.h> - -#include <bcm_host.h> - -#include <EGL/egl.h> -#include <EGL/eglext.h> - -#include "common/common.h" -#include "video/out/win_state.h" -#include "context.h" -#include "egl_helpers.h" - -struct priv { - struct GL gl; - DISPMANX_DISPLAY_HANDLE_T display; - DISPMANX_ELEMENT_HANDLE_T window; - DISPMANX_UPDATE_HANDLE_T update; - EGLDisplay egl_display; - EGLConfig egl_config; - EGLContext egl_context; - EGLSurface egl_surface; - // yep, the API keeps a pointer to it - EGL_DISPMANX_WINDOW_T egl_window; - int x, y, w, h; - double display_fps; - atomic_int reload_display; - int win_params[4]; -}; - -static void tv_callback(void *callback_data, uint32_t reason, uint32_t param1, - uint32_t param2) -{ - struct ra_ctx *ctx = callback_data; - struct priv *p = ctx->priv; - atomic_store(&p->reload_display, true); - vo_wakeup(ctx->vo); -} - -static void destroy_dispmanx(struct ra_ctx *ctx) -{ - struct priv *p = ctx->priv; - - if (p->egl_surface) { - eglMakeCurrent(p->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, - EGL_NO_CONTEXT); - eglDestroySurface(p->egl_display, p->egl_surface); - p->egl_surface = EGL_NO_SURFACE; - } - - if (p->window) - vc_dispmanx_element_remove(p->update, p->window); - p->window = 0; - if (p->display) - vc_dispmanx_display_close(p->display); - p->display = 0; - if (p->update) - vc_dispmanx_update_submit_sync(p->update); - p->update = 0; -} - -static void rpi_uninit(struct ra_ctx *ctx) -{ - struct priv *p = ctx->priv; - ra_gl_ctx_uninit(ctx); - - vc_tv_unregister_callback_full(tv_callback, ctx); - - destroy_dispmanx(ctx); - - if (p->egl_context) - eglDestroyContext(p->egl_display, p->egl_context); - p->egl_context = EGL_NO_CONTEXT; - eglReleaseThread(); - p->egl_display = EGL_NO_DISPLAY; -} - -static bool recreate_dispmanx(struct ra_ctx *ctx) -{ - struct priv *p = ctx->priv; - int display_nr = 0; - int layer = 0; - - MP_VERBOSE(ctx, "Recreating DISPMANX state...\n"); - - destroy_dispmanx(ctx); - - p->display = vc_dispmanx_display_open(display_nr); - p->update = vc_dispmanx_update_start(0); - if (!p->display || !p->update) { - MP_FATAL(ctx, "Could not get DISPMANX objects.\n"); - goto fail; - } - - uint32_t dispw, disph; - if (graphics_get_display_size(0, &dispw, &disph) < 0) { - MP_FATAL(ctx, "Could not get display size.\n"); - goto fail; - } - p->w = dispw; - p->h = disph; - - if (ctx->vo->opts->fullscreen) { - p->x = p->y = 0; - } else { - struct vo_win_geometry geo; - struct mp_rect screenrc = {0, 0, p->w, p->h}; - - vo_calc_window_geometry(ctx->vo, &screenrc, &geo); - - mp_rect_intersection(&geo.win, &screenrc); - - p->x = geo.win.x0; - p->y = geo.win.y0; - p->w = geo.win.x1 - geo.win.x0; - p->h = geo.win.y1 - geo.win.y0; - } - - // dispmanx is like a neanderthal version of Wayland - you can add an - // overlay any place on the screen. - VC_RECT_T dst = {.x = p->x, .y = p->y, .width = p->w, .height = p->h}; - VC_RECT_T src = {.width = p->w << 16, .height = p->h << 16}; - VC_DISPMANX_ALPHA_T alpha = { - .flags = DISPMANX_FLAGS_ALPHA_FIXED_ALL_PIXELS, - .opacity = 0xFF, - }; - p->window = vc_dispmanx_element_add(p->update, p->display, layer, &dst, 0, - &src, DISPMANX_PROTECTION_NONE, &alpha, - 0, 0); - if (!p->window) { - MP_FATAL(ctx, "Could not add DISPMANX element.\n"); - goto fail; - } - - vc_dispmanx_update_submit_sync(p->update); - p->update = vc_dispmanx_update_start(0); - - p->egl_window = (EGL_DISPMANX_WINDOW_T){ - .element = p->window, - .width = p->w, - .height = p->h, - }; - p->egl_surface = eglCreateWindowSurface(p->egl_display, p->egl_config, - &p->egl_window, NULL); - - if (p->egl_surface == EGL_NO_SURFACE) { - MP_FATAL(ctx, "Could not create EGL surface!\n"); - goto fail; - } - - if (!eglMakeCurrent(p->egl_display, p->egl_surface, p->egl_surface, - p->egl_context)) - { - MP_FATAL(ctx, "Failed to set context!\n"); - goto fail; - } - - p->display_fps = 0; - TV_GET_STATE_RESP_T tvstate; - TV_DISPLAY_STATE_T tvstate_disp; - if (!vc_tv_get_state(&tvstate) && !vc_tv_get_display_state(&tvstate_disp)) { - if (tvstate_disp.state & (VC_HDMI_HDMI | VC_HDMI_DVI)) { - p->display_fps = tvstate_disp.display.hdmi.frame_rate; - - HDMI_PROPERTY_PARAM_T param = { - .property = HDMI_PROPERTY_PIXEL_CLOCK_TYPE, - }; - if (!vc_tv_hdmi_get_property(¶m) && - param.param1 == HDMI_PIXEL_CLOCK_TYPE_NTSC) - p->display_fps = p->display_fps / 1.001; - } else { - p->display_fps = tvstate_disp.display.sdtv.frame_rate; - } - } - - p->win_params[0] = display_nr; - p->win_params[1] = layer; - p->win_params[2] = p->x; - p->win_params[3] = p->y; - - ctx->vo->dwidth = p->w; - ctx->vo->dheight = p->h; - if (ctx->swapchain) - ra_gl_ctx_resize(ctx->swapchain, p->w, p->h, 0); - - ctx->vo->want_redraw = true; - - vo_event(ctx->vo, VO_EVENT_WIN_STATE); - return true; - -fail: - destroy_dispmanx(ctx); - return false; -} - -static void rpi_swap_buffers(struct ra_ctx *ctx) -{ - struct priv *p = ctx->priv; - eglSwapBuffers(p->egl_display, p->egl_surface); -} - -static bool rpi_init(struct ra_ctx *ctx) -{ - struct priv *p = ctx->priv = talloc_zero(ctx, struct priv); - - bcm_host_init(); - - vc_tv_register_callback(tv_callback, ctx); - - p->egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY); - if (!eglInitialize(p->egl_display, NULL, NULL)) { - MP_FATAL(ctx, "EGL failed to initialize.\n"); - goto fail; - } - - if (!mpegl_create_context(ctx, p->egl_display, &p->egl_context, &p->egl_config)) - goto fail; - - if (!recreate_dispmanx(ctx)) - goto fail; - - mpegl_load_functions(&p->gl, ctx->log); - - struct ra_gl_ctx_params params = { - .swap_buffers = rpi_swap_buffers, - }; - - if (!ra_gl_ctx_init(ctx, &p->gl, params)) - goto fail; - - ra_add_native_resource(ctx->ra, "MPV_RPI_WINDOW", p->win_params); - - ra_gl_ctx_resize(ctx->swapchain, ctx->vo->dwidth, ctx->vo->dheight, 0); - return true; - -fail: - rpi_uninit(ctx); - return false; -} - -static bool rpi_reconfig(struct ra_ctx *ctx) -{ - return recreate_dispmanx(ctx); -} - -static struct mp_image *take_screenshot(struct ra_ctx *ctx) -{ - struct priv *p = ctx->priv; - - if (!p->display) - return NULL; - - struct mp_image *img = mp_image_alloc(IMGFMT_BGR0, p->w, p->h); - if (!img) - return NULL; - - DISPMANX_RESOURCE_HANDLE_T resource = - vc_dispmanx_resource_create(VC_IMAGE_ARGB8888, - img->w | ((img->w * 4) << 16), img->h, - &(int32_t){0}); - if (!resource) - goto fail; - - if (vc_dispmanx_snapshot(p->display, resource, 0)) - goto fail; - - VC_RECT_T rc = {.width = img->w, .height = img->h}; - if (vc_dispmanx_resource_read_data(resource, &rc, img->planes[0], img->stride[0])) - goto fail; - - vc_dispmanx_resource_delete(resource); - return img; - -fail: - vc_dispmanx_resource_delete(resource); - talloc_free(img); - return NULL; -} - -static int rpi_control(struct ra_ctx *ctx, int *events, int request, void *arg) -{ - struct priv *p = ctx->priv; - - switch (request) { - case VOCTRL_SCREENSHOT_WIN: - *(struct mp_image **)arg = take_screenshot(ctx); - return VO_TRUE; - case VOCTRL_CHECK_EVENTS: - if (atomic_fetch_and(&p->reload_display, 0)) { - MP_WARN(ctx, "Recovering from display mode switch...\n"); - recreate_dispmanx(ctx); - } - return VO_TRUE; - case VOCTRL_GET_DISPLAY_FPS: - *(double *)arg = p->display_fps; - return VO_TRUE; - } - - return VO_NOTIMPL; -} - -const struct ra_ctx_fns ra_ctx_rpi = { - .type = "opengl", - .name = "rpi", - .reconfig = rpi_reconfig, - .control = rpi_control, - .init = rpi_init, - .uninit = rpi_uninit, -}; diff --git a/video/out/opengl/context_wayland.c b/video/out/opengl/context_wayland.c index 26c5268..2c5611b 100644 --- a/video/out/opengl/context_wayland.c +++ b/video/out/opengl/context_wayland.c @@ -47,14 +47,14 @@ static void resize(struct ra_ctx *ctx) const int32_t width = mp_rect_w(wl->geometry); const int32_t height = mp_rect_h(wl->geometry); + vo_wayland_handle_scale(wl); + vo_wayland_set_opaque_region(wl, ctx->opts.want_alpha); if (p->egl_window) wl_egl_window_resize(p->egl_window, width, height, 0, 0); wl->vo->dwidth = width; wl->vo->dheight = height; - - vo_wayland_handle_fractional_scale(wl); } static bool wayland_egl_check_visible(struct ra_ctx *ctx) diff --git a/video/out/opengl/context_win.c b/video/out/opengl/context_win.c index 968b176..a582e28 100644 --- a/video/out/opengl/context_win.c +++ b/video/out/opengl/context_win.c @@ -96,6 +96,7 @@ static bool create_dc(struct ra_ctx *ctx) pfd.iPixelType = PFD_TYPE_RGBA; pfd.cColorBits = 24; + pfd.cAlphaBits = 8; pfd.iLayerType = PFD_MAIN_PLANE; int pf = ChoosePixelFormat(hdc, &pfd); @@ -293,6 +294,9 @@ static bool wgl_init(struct ra_ctx *ctx) if (!vo_w32_init(ctx->vo)) goto fail; + if (ctx->opts.want_alpha) + vo_w32_set_transparency(ctx->vo, ctx->opts.want_alpha); + vo_w32_run_on_thread(ctx->vo, create_ctx, ctx); if (!p->context) goto fail; @@ -368,11 +372,17 @@ static int wgl_control(struct ra_ctx *ctx, int *events, int request, void *arg) return ret; } +static void wgl_update_render_opts(struct ra_ctx *ctx) +{ + vo_w32_set_transparency(ctx->vo, ctx->opts.want_alpha); +} + const struct ra_ctx_fns ra_ctx_wgl = { - .type = "opengl", - .name = "win", - .init = wgl_init, - .reconfig = wgl_reconfig, - .control = wgl_control, - .uninit = wgl_uninit, + .type = "opengl", + .name = "win", + .init = wgl_init, + .reconfig = wgl_reconfig, + .control = wgl_control, + .update_render_opts = wgl_update_render_opts, + .uninit = wgl_uninit, }; diff --git a/video/out/opengl/egl_helpers.c b/video/out/opengl/egl_helpers.c index 3bf6239..18d9027 100644 --- a/video/out/opengl/egl_helpers.c +++ b/video/out/opengl/egl_helpers.c @@ -192,18 +192,20 @@ static bool create_context(struct ra_ctx *ctx, EGLDisplay display, } if (!egl_ctx) { // Fallback for EGL 1.4 without EGL_KHR_create_context or GLES - // Add the context flags only for GLES - GL has been attempted above EGLint attrs[] = { - EGL_CONTEXT_CLIENT_VERSION, 2, - es ? EGL_CONTEXT_FLAGS_KHR : EGL_NONE, ctx_flags, + EGL_CONTEXT_FLAGS_KHR, ctx_flags, + es ? EGL_CONTEXT_CLIENT_VERSION : EGL_NONE, 2, EGL_NONE }; egl_ctx = eglCreateContext(display, config, EGL_NO_CONTEXT, attrs); + if (!egl_ctx) + egl_ctx = eglCreateContext(display, config, EGL_NO_CONTEXT, &attrs[2]); } if (!egl_ctx) { - MP_MSG(ctx, msgl, "Could not create EGL context for %s!\n", name); + MP_MSG(ctx, msgl, "Could not create EGL context for %s (error=%d)!\n", + name, eglGetError()); return false; } diff --git a/video/out/opengl/formats.c b/video/out/opengl/formats.c index a0b79e2..dd0e89d 100644 --- a/video/out/opengl/formats.c +++ b/video/out/opengl/formats.c @@ -94,7 +94,7 @@ const struct gl_format gl_formats[] = { // Special formats. {"rgb565", GL_RGB8, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, F_TF | F_GL2 | F_GL3}, - // Worthless, but needed by OSX videotoolbox interop on old Apple hardware. + // Worthless, but needed by macOS videotoolbox interop on old Apple hardware. {"appleyp", GL_RGB, GL_RGB_422_APPLE, GL_UNSIGNED_SHORT_8_8_APPLE, F_TF | F_APPL}, diff --git a/video/out/opengl/hwdec_rpi.c b/video/out/opengl/hwdec_rpi.c deleted file mode 100644 index 5362832..0000000 --- a/video/out/opengl/hwdec_rpi.c +++ /dev/null @@ -1,384 +0,0 @@ -/* - * This file is part of mpv. - * - * mpv is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * mpv is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with mpv. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <math.h> -#include <stdbool.h> -#include <assert.h> - -#include <bcm_host.h> -#include <interface/mmal/mmal.h> -#include <interface/mmal/util/mmal_util.h> -#include <interface/mmal/util/mmal_default_components.h> -#include <interface/mmal/vc/mmal_vc_api.h> - -#include <libavutil/rational.h> - -#include "common/common.h" -#include "common/msg.h" -#include "video/mp_image.h" -#include "video/out/gpu/hwdec.h" - -#include "common.h" - -struct priv { - struct mp_log *log; - - struct mp_image_params params; - - MMAL_COMPONENT_T *renderer; - bool renderer_enabled; - - // for RAM input - MMAL_POOL_T *swpool; - - struct mp_image *current_frame; - - struct mp_rect src, dst; - int cur_window[4]; // raw user params -}; - -// Magic alignments (in pixels) expected by the MMAL internals. -#define ALIGN_W 32 -#define ALIGN_H 16 - -// Make mpi point to buffer, assuming MMAL_ENCODING_I420. -// buffer can be NULL. -// Return the required buffer space. -static size_t layout_buffer(struct mp_image *mpi, MMAL_BUFFER_HEADER_T *buffer, - struct mp_image_params *params) -{ - assert(params->imgfmt == IMGFMT_420P); - mp_image_set_params(mpi, params); - int w = MP_ALIGN_UP(params->w, ALIGN_W); - int h = MP_ALIGN_UP(params->h, ALIGN_H); - uint8_t *cur = buffer ? buffer->data : NULL; - size_t size = 0; - for (int i = 0; i < 3; i++) { - int div = i ? 2 : 1; - mpi->planes[i] = cur; - mpi->stride[i] = w / div; - size_t plane_size = h / div * mpi->stride[i]; - if (cur) - cur += plane_size; - size += plane_size; - } - return size; -} - -static MMAL_FOURCC_T map_csp(enum mp_csp csp) -{ - switch (csp) { - case MP_CSP_BT_601: return MMAL_COLOR_SPACE_ITUR_BT601; - case MP_CSP_BT_709: return MMAL_COLOR_SPACE_ITUR_BT709; - case MP_CSP_SMPTE_240M: return MMAL_COLOR_SPACE_SMPTE240M; - default: return MMAL_COLOR_SPACE_UNKNOWN; - } -} - -static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) -{ - mmal_buffer_header_release(buffer); -} - -static void input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) -{ - struct mp_image *mpi = buffer->user_data; - talloc_free(mpi); -} - -static void disable_renderer(struct ra_hwdec *hw) -{ - struct priv *p = hw->priv; - - if (p->renderer_enabled) { - mmal_port_disable(p->renderer->control); - mmal_port_disable(p->renderer->input[0]); - - mmal_port_flush(p->renderer->control); - mmal_port_flush(p->renderer->input[0]); - - mmal_component_disable(p->renderer); - } - mmal_pool_destroy(p->swpool); - p->swpool = NULL; - p->renderer_enabled = false; -} - -// check_window_only: assume params and dst/src rc are unchanged -static void update_overlay(struct ra_hwdec *hw, bool check_window_only) -{ - struct priv *p = hw->priv; - MMAL_PORT_T *input = p->renderer->input[0]; - struct mp_rect src = p->src; - struct mp_rect dst = p->dst; - - int defs[4] = {0, 0, 0, 0}; - int *z = ra_get_native_resource(hw->ra_ctx->ra, "MPV_RPI_WINDOW"); - if (!z) - z = defs; - - // As documented in the libmpv openglcb headers. - int display = z[0]; - int layer = z[1]; - int x = z[2]; - int y = z[3]; - - if (check_window_only && memcmp(z, p->cur_window, sizeof(p->cur_window)) == 0) - return; - - memcpy(p->cur_window, z, sizeof(p->cur_window)); - - int rotate[] = {MMAL_DISPLAY_ROT0, - MMAL_DISPLAY_ROT90, - MMAL_DISPLAY_ROT180, - MMAL_DISPLAY_ROT270}; - - int src_w = src.x1 - src.x0, src_h = src.y1 - src.y0, - dst_w = dst.x1 - dst.x0, dst_h = dst.y1 - dst.y0; - int p_x, p_y; - av_reduce(&p_x, &p_y, dst_w * src_h, src_w * dst_h, 16000); - MMAL_DISPLAYREGION_T dr = { - .hdr = { .id = MMAL_PARAMETER_DISPLAYREGION, - .size = sizeof(MMAL_DISPLAYREGION_T), }, - .src_rect = { .x = src.x0, .y = src.y0, - .width = src_w, .height = src_h }, - .dest_rect = { .x = dst.x0 + x, .y = dst.y0 + y, - .width = dst_w, .height = dst_h }, - .layer = layer - 1, // under the GL layer - .display_num = display, - .pixel_x = p_x, - .pixel_y = p_y, - .transform = rotate[p->params.rotate / 90], - .fullscreen = 0, - .set = MMAL_DISPLAY_SET_SRC_RECT | MMAL_DISPLAY_SET_DEST_RECT | - MMAL_DISPLAY_SET_LAYER | MMAL_DISPLAY_SET_NUM | - MMAL_DISPLAY_SET_PIXEL | MMAL_DISPLAY_SET_TRANSFORM | - MMAL_DISPLAY_SET_FULLSCREEN, - }; - - if (p->params.rotate % 180 == 90) { - MPSWAP(int, dr.src_rect.x, dr.src_rect.y); - MPSWAP(int, dr.src_rect.width, dr.src_rect.height); - } - - if (mmal_port_parameter_set(input, &dr.hdr)) - MP_WARN(p, "could not set video rectangle\n"); -} - -static int enable_renderer(struct ra_hwdec *hw) -{ - struct priv *p = hw->priv; - MMAL_PORT_T *input = p->renderer->input[0]; - struct mp_image_params *params = &p->params; - - if (p->renderer_enabled) - return 0; - - if (!params->imgfmt) - return -1; - - bool opaque = params->imgfmt == IMGFMT_MMAL; - - input->format->encoding = opaque ? MMAL_ENCODING_OPAQUE : MMAL_ENCODING_I420; - input->format->es->video.width = MP_ALIGN_UP(params->w, ALIGN_W); - input->format->es->video.height = MP_ALIGN_UP(params->h, ALIGN_H); - input->format->es->video.crop = (MMAL_RECT_T){0, 0, params->w, params->h}; - input->format->es->video.par = (MMAL_RATIONAL_T){params->p_w, params->p_h}; - input->format->es->video.color_space = map_csp(params->color.space); - - if (mmal_port_format_commit(input)) - return -1; - - input->buffer_num = MPMAX(input->buffer_num_min, - input->buffer_num_recommended) + 3; - input->buffer_size = MPMAX(input->buffer_size_min, - input->buffer_size_recommended); - - if (!opaque) { - size_t size = layout_buffer(&(struct mp_image){0}, NULL, params); - if (input->buffer_size != size) { - MP_FATAL(hw, "We disagree with MMAL about buffer sizes.\n"); - return -1; - } - - p->swpool = mmal_pool_create(input->buffer_num, input->buffer_size); - if (!p->swpool) { - MP_FATAL(hw, "Could not allocate buffer pool.\n"); - return -1; - } - } - - update_overlay(hw, false); - - p->renderer_enabled = true; - - if (mmal_port_enable(p->renderer->control, control_port_cb)) - return -1; - - if (mmal_port_enable(input, input_port_cb)) - return -1; - - if (mmal_component_enable(p->renderer)) { - MP_FATAL(hw, "Failed to enable video renderer.\n"); - return -1; - } - - return 0; -} - -static void free_mmal_buffer(void *arg) -{ - MMAL_BUFFER_HEADER_T *buffer = arg; - mmal_buffer_header_release(buffer); -} - -static struct mp_image *upload(struct ra_hwdec *hw, struct mp_image *hw_image) -{ - struct priv *p = hw->priv; - - MMAL_BUFFER_HEADER_T *buffer = mmal_queue_wait(p->swpool->queue); - if (!buffer) { - MP_ERR(hw, "Can't allocate buffer.\n"); - return NULL; - } - mmal_buffer_header_reset(buffer); - - struct mp_image *new_ref = mp_image_new_custom_ref(NULL, buffer, - free_mmal_buffer); - if (!new_ref) { - mmal_buffer_header_release(buffer); - MP_ERR(hw, "Out of memory.\n"); - return NULL; - } - - mp_image_setfmt(new_ref, IMGFMT_MMAL); - new_ref->planes[3] = (void *)buffer; - - struct mp_image dmpi = {0}; - buffer->length = layout_buffer(&dmpi, buffer, &p->params); - mp_image_copy(&dmpi, hw_image); - - return new_ref; -} - -static int overlay_frame(struct ra_hwdec *hw, struct mp_image *hw_image, - struct mp_rect *src, struct mp_rect *dst, bool newframe) -{ - struct priv *p = hw->priv; - - if (hw_image && !mp_image_params_equal(&p->params, &hw_image->params)) { - p->params = hw_image->params; - - disable_renderer(hw); - mp_image_unrefp(&p->current_frame); - - if (enable_renderer(hw) < 0) - return -1; - } - - if (hw_image && p->current_frame && !newframe) { - if (!mp_rect_equals(&p->src, src) ||mp_rect_equals(&p->dst, dst)) { - p->src = *src; - p->dst = *dst; - update_overlay(hw, false); - } - return 0; // don't reupload - } - - mp_image_unrefp(&p->current_frame); - - if (!hw_image) { - disable_renderer(hw); - return 0; - } - - if (enable_renderer(hw) < 0) - return -1; - - update_overlay(hw, true); - - struct mp_image *mpi = NULL; - if (hw_image->imgfmt == IMGFMT_MMAL) { - mpi = mp_image_new_ref(hw_image); - } else { - mpi = upload(hw, hw_image); - } - - if (!mpi) { - disable_renderer(hw); - return -1; - } - - MMAL_BUFFER_HEADER_T *ref = (void *)mpi->planes[3]; - - // Assume this field is free for use by us. - ref->user_data = mpi; - - if (mmal_port_send_buffer(p->renderer->input[0], ref)) { - MP_ERR(hw, "could not queue picture!\n"); - talloc_free(mpi); - return -1; - } - - return 0; -} - -static void destroy(struct ra_hwdec *hw) -{ - struct priv *p = hw->priv; - - disable_renderer(hw); - - if (p->renderer) - mmal_component_release(p->renderer); - - mmal_vc_deinit(); -} - -static int create(struct ra_hwdec *hw) -{ - struct priv *p = hw->priv; - p->log = hw->log; - - bcm_host_init(); - - if (mmal_vc_init()) { - MP_FATAL(hw, "Could not initialize MMAL.\n"); - return -1; - } - - if (mmal_component_create(MMAL_COMPONENT_DEFAULT_VIDEO_RENDERER, &p->renderer)) - { - MP_FATAL(hw, "Could not create MMAL renderer.\n"); - mmal_vc_deinit(); - return -1; - } - - return 0; -} - -const struct ra_hwdec_driver ra_hwdec_rpi_overlay = { - .name = "rpi-overlay", - .priv_size = sizeof(struct priv), - .imgfmts = {IMGFMT_MMAL, IMGFMT_420P, 0}, - .init = create, - .overlay_frame = overlay_frame, - .uninit = destroy, -}; diff --git a/video/out/placebo/ra_pl.c b/video/out/placebo/ra_pl.c index 6259651..14c9444 100644 --- a/video/out/placebo/ra_pl.c +++ b/video/out/placebo/ra_pl.c @@ -534,7 +534,7 @@ static void renderpass_run_pl(struct ra *ra, .data = val->data, }); } else { - struct pl_desc_binding bind; + struct pl_desc_binding bind = {0}; switch (inp->type) { case RA_VARTYPE_TEX: case RA_VARTYPE_IMG_W: { diff --git a/video/out/placebo/utils.c b/video/out/placebo/utils.c index 1209b72..dae9b14 100644 --- a/video/out/placebo/utils.c +++ b/video/out/placebo/utils.c @@ -25,7 +25,7 @@ static const enum pl_log_level msg_lev_to_pl_log[MSGL_MAX+1] = { }; // translates log levels while probing -static const enum pl_log_level probing_map(enum pl_log_level level) +static enum pl_log_level probing_map(enum pl_log_level level) { switch (level) { case PL_LOG_FATAL: @@ -44,6 +44,12 @@ static void log_cb(void *priv, enum pl_log_level level, const char *msg) mp_msg(log, pl_log_to_msg_lev[level], "%s\n", msg); } +static int determine_pl_log_level(struct mp_log *log) +{ + int log_level = mp_msg_level(log); + return log_level == -1 ? PL_LOG_NONE : msg_lev_to_pl_log[log_level]; +} + static void log_cb_probing(void *priv, enum pl_log_level level, const char *msg) { struct mp_log *log = priv; @@ -54,7 +60,7 @@ pl_log mppl_log_create(void *tactx, struct mp_log *log) { return pl_log_create(PL_API_VER, &(struct pl_log_params) { .log_cb = log_cb, - .log_level = msg_lev_to_pl_log[mp_msg_level(log)], + .log_level = determine_pl_log_level(log), .log_priv = mp_log_new(tactx, log, "libplacebo"), }); } @@ -65,199 +71,3 @@ void mppl_log_set_probing(pl_log log, bool probing) params.log_cb = probing ? log_cb_probing : log_cb; pl_log_update(log, ¶ms); } - -enum pl_color_primaries mp_prim_to_pl(enum mp_csp_prim prim) -{ - switch (prim) { - case MP_CSP_PRIM_AUTO: return PL_COLOR_PRIM_UNKNOWN; - case MP_CSP_PRIM_BT_601_525: return PL_COLOR_PRIM_BT_601_525; - case MP_CSP_PRIM_BT_601_625: return PL_COLOR_PRIM_BT_601_625; - case MP_CSP_PRIM_BT_709: return PL_COLOR_PRIM_BT_709; - case MP_CSP_PRIM_BT_2020: return PL_COLOR_PRIM_BT_2020; - case MP_CSP_PRIM_BT_470M: return PL_COLOR_PRIM_BT_470M; - case MP_CSP_PRIM_APPLE: return PL_COLOR_PRIM_APPLE; - case MP_CSP_PRIM_ADOBE: return PL_COLOR_PRIM_ADOBE; - case MP_CSP_PRIM_PRO_PHOTO: return PL_COLOR_PRIM_PRO_PHOTO; - case MP_CSP_PRIM_CIE_1931: return PL_COLOR_PRIM_CIE_1931; - case MP_CSP_PRIM_DCI_P3: return PL_COLOR_PRIM_DCI_P3; - case MP_CSP_PRIM_DISPLAY_P3: return PL_COLOR_PRIM_DISPLAY_P3; - case MP_CSP_PRIM_V_GAMUT: return PL_COLOR_PRIM_V_GAMUT; - case MP_CSP_PRIM_S_GAMUT: return PL_COLOR_PRIM_S_GAMUT; - case MP_CSP_PRIM_EBU_3213: return PL_COLOR_PRIM_EBU_3213; - case MP_CSP_PRIM_FILM_C: return PL_COLOR_PRIM_FILM_C; - case MP_CSP_PRIM_ACES_AP0: return PL_COLOR_PRIM_ACES_AP0; - case MP_CSP_PRIM_ACES_AP1: return PL_COLOR_PRIM_ACES_AP1; - case MP_CSP_PRIM_COUNT: return PL_COLOR_PRIM_COUNT; - } - - MP_ASSERT_UNREACHABLE(); -} - -enum mp_csp_prim mp_prim_from_pl(enum pl_color_primaries prim) -{ - switch (prim){ - case PL_COLOR_PRIM_UNKNOWN: return MP_CSP_PRIM_AUTO; - case PL_COLOR_PRIM_BT_601_525: return MP_CSP_PRIM_BT_601_525; - case PL_COLOR_PRIM_BT_601_625: return MP_CSP_PRIM_BT_601_625; - case PL_COLOR_PRIM_BT_709: return MP_CSP_PRIM_BT_709; - case PL_COLOR_PRIM_BT_2020: return MP_CSP_PRIM_BT_2020; - case PL_COLOR_PRIM_BT_470M: return MP_CSP_PRIM_BT_470M; - case PL_COLOR_PRIM_APPLE: return MP_CSP_PRIM_APPLE; - case PL_COLOR_PRIM_ADOBE: return MP_CSP_PRIM_ADOBE; - case PL_COLOR_PRIM_PRO_PHOTO: return MP_CSP_PRIM_PRO_PHOTO; - case PL_COLOR_PRIM_CIE_1931: return MP_CSP_PRIM_CIE_1931; - case PL_COLOR_PRIM_DCI_P3: return MP_CSP_PRIM_DCI_P3; - case PL_COLOR_PRIM_DISPLAY_P3: return MP_CSP_PRIM_DISPLAY_P3; - case PL_COLOR_PRIM_V_GAMUT: return MP_CSP_PRIM_V_GAMUT; - case PL_COLOR_PRIM_S_GAMUT: return MP_CSP_PRIM_S_GAMUT; - case PL_COLOR_PRIM_EBU_3213: return MP_CSP_PRIM_EBU_3213; - case PL_COLOR_PRIM_FILM_C: return MP_CSP_PRIM_FILM_C; - case PL_COLOR_PRIM_ACES_AP0: return MP_CSP_PRIM_ACES_AP0; - case PL_COLOR_PRIM_ACES_AP1: return MP_CSP_PRIM_ACES_AP1; - case PL_COLOR_PRIM_COUNT: return MP_CSP_PRIM_COUNT; - } - - MP_ASSERT_UNREACHABLE(); -} - -enum pl_color_transfer mp_trc_to_pl(enum mp_csp_trc trc) -{ - switch (trc) { - case MP_CSP_TRC_AUTO: return PL_COLOR_TRC_UNKNOWN; - case MP_CSP_TRC_BT_1886: return PL_COLOR_TRC_BT_1886; - case MP_CSP_TRC_SRGB: return PL_COLOR_TRC_SRGB; - case MP_CSP_TRC_LINEAR: return PL_COLOR_TRC_LINEAR; - case MP_CSP_TRC_GAMMA18: return PL_COLOR_TRC_GAMMA18; - case MP_CSP_TRC_GAMMA20: return PL_COLOR_TRC_GAMMA20; - case MP_CSP_TRC_GAMMA22: return PL_COLOR_TRC_GAMMA22; - case MP_CSP_TRC_GAMMA24: return PL_COLOR_TRC_GAMMA24; - case MP_CSP_TRC_GAMMA26: return PL_COLOR_TRC_GAMMA26; - case MP_CSP_TRC_GAMMA28: return PL_COLOR_TRC_GAMMA28; - case MP_CSP_TRC_PRO_PHOTO: return PL_COLOR_TRC_PRO_PHOTO; - case MP_CSP_TRC_PQ: return PL_COLOR_TRC_PQ; - case MP_CSP_TRC_HLG: return PL_COLOR_TRC_HLG; - case MP_CSP_TRC_V_LOG: return PL_COLOR_TRC_V_LOG; - case MP_CSP_TRC_S_LOG1: return PL_COLOR_TRC_S_LOG1; - case MP_CSP_TRC_S_LOG2: return PL_COLOR_TRC_S_LOG2; - case MP_CSP_TRC_ST428: return PL_COLOR_TRC_ST428; - case MP_CSP_TRC_COUNT: return PL_COLOR_TRC_COUNT; - } - - MP_ASSERT_UNREACHABLE(); -} - -enum mp_csp_trc mp_trc_from_pl(enum pl_color_transfer trc) -{ - switch (trc){ - case PL_COLOR_TRC_UNKNOWN: return MP_CSP_TRC_AUTO; - case PL_COLOR_TRC_BT_1886: return MP_CSP_TRC_BT_1886; - case PL_COLOR_TRC_SRGB: return MP_CSP_TRC_SRGB; - case PL_COLOR_TRC_LINEAR: return MP_CSP_TRC_LINEAR; - case PL_COLOR_TRC_GAMMA18: return MP_CSP_TRC_GAMMA18; - case PL_COLOR_TRC_GAMMA20: return MP_CSP_TRC_GAMMA20; - case PL_COLOR_TRC_GAMMA22: return MP_CSP_TRC_GAMMA22; - case PL_COLOR_TRC_GAMMA24: return MP_CSP_TRC_GAMMA24; - case PL_COLOR_TRC_GAMMA26: return MP_CSP_TRC_GAMMA26; - case PL_COLOR_TRC_GAMMA28: return MP_CSP_TRC_GAMMA28; - case PL_COLOR_TRC_PRO_PHOTO: return MP_CSP_TRC_PRO_PHOTO; - case PL_COLOR_TRC_PQ: return MP_CSP_TRC_PQ; - case PL_COLOR_TRC_HLG: return MP_CSP_TRC_HLG; - case PL_COLOR_TRC_V_LOG: return MP_CSP_TRC_V_LOG; - case PL_COLOR_TRC_S_LOG1: return MP_CSP_TRC_S_LOG1; - case PL_COLOR_TRC_S_LOG2: return MP_CSP_TRC_S_LOG2; - case PL_COLOR_TRC_ST428: return MP_CSP_TRC_ST428; - case PL_COLOR_TRC_COUNT: return MP_CSP_TRC_COUNT; - } - - MP_ASSERT_UNREACHABLE(); -} - -enum pl_color_system mp_csp_to_pl(enum mp_csp csp) -{ - switch (csp) { - case MP_CSP_AUTO: return PL_COLOR_SYSTEM_UNKNOWN; - case MP_CSP_BT_601: return PL_COLOR_SYSTEM_BT_601; - case MP_CSP_BT_709: return PL_COLOR_SYSTEM_BT_709; - case MP_CSP_SMPTE_240M: return PL_COLOR_SYSTEM_SMPTE_240M; - case MP_CSP_BT_2020_NC: return PL_COLOR_SYSTEM_BT_2020_NC; - case MP_CSP_BT_2020_C: return PL_COLOR_SYSTEM_BT_2020_C; - case MP_CSP_RGB: return PL_COLOR_SYSTEM_RGB; - case MP_CSP_XYZ: return PL_COLOR_SYSTEM_XYZ; - case MP_CSP_YCGCO: return PL_COLOR_SYSTEM_YCGCO; - case MP_CSP_COUNT: return PL_COLOR_SYSTEM_COUNT; - } - - MP_ASSERT_UNREACHABLE(); -} - -enum pl_color_levels mp_levels_to_pl(enum mp_csp_levels levels) -{ - switch (levels) { - case MP_CSP_LEVELS_AUTO: return PL_COLOR_LEVELS_UNKNOWN; - case MP_CSP_LEVELS_TV: return PL_COLOR_LEVELS_TV; - case MP_CSP_LEVELS_PC: return PL_COLOR_LEVELS_PC; - case MP_CSP_LEVELS_COUNT: return PL_COLOR_LEVELS_COUNT; - } - - MP_ASSERT_UNREACHABLE(); -} - -enum mp_csp_levels mp_levels_from_pl(enum pl_color_levels levels) -{ - switch (levels){ - case PL_COLOR_LEVELS_UNKNOWN: return MP_CSP_LEVELS_AUTO; - case PL_COLOR_LEVELS_TV: return MP_CSP_LEVELS_TV; - case PL_COLOR_LEVELS_PC: return MP_CSP_LEVELS_PC; - case PL_COLOR_LEVELS_COUNT: return MP_CSP_LEVELS_COUNT; - } - - MP_ASSERT_UNREACHABLE(); -} - -enum pl_alpha_mode mp_alpha_to_pl(enum mp_alpha_type alpha) -{ - switch (alpha) { - case MP_ALPHA_AUTO: return PL_ALPHA_UNKNOWN; - case MP_ALPHA_STRAIGHT: return PL_ALPHA_INDEPENDENT; - case MP_ALPHA_PREMUL: return PL_ALPHA_PREMULTIPLIED; - } - - MP_ASSERT_UNREACHABLE(); -} - -enum pl_chroma_location mp_chroma_to_pl(enum mp_chroma_location chroma) -{ - switch (chroma) { - case MP_CHROMA_AUTO: return PL_CHROMA_UNKNOWN; - case MP_CHROMA_TOPLEFT: return PL_CHROMA_TOP_LEFT; - case MP_CHROMA_LEFT: return PL_CHROMA_LEFT; - case MP_CHROMA_CENTER: return PL_CHROMA_CENTER; - case MP_CHROMA_COUNT: return PL_CHROMA_COUNT; - } - - MP_ASSERT_UNREACHABLE(); -} - -void mp_map_dovi_metadata_to_pl(struct mp_image *mpi, - struct pl_frame *frame) -{ -#ifdef PL_HAVE_LAV_DOLBY_VISION - if (mpi->dovi) { - const AVDOVIMetadata *metadata = (AVDOVIMetadata *) mpi->dovi->data; - const AVDOVIRpuDataHeader *header = av_dovi_get_header(metadata); - - if (header->disable_residual_flag) { - // Only automatically map DoVi RPUs that don't require an EL - struct pl_dovi_metadata *dovi = talloc_ptrtype(mpi, dovi); - pl_frame_map_avdovi_metadata(frame, dovi, metadata); - } - } - -#if defined(PL_HAVE_LIBDOVI) - if (mpi->dovi_buf) - pl_hdr_metadata_from_dovi_rpu(&frame->color.hdr, mpi->dovi_buf->data, - mpi->dovi_buf->size); -#endif - -#endif // PL_HAVE_LAV_DOLBY_VISION -} diff --git a/video/out/placebo/utils.h b/video/out/placebo/utils.h index bf780a8..3f61d8b 100644 --- a/video/out/placebo/utils.h +++ b/video/out/placebo/utils.h @@ -26,16 +26,3 @@ static inline struct pl_rect2d mp_rect2d_to_pl(struct mp_rect rc) .y1 = rc.y1, }; } - -enum pl_color_primaries mp_prim_to_pl(enum mp_csp_prim prim); -enum mp_csp_prim mp_prim_from_pl(enum pl_color_primaries prim); -enum pl_color_transfer mp_trc_to_pl(enum mp_csp_trc trc); -enum mp_csp_trc mp_trc_from_pl(enum pl_color_transfer trc); -enum pl_color_system mp_csp_to_pl(enum mp_csp csp); -enum pl_color_levels mp_levels_to_pl(enum mp_csp_levels levels); -enum mp_csp_levels mp_levels_from_pl(enum pl_color_levels levels); -enum pl_alpha_mode mp_alpha_to_pl(enum mp_alpha_type alpha); -enum pl_chroma_location mp_chroma_to_pl(enum mp_chroma_location chroma); - -void mp_map_dovi_metadata_to_pl(struct mp_image *mpi, - struct pl_frame *frame); diff --git a/video/out/vo.c b/video/out/vo.c index 50129fb..db29690 100644 --- a/video/out/vo.c +++ b/video/out/vo.c @@ -63,14 +63,12 @@ extern const struct vo_driver video_out_sdl; extern const struct vo_driver video_out_vaapi; extern const struct vo_driver video_out_dmabuf_wayland; extern const struct vo_driver video_out_wlshm; -extern const struct vo_driver video_out_rpi; extern const struct vo_driver video_out_tct; extern const struct vo_driver video_out_sixel; extern const struct vo_driver video_out_kitty; static const struct vo_driver *const video_out_drivers[] = { - &video_out_libmpv, #if HAVE_ANDROID &video_out_mediacodec_embed, #endif @@ -100,6 +98,7 @@ static const struct vo_driver *const video_out_drivers[] = #if HAVE_X11 &video_out_x11, #endif + &video_out_libmpv, &video_out_null, // should not be auto-selected &video_out_image, @@ -110,9 +109,6 @@ static const struct vo_driver *const video_out_drivers[] = #if HAVE_DRM &video_out_drm, #endif -#if HAVE_RPI_MMAL - &video_out_rpi, -#endif #if HAVE_SIXEL &video_out_sixel, #endif @@ -138,6 +134,7 @@ struct vo_internal { bool want_redraw; // redraw request from VO to player bool send_reset; // send VOCTRL_RESET bool paused; + bool wakeup_on_done; int queued_events; // event mask for the user int internal_events; // event mask for us @@ -239,7 +236,6 @@ static void update_opts(void *p) if (m_config_cache_update(vo->opts_cache)) { read_opts(vo); - if (vo->driver->control) { vo->driver->control(vo, VOCTRL_VO_OPTS_CHANGED, NULL); // "Legacy" update of video position related options. @@ -247,18 +243,6 @@ static void update_opts(void *p) vo->driver->control(vo, VOCTRL_SET_PANSCAN, NULL); } } - - if (vo->gl_opts_cache && m_config_cache_update(vo->gl_opts_cache)) { - // "Legacy" update of video GL renderer related options. - if (vo->driver->control) - vo->driver->control(vo, VOCTRL_UPDATE_RENDER_OPTS, NULL); - } - - if (m_config_cache_update(vo->eq_opts_cache)) { - // "Legacy" update of video equalizer related options. - if (vo->driver->control) - vo->driver->control(vo, VOCTRL_SET_EQUALIZER, NULL); - } } // Does not include thread- and VO uninit. @@ -270,6 +254,7 @@ static void dealloc_vo(struct vo *vo) talloc_free(vo->opts_cache); talloc_free(vo->gl_opts_cache); talloc_free(vo->eq_opts_cache); + mp_mutex_destroy(&vo->params_mutex); mp_mutex_destroy(&vo->in->lock); mp_cond_destroy(&vo->in->wakeup); @@ -301,6 +286,7 @@ static struct vo *vo_create(bool probing, struct mpv_global *global, .probing = probing, .in = talloc(vo, struct vo_internal), }; + mp_mutex_init(&vo->params_mutex); talloc_steal(vo, log); *vo->in = (struct vo_internal) { .dispatch = mp_dispatch_create(vo), @@ -319,12 +305,7 @@ static struct vo *vo_create(bool probing, struct mpv_global *global, update_opts, vo); vo->gl_opts_cache = m_config_cache_alloc(NULL, global, &gl_video_conf); - m_config_cache_set_dispatch_change_cb(vo->gl_opts_cache, vo->in->dispatch, - update_opts, vo); - vo->eq_opts_cache = m_config_cache_alloc(NULL, global, &mp_csp_equalizer_conf); - m_config_cache_set_dispatch_change_cb(vo->eq_opts_cache, vo->in->dispatch, - update_opts, vo); mp_input_set_mouse_transform(vo->input_ctx, NULL, NULL); if (vo->driver->encode != !!vo->encode_lavc_ctx) @@ -613,8 +594,10 @@ static void run_reconfig(void *p) mp_image_params_get_dsize(params, &vo->dwidth, &vo->dheight); + mp_mutex_lock(&vo->params_mutex); talloc_free(vo->params); vo->params = talloc_dup(vo, params); + mp_mutex_unlock(&vo->params_mutex); if (vo->driver->reconfig2) { *ret = vo->driver->reconfig2(vo, img); @@ -625,8 +608,10 @@ static void run_reconfig(void *p) if (vo->config_ok) { check_vo_caps(vo); } else { + mp_mutex_lock(&vo->params_mutex); talloc_free(vo->params); vo->params = NULL; + mp_mutex_unlock(&vo->params_mutex); } mp_mutex_lock(&in->lock); @@ -765,6 +750,52 @@ void vo_wakeup(struct vo *vo) mp_mutex_unlock(&in->lock); } +static int64_t get_current_frame_end(struct vo *vo) +{ + struct vo_internal *in = vo->in; + if (!in->current_frame) + return -1; + return in->current_frame->pts + MPMAX(in->current_frame->duration, 0); +} + +static bool still_displaying(struct vo *vo) +{ + struct vo_internal *in = vo->in; + bool working = in->rendering || in->frame_queued; + if (working) + goto done; + + int64_t frame_end = get_current_frame_end(vo); + if (frame_end < 0) + goto done; + working = mp_time_ns() < frame_end; + +done: + return working && in->hasframe; +} + +// Return true if there is still a frame being displayed (or queued). +bool vo_still_displaying(struct vo *vo) +{ + mp_mutex_lock(&vo->in->lock); + bool res = still_displaying(vo); + mp_mutex_unlock(&vo->in->lock); + return res; +} + +// Make vo issue a wakeup once vo_still_displaying() becomes false. +void vo_request_wakeup_on_done(struct vo *vo) +{ + struct vo_internal *in = vo->in; + mp_mutex_lock(&vo->in->lock); + if (still_displaying(vo)) { + in->wakeup_on_done = true; + } else { + wakeup_core(vo); + } + mp_mutex_unlock(&vo->in->lock); +} + // Whether vo_queue_frame() can be called. If the VO is not ready yet, the // function will return false, and the VO will call the wakeup callback once // it's ready. @@ -923,6 +954,7 @@ static bool render_frame(struct vo *vo) if (in->dropped_frame) { in->drop_count += 1; + wakeup_core(vo); } else { in->rendering = true; in->hasframe_rendered = true; @@ -994,10 +1026,9 @@ static bool render_frame(struct vo *vo) more_frames = true; mp_cond_broadcast(&in->wakeup); // for vo_wait_frame() - wakeup_core(vo); done: - if (!vo->driver->frame_owner) + if (!vo->driver->frame_owner || in->dropped_frame) talloc_free(frame); mp_mutex_unlock(&in->lock); @@ -1074,6 +1105,8 @@ static MP_THREAD_VOID vo_thread(void *ptr) bool working = render_frame(vo); int64_t now = mp_time_ns(); int64_t wait_until = now + MP_TIME_S_TO_NS(working ? 0 : 1000); + bool wakeup_on_done = false; + int64_t wakeup_core_after = 0; mp_mutex_lock(&in->lock); if (in->wakeup_pts) { @@ -1088,6 +1121,14 @@ static MP_THREAD_VOID vo_thread(void *ptr) in->want_redraw = true; wakeup_core(vo); } + if ((!working && !in->rendering && !in->frame_queued) && in->wakeup_on_done) { + // At this point we know VO is going to sleep + int64_t frame_end = get_current_frame_end(vo); + if (frame_end >= 0) + wakeup_core_after = frame_end; + wakeup_on_done = true; + in->wakeup_on_done = false; + } vo->want_redraw = false; bool redraw = in->request_redraw; bool send_reset = in->send_reset; @@ -1110,6 +1151,17 @@ static MP_THREAD_VOID vo_thread(void *ptr) if (wait_until <= now) continue; + if (wakeup_on_done) { + // At this point wait_until should be longer than frame duration + if (wakeup_core_after >= 0 && wait_until >= wakeup_core_after) { + wait_vo(vo, wakeup_core_after); + mp_mutex_lock(&in->lock); + in->need_wakeup = true; + mp_mutex_unlock(&in->lock); + } + wakeup_core(vo); + } + wait_vo(vo, wait_until); } forget_frames(vo); // implicitly synchronized @@ -1185,17 +1237,6 @@ void vo_seek_reset(struct vo *vo) mp_mutex_unlock(&in->lock); } -// Return true if there is still a frame being displayed (or queued). -// If this returns true, a wakeup some time in the future is guaranteed. -bool vo_still_displaying(struct vo *vo) -{ - struct vo_internal *in = vo->in; - mp_mutex_lock(&in->lock); - bool working = in->rendering || in->frame_queued; - mp_mutex_unlock(&in->lock); - return working && in->hasframe; -} - // Whether at least 1 frame was queued or rendered since last seek or reconfig. bool vo_has_frame(struct vo *vo) { @@ -1433,9 +1474,19 @@ int lookup_keymap_table(const struct mp_keymap *map, int key) struct mp_image_params vo_get_current_params(struct vo *vo) { struct mp_image_params p = {0}; - mp_mutex_lock(&vo->in->lock); + mp_mutex_lock(&vo->params_mutex); if (vo->params) p = *vo->params; - mp_mutex_unlock(&vo->in->lock); + mp_mutex_unlock(&vo->params_mutex); + return p; +} + +struct mp_image_params vo_get_target_params(struct vo *vo) +{ + struct mp_image_params p = {0}; + mp_mutex_lock(&vo->params_mutex); + if (vo->target_params) + p = *vo->target_params; + mp_mutex_unlock(&vo->params_mutex); return p; } diff --git a/video/out/vo.h b/video/out/vo.h index e38dcf8..313c08a 100644 --- a/video/out/vo.h +++ b/video/out/vo.h @@ -29,6 +29,7 @@ #include "video/img_format.h" #include "common/common.h" #include "options/options.h" +#include "osdep/threads.h" enum { // VO needs to redraw @@ -66,7 +67,6 @@ enum mp_voctrl { VOCTRL_RESUME, VOCTRL_SET_PANSCAN, - VOCTRL_SET_EQUALIZER, // Triggered by any change to mp_vo_opts. This is for convenience. In theory, // you could install your own listener. @@ -122,6 +122,13 @@ enum mp_voctrl { /* private to vo_gpu and vo_gpu_next */ VOCTRL_EXTERNAL_RESIZE, + + // Begin VO dragging. + VOCTRL_BEGIN_DRAGGING, + + // Native context menu + VOCTRL_SHOW_MENU, + VOCTRL_UPDATE_MENU, }; // Helper to expose what kind of content is currently playing to the VO. @@ -210,7 +217,7 @@ struct vo_frame { // If 0, present immediately. int64_t pts; // Approximate frame duration, in ns. - int duration; + double duration; // Realtime of estimated distance between 2 vsync events. double vsync_interval; // "ideal" display time within the vsync @@ -470,7 +477,17 @@ struct vo { // be accessed unsynchronized (read-only). int config_ok; // Last config call was successful? - struct mp_image_params *params; // Configured parameters (as in vo_reconfig) + + // --- The following fields are synchronized by params_mutex, most of + // the params are set only in the vo_reconfig and safe to read + // unsynchronized. Some of the parameters are updated in draw_frame, + // which are still safe to read in the play loop, but for correctness + // generic getter is protected by params_mutex. + mp_mutex params_mutex; + // Configured parameters (changed in vo_reconfig) + struct mp_image_params *params; + // Target display parameters (VO is responsible for re-/setting) + struct mp_image_params *target_params; // --- The following fields can be accessed only by the VO thread, or from // anywhere _if_ the VO thread is suspended (use vo->dispatch). @@ -486,6 +503,9 @@ struct vo { int dwidth; int dheight; float monitor_par; + + // current GPU context (--vo=gpu and --vo=gpu-next only) + const char *context_name; }; struct mpv_global; @@ -499,6 +519,7 @@ bool vo_is_ready_for_frame(struct vo *vo, int64_t next_pts); void vo_queue_frame(struct vo *vo, struct vo_frame *frame); void vo_wait_frame(struct vo *vo); bool vo_still_displaying(struct vo *vo); +void vo_request_wakeup_on_done(struct vo *vo); bool vo_has_frame(struct vo *vo); void vo_redraw(struct vo *vo); bool vo_want_redraw(struct vo *vo); @@ -540,5 +561,6 @@ void vo_get_src_dst_rects(struct vo *vo, struct mp_rect *out_src, struct vo_frame *vo_frame_ref(struct vo_frame *frame); struct mp_image_params vo_get_current_params(struct vo *vo); +struct mp_image_params vo_get_target_params(struct vo *vo); #endif /* MPLAYER_VIDEO_OUT_H */ diff --git a/video/out/vo_direct3d.c b/video/out/vo_direct3d.c index 16936bb..91e962f 100644 --- a/video/out/vo_direct3d.c +++ b/video/out/vo_direct3d.c @@ -102,6 +102,7 @@ typedef struct d3d_priv { struct mp_osd_res osd_res; int image_format; /**< mplayer image format */ struct mp_image_params params; + struct mp_image_params dst_params; D3DFORMAT movie_src_fmt; /**< Movie colorspace format (depends on the movie's codec) */ @@ -896,6 +897,18 @@ static int reconfig(struct vo *vo, struct mp_image_params *params) if (!resize_d3d(priv)) return VO_ERROR; + priv->dst_params = *params; + for (const struct fmt_entry *cur = &fmt_table[0]; cur->mplayer_fmt; ++cur) { + if (cur->fourcc == priv->desktop_fmt) { + priv->dst_params.imgfmt = cur->mplayer_fmt; + break; + } + } + mp_image_params_guess_csp(&priv->dst_params); + mp_mutex_lock(&vo->params_mutex); + vo->target_params = &priv->dst_params; + mp_mutex_unlock(&vo->params_mutex); + return 0; /* Success */ } diff --git a/video/out/vo_dmabuf_wayland.c b/video/out/vo_dmabuf_wayland.c index e04ff5d..35a4dac 100644 --- a/video/out/vo_dmabuf_wayland.c +++ b/video/out/vo_dmabuf_wayland.c @@ -137,10 +137,10 @@ static const struct wl_buffer_listener osd_buffer_listener = { }; #if HAVE_VAAPI -static void close_file_descriptors(VADRMPRIMESurfaceDescriptor desc) +static void close_file_descriptors(const VADRMPRIMESurfaceDescriptor *desc) { - for (int i = 0; i < desc.num_objects; i++) - close(desc.objects[i].fd); + for (int i = 0; i < desc->num_objects; i++) + close(desc->objects[i].fd); } #endif @@ -175,7 +175,7 @@ static bool vaapi_drm_format(struct vo *vo, struct mp_image *src) p->drm_modifier = desc.objects[0].drm_format_modifier; format = true; done: - close_file_descriptors(desc); + close_file_descriptors(&desc); #endif return format; } @@ -216,7 +216,7 @@ static void vaapi_dmabuf_importer(struct buffer *buf, struct mp_image *src, } done: - close_file_descriptors(desc); + close_file_descriptors(&desc); #endif } @@ -493,7 +493,7 @@ static void set_viewport_source(struct vo *vo, struct mp_rect src) if (p->force_window) return; - if (wl->video_viewport && !mp_rect_equals(&p->src, &src)) { + if (!mp_rect_equals(&p->src, &src)) { wp_viewport_set_source(wl->video_viewport, src.x0 << 8, src.y0 << 8, mp_rect_w(src) << 8, mp_rect_h(src) << 8); @@ -528,15 +528,18 @@ static void resize(struct vo *vo) vo_get_src_dst_rects(vo, &src, &dst, &p->screen_osd_res); int window_w = p->screen_osd_res.ml + p->screen_osd_res.mr + mp_rect_w(dst); int window_h = p->screen_osd_res.mt + p->screen_osd_res.mb + mp_rect_h(dst); - wp_viewport_set_destination(wl->viewport, window_w, window_h); + wp_viewport_set_destination(wl->viewport, lround(window_w / wl->scaling), + lround(window_h / wl->scaling)); //now we restore pan for video viewport calculation vo->opts->pan_x = vo_opts->pan_x; vo->opts->pan_y = vo_opts->pan_y; vo_get_src_dst_rects(vo, &src, &dst, &p->screen_osd_res); - wp_viewport_set_destination(wl->video_viewport, mp_rect_w(dst), mp_rect_h(dst)); + wp_viewport_set_destination(wl->video_viewport, lround(mp_rect_w(dst) / wl->scaling), + lround(mp_rect_h(dst) / wl->scaling)); wl_subsurface_set_position(wl->video_subsurface, dst.x0, dst.y0); - wp_viewport_set_destination(wl->osd_viewport, vo->dwidth, vo->dheight); + wp_viewport_set_destination(wl->osd_viewport, lround(vo->dwidth / wl->scaling), + lround(vo->dheight / wl->scaling)); wl_subsurface_set_position(wl->osd_subsurface, 0 - dst.x0, 0 - dst.y0); set_viewport_source(vo, src); } @@ -692,10 +695,7 @@ done: if (!vo_wayland_reconfig(vo)) return VO_ERROR; - // mpv rotates clockwise but the wayland spec has counter-clockwise rotations - // swap 1 and 3 to match mpv's direction - int transform = (360 - img->params.rotate) % 360 / 90; - wl_surface_set_buffer_transform(vo->wl->video_surface, transform); + wl_surface_set_buffer_transform(vo->wl->video_surface, img->params.rotate / 90); // Immediately destroy all buffers if params change. destroy_buffers(vo); @@ -781,12 +781,6 @@ static int preinit(struct vo *vo) goto err; } - if (!vo->wl->viewport) { - MP_FATAL(vo->wl, "Compositor doesn't support the %s protocol!\n", - wp_viewporter_interface.name); - goto err; - } - if (vo->wl->single_pixel_manager) { #if HAVE_WAYLAND_PROTOCOLS_1_27 p->solid_buffer = wp_single_pixel_buffer_manager_v1_create_u32_rgba_buffer( diff --git a/video/out/vo_drm.c b/video/out/vo_drm.c index aae73f7..34726a3 100644 --- a/video/out/vo_drm.c +++ b/video/out/vo_drm.c @@ -24,7 +24,6 @@ #include <unistd.h> #include <drm_fourcc.h> -#include <libswscale/swscale.h> #include "common/msg.h" #include "drm_atomic.h" @@ -38,11 +37,12 @@ #include "vo.h" #define IMGFMT_XRGB8888 IMGFMT_BGR0 -#if BYTE_ORDER == BIG_ENDIAN -#define IMGFMT_XRGB2101010 pixfmt2imgfmt(AV_PIX_FMT_GBRP10BE) -#else -#define IMGFMT_XRGB2101010 pixfmt2imgfmt(AV_PIX_FMT_GBRP10LE) -#endif +#define IMGFMT_XBGR8888 IMGFMT_RGB0 +#define IMGFMT_XRGB2101010 \ + pixfmt2imgfmt(MP_SELECT_LE_BE(AV_PIX_FMT_X2RGB10LE, AV_PIX_FMT_X2RGB10BE)) +#define IMGFMT_XBGR2101010 \ + pixfmt2imgfmt(MP_SELECT_LE_BE(AV_PIX_FMT_X2BGR10LE, AV_PIX_FMT_X2BGR10BE)) +#define IMGFMT_YUYV pixfmt2imgfmt(AV_PIX_FMT_YUYV422) #define BYTES_PER_PIXEL 4 #define BITS_PER_PIXEL 32 @@ -118,12 +118,31 @@ static struct framebuffer *setup_framebuffer(struct vo *vo) fb->handle = creq.handle; // select format - if (drm->opts->drm_format == DRM_OPTS_FORMAT_XRGB2101010) { + switch (drm->opts->drm_format) { + case DRM_OPTS_FORMAT_XRGB2101010: p->drm_format = DRM_FORMAT_XRGB2101010; p->imgfmt = IMGFMT_XRGB2101010; - } else { - p->drm_format = DRM_FORMAT_XRGB8888;; + break; + case DRM_OPTS_FORMAT_XBGR2101010: + p->drm_format = DRM_FORMAT_XRGB2101010; + p->imgfmt = IMGFMT_XRGB2101010; + break; + case DRM_OPTS_FORMAT_XBGR8888: + p->drm_format = DRM_FORMAT_XBGR8888; + p->imgfmt = IMGFMT_XBGR8888; + break; + case DRM_OPTS_FORMAT_YUYV: + p->drm_format = DRM_FORMAT_YUYV; + p->imgfmt = IMGFMT_YUYV; + break; + default: + if (drm->opts->drm_format != DRM_OPTS_FORMAT_XRGB8888) { + MP_VERBOSE(vo, "Requested format not supported by VO, " + "falling back to xrgb8888\n"); + } + p->drm_format = DRM_FORMAT_XRGB8888; p->imgfmt = IMGFMT_XRGB8888; + break; } // create framebuffer object for the dumb-buffer @@ -172,14 +191,15 @@ static int reconfig(struct vo *vo, struct mp_image_params *params) vo->dheight = drm->fb->height; vo_get_src_dst_rects(vo, &p->src, &p->dst, &p->osd); - int w = p->dst.x1 - p->dst.x0; - int h = p->dst.y1 - p->dst.y0; + struct mp_imgfmt_desc fmt = mp_imgfmt_get_desc(p->imgfmt); + p->dst.x0 = MP_ALIGN_DOWN(p->dst.x0, fmt.align_x); + p->dst.y0 = MP_ALIGN_DOWN(p->dst.y0, fmt.align_y); p->sws->src = *params; p->sws->dst = (struct mp_image_params) { .imgfmt = p->imgfmt, - .w = w, - .h = h, + .w = mp_rect_w(p->dst), + .h = mp_rect_h(p->dst), .p_w = 1, .p_h = 1, }; @@ -200,6 +220,9 @@ static int reconfig(struct vo *vo, struct mp_image_params *params) if (mp_sws_reinit(p->sws) < 0) return -1; + mp_mutex_lock(&vo->params_mutex); + vo->target_params = &p->sws->dst; // essentially constant, so this is okay + mp_mutex_unlock(&vo->params_mutex); vo->want_redraw = true; return 0; } @@ -239,35 +262,10 @@ static void draw_image(struct vo *vo, mp_image_t *mpi, struct framebuffer *buf) osd_draw_on_image(vo->osd, p->osd, 0, 0, p->cur_frame); } - if (p->drm_format == DRM_FORMAT_XRGB2101010) { - // Pack GBRP10 image into XRGB2101010 for DRM - const int w = p->cur_frame->w; - const int h = p->cur_frame->h; - - const int g_padding = p->cur_frame->stride[0]/sizeof(uint16_t) - w; - const int b_padding = p->cur_frame->stride[1]/sizeof(uint16_t) - w; - const int r_padding = p->cur_frame->stride[2]/sizeof(uint16_t) - w; - const int fbuf_padding = buf->stride/sizeof(uint32_t) - w; - - uint16_t *g_ptr = (uint16_t*)p->cur_frame->planes[0]; - uint16_t *b_ptr = (uint16_t*)p->cur_frame->planes[1]; - uint16_t *r_ptr = (uint16_t*)p->cur_frame->planes[2]; - uint32_t *fbuf_ptr = (uint32_t*)buf->map; - for (unsigned y = 0; y < h; ++y) { - for (unsigned x = 0; x < w; ++x) { - *fbuf_ptr++ = (*r_ptr++ << 20) | (*g_ptr++ << 10) | (*b_ptr++); - } - g_ptr += g_padding; - b_ptr += b_padding; - r_ptr += r_padding; - fbuf_ptr += fbuf_padding; - } - } else { // p->drm_format == DRM_FORMAT_XRGB8888 - memcpy_pic(buf->map, p->cur_frame->planes[0], - p->cur_frame->w * BYTES_PER_PIXEL, p->cur_frame->h, - buf->stride, - p->cur_frame->stride[0]); - } + memcpy_pic(buf->map, p->cur_frame->planes[0], + p->cur_frame->w * BYTES_PER_PIXEL, p->cur_frame->h, + buf->stride, + p->cur_frame->stride[0]); } if (mpi != p->last_input) { @@ -423,7 +421,8 @@ err: static int query_format(struct vo *vo, int format) { - return sws_isSupportedInput(imgfmt2pixfmt(format)); + struct priv *p = vo->priv; + return mp_sws_supports_formats(p->sws, p->imgfmt, format) ? 1 : 0; } static int control(struct vo *vo, uint32_t request, void *arg) diff --git a/video/out/vo_gpu.c b/video/out/vo_gpu.c index c02e6e7..d49a6ba 100644 --- a/video/out/vo_gpu.c +++ b/video/out/vo_gpu.c @@ -80,11 +80,16 @@ static void draw_frame(struct vo *vo, struct vo_frame *frame) if (!sw->fns->start_frame(sw, &fbo)) return; - gl_video_render_frame(p->renderer, frame, fbo, RENDER_FRAME_DEF); + gl_video_render_frame(p->renderer, frame, &fbo, RENDER_FRAME_DEF); if (!sw->fns->submit_frame(sw, frame)) { MP_ERR(vo, "Failed presenting frame!\n"); return; } + + struct mp_image_params *params = gl_video_get_target_params_ptr(p->renderer); + mp_mutex_lock(&vo->params_mutex); + vo->target_params = params; + mp_mutex_unlock(&vo->params_mutex); } static void flip_page(struct vo *vo) @@ -169,13 +174,14 @@ static void get_and_update_ambient_lighting(struct gpu_priv *p) } } -static void update_ra_ctx_options(struct vo *vo) +static void update_ra_ctx_options(struct vo *vo, struct ra_ctx_opts *ctx_opts) { struct gpu_priv *p = vo->priv; - - /* Only the alpha option has any runtime toggle ability. */ struct gl_video_opts *gl_opts = mp_get_config_group(p->ctx, vo->global, &gl_video_conf); - p->ctx->opts.want_alpha = gl_opts->alpha_mode == 1; + ctx_opts->want_alpha = (gl_opts->background == BACKGROUND_COLOR && + gl_opts->background_color.a != 255) || + gl_opts->background == BACKGROUND_NONE; + talloc_free(gl_opts); } static int control(struct vo *vo, uint32_t request, void *data) @@ -186,9 +192,6 @@ static int control(struct vo *vo, uint32_t request, void *data) case VOCTRL_SET_PANSCAN: resize(vo); return VO_TRUE; - case VOCTRL_SET_EQUALIZER: - vo->want_redraw = true; - return VO_TRUE; case VOCTRL_SCREENSHOT: { struct vo_frame *frame = vo_get_current_vo_frame(vo); if (frame) @@ -200,12 +203,14 @@ static int control(struct vo *vo, uint32_t request, void *data) request_hwdec_api(vo, data); return true; case VOCTRL_UPDATE_RENDER_OPTS: { - update_ra_ctx_options(vo); + struct ra_ctx_opts *ctx_opts = mp_get_config_group(vo, vo->global, &ra_ctx_conf); + update_ra_ctx_options(vo, ctx_opts); gl_video_configure_queue(p->renderer, vo); get_and_update_icc_profile(p); if (p->ctx->fns->update_render_opts) p->ctx->fns->update_render_opts(p->ctx); vo->want_redraw = true; + talloc_free(ctx_opts); return true; } case VOCTRL_RESET: @@ -288,12 +293,9 @@ static int preinit(struct vo *vo) p->log = vo->log; struct ra_ctx_opts *ctx_opts = mp_get_config_group(vo, vo->global, &ra_ctx_conf); - struct gl_video_opts *gl_opts = mp_get_config_group(vo, vo->global, &gl_video_conf); - struct ra_ctx_opts opts = *ctx_opts; - opts.want_alpha = gl_opts->alpha_mode == 1; - p->ctx = ra_ctx_create(vo, opts); + update_ra_ctx_options(vo, ctx_opts); + p->ctx = ra_ctx_create(vo, *ctx_opts); talloc_free(ctx_opts); - talloc_free(gl_opts); if (!p->ctx) goto err_out; assert(p->ctx->ra); diff --git a/video/out/vo_gpu_next.c b/video/out/vo_gpu_next.c index 1dc1b18..0a93f63 100644 --- a/video/out/vo_gpu_next.c +++ b/video/out/vo_gpu_next.c @@ -17,6 +17,9 @@ * License along with mpv. If not, see <http://www.gnu.org/licenses/>. */ +#include <dirent.h> +#include <sys/stat.h> +#include <time.h> #include <unistd.h> #include <libplacebo/colorspace.h> @@ -29,7 +32,9 @@ #include "config.h" #include "common/common.h" +#include "misc/io_utils.h" #include "options/m_config.h" +#include "options/options.h" #include "options/path.h" #include "osdep/io.h" #include "osdep/threads.h" @@ -90,9 +95,12 @@ struct frame_info { }; struct cache { - char *path; + struct mp_log *log; + struct mpv_global *global; + char *dir; + const char *name; + size_t size_limit; pl_cache cache; - uint64_t sig; }; struct priv { @@ -131,21 +139,18 @@ struct priv { pl_options pars; struct m_config_cache *opts_cache; + struct m_config_cache *next_opts_cache; + struct gl_next_opts *next_opts; struct cache shader_cache, icc_cache; struct mp_csp_equalizer_state *video_eq; struct scaler_params scalers[SCALER_COUNT]; const struct pl_hook **hooks; // storage for `params.hooks` - enum mp_csp_levels output_levels; - char **raw_opts; + enum pl_color_levels output_levels; struct pl_icc_params icc_params; char *icc_path; pl_icc_object icc_profile; - struct user_lut image_lut; - struct user_lut target_lut; - struct user_lut lut; - // Cached shaders, preserved across options updates struct user_hook *user_hooks; int num_user_hooks; @@ -154,15 +159,59 @@ struct priv { struct frame_info perf_fresh; struct frame_info perf_redraw; + struct mp_image_params target_params; +}; + +static void update_render_options(struct vo *vo); +static void update_lut(struct priv *p, struct user_lut *lut); + +struct gl_next_opts { bool delayed_peak; + int border_background; + float corner_rounding; bool inter_preserve; + struct user_lut lut; + struct user_lut image_lut; + struct user_lut target_lut; bool target_hint; + char **raw_opts; +}; - float corner_rounding; +const struct m_opt_choice_alternatives lut_types[] = { + {"auto", PL_LUT_UNKNOWN}, + {"native", PL_LUT_NATIVE}, + {"normalized", PL_LUT_NORMALIZED}, + {"conversion", PL_LUT_CONVERSION}, + {0} }; -static void update_render_options(struct vo *vo); -static void update_lut(struct priv *p, struct user_lut *lut); +#define OPT_BASE_STRUCT struct gl_next_opts +const struct m_sub_options gl_next_conf = { + .opts = (const struct m_option[]) { + {"allow-delayed-peak-detect", OPT_BOOL(delayed_peak)}, + {"border-background", OPT_CHOICE(border_background, + {"none", BACKGROUND_NONE}, + {"color", BACKGROUND_COLOR}, + {"tiles", BACKGROUND_TILES})}, + {"corner-rounding", OPT_FLOAT(corner_rounding), M_RANGE(0, 1)}, + {"interpolation-preserve", OPT_BOOL(inter_preserve)}, + {"lut", OPT_STRING(lut.opt), .flags = M_OPT_FILE}, + {"lut-type", OPT_CHOICE_C(lut.type, lut_types)}, + {"image-lut", OPT_STRING(image_lut.opt), .flags = M_OPT_FILE}, + {"image-lut-type", OPT_CHOICE_C(image_lut.type, lut_types)}, + {"target-lut", OPT_STRING(target_lut.opt), .flags = M_OPT_FILE}, + {"target-colorspace-hint", OPT_BOOL(target_hint)}, + // No `target-lut-type` because we don't support non-RGB targets + {"libplacebo-opts", OPT_KEYVALUELIST(raw_opts)}, + {0}, + }, + .defaults = &(struct gl_next_opts) { + .border_background = BACKGROUND_COLOR, + .inter_preserve = true, + }, + .size = sizeof(struct gl_next_opts), + .change_flags = UPDATE_VIDEO, +}; static pl_buf get_dr_buf(struct priv *p, const uint8_t *ptr) { @@ -238,8 +287,6 @@ static struct mp_image *get_image(struct vo *vo, int imgfmt, int w, int h, return mpi; } -static struct pl_color_space get_mpi_csp(struct vo *vo, struct mp_image *mpi); - static void update_overlays(struct vo *vo, struct mp_osd_res res, int flags, enum pl_overlay_coords coords, struct osd_state *state, struct pl_frame *frame, @@ -290,6 +337,8 @@ static void update_overlays(struct vo *vo, struct mp_osd_res res, entry->num_parts = 0; for (int i = 0; i < item->num_parts; i++) { const struct sub_bitmap *b = &item->parts[i]; + if (b->dw == 0 || b->dh == 0) + continue; uint32_t c = b->libass.color; struct pl_overlay_part part = { .src = { b->src_x, b->src_y, b->src_x + b->w, b->src_y + b->h }, @@ -322,7 +371,7 @@ static void update_overlays(struct vo *vo, struct mp_osd_res res, ol->repr.alpha = PL_ALPHA_PREMULTIPLIED; // Infer bitmap colorspace from source if (src) { - ol->color = get_mpi_csp(vo, src); + ol->color = src->params.color; // Seems like HDR subtitles are targeting SDR white if (pl_color_transfer_is_hdr(ol->color.transfer)) { ol->color.hdr = (struct pl_hdr_metadata) { @@ -332,6 +381,8 @@ static void update_overlays(struct vo *vo, struct mp_osd_res res, } break; case SUBBITMAP_LIBASS: + if (src && item->video_color_space && !pl_color_space_is_hdr(&src->params.color)) + ol->color = src->params.color; ol->mode = PL_OVERLAY_MONOCHROME; ol->repr.alpha = PL_ALPHA_INDEPENDENT; break; @@ -441,21 +492,15 @@ static int plane_data_from_imgfmt(struct pl_plane_data out_data[4], return desc.num_planes; } -static struct pl_color_space get_mpi_csp(struct vo *vo, struct mp_image *mpi) -{ - struct pl_color_space csp = { - .primaries = mp_prim_to_pl(mpi->params.color.primaries), - .transfer = mp_trc_to_pl(mpi->params.color.gamma), - .hdr = mpi->params.color.hdr, - }; - return csp; -} - static bool hwdec_reconfig(struct priv *p, struct ra_hwdec *hwdec, const struct mp_image_params *par) { if (p->hwdec_mapper) { - if (mp_image_params_equal(par, &p->hwdec_mapper->src_params)) { + if (mp_image_params_static_equal(par, &p->hwdec_mapper->src_params)) { + p->hwdec_mapper->src_params.repr.dovi = par->repr.dovi; + p->hwdec_mapper->dst_params.repr.dovi = par->repr.dovi; + p->hwdec_mapper->src_params.color.hdr = par->color.hdr; + p->hwdec_mapper->dst_params.color.hdr = par->color.hdr; return p->hwdec_mapper; } else { ra_hwdec_mapper_free(&p->hwdec_mapper); @@ -571,12 +616,8 @@ static bool map_frame(pl_gpu gpu, pl_tex *tex, const struct pl_source_frame *src } *frame = (struct pl_frame) { - .color = get_mpi_csp(vo, mpi), - .repr = { - .sys = mp_csp_to_pl(par->color.space), - .levels = mp_levels_to_pl(par->color.levels), - .alpha = mp_alpha_to_pl(par->alpha), - }, + .color = par->color, + .repr = par->repr, .profile = { .data = mpi->icc_profile ? mpi->icc_profile->data : NULL, .len = mpi->icc_profile ? mpi->icc_profile->size : 0, @@ -588,14 +629,14 @@ static bool map_frame(pl_gpu gpu, pl_tex *tex, const struct pl_source_frame *src // mp_image, like AVFrame, likes communicating RGB/XYZ/YCbCr status // implicitly via the image format, rather than the actual tagging. switch (mp_imgfmt_get_forced_csp(par->imgfmt)) { - case MP_CSP_RGB: + case PL_COLOR_SYSTEM_RGB: frame->repr.sys = PL_COLOR_SYSTEM_RGB; frame->repr.levels = PL_COLOR_LEVELS_FULL; break; - case MP_CSP_XYZ: + case PL_COLOR_SYSTEM_XYZ: frame->repr.sys = PL_COLOR_SYSTEM_XYZ; break; - case MP_CSP_AUTO: + case PL_COLOR_SYSTEM_UNKNOWN: if (!frame->repr.sys) frame->repr.sys = pl_color_system_guess_ycbcr(par->w, par->h); break; @@ -664,10 +705,7 @@ static bool map_frame(pl_gpu gpu, pl_tex *tex, const struct pl_source_frame *src } // Update chroma location, must be done after initializing planes - pl_frame_set_chroma_location(frame, mp_chroma_to_pl(par->chroma_location)); - - // Set the frame DOVI metadata - mp_map_dovi_metadata_to_pl(mpi, frame); + pl_frame_set_chroma_location(frame, par->chroma_location); if (mpi->film_grain) pl_film_grain_from_av(&frame->film_grain, (AVFilmGrainParams *) mpi->film_grain->data); @@ -679,9 +717,9 @@ static bool map_frame(pl_gpu gpu, pl_tex *tex, const struct pl_source_frame *src pl_icc_profile_compute_signature(&frame->profile); // Update LUT attached to this frame - update_lut(p, &p->image_lut); - frame->lut = p->image_lut.lut; - frame->lut_type = p->image_lut.type; + update_lut(p, &p->next_opts->image_lut); + frame->lut = p->next_opts->image_lut.lut; + frame->lut_type = p->next_opts->image_lut.type; return true; } @@ -727,12 +765,14 @@ static void update_options(struct vo *vo) { struct priv *p = vo->priv; pl_options pars = p->pars; - if (m_config_cache_update(p->opts_cache)) + bool changed = m_config_cache_update(p->opts_cache); + changed = m_config_cache_update(p->next_opts_cache) || changed; + if (changed) update_render_options(vo); - update_lut(p, &p->lut); - pars->params.lut = p->lut.lut; - pars->params.lut_type = p->lut.type; + update_lut(p, &p->next_opts->lut); + pars->params.lut = p->next_opts->lut.lut; + pars->params.lut_type = p->next_opts->lut.type; // Update equalizer state struct mp_csp_params cparams = MP_CSP_PARAMS_DEFAULTS; @@ -744,7 +784,7 @@ static void update_options(struct vo *vo) pars->color_adjustment.gamma = cparams.gamma; p->output_levels = cparams.levels_out; - for (char **kv = p->raw_opts; kv && kv[0]; kv += 2) + for (char **kv = p->next_opts->raw_opts; kv && kv[0]; kv += 2) pl_options_set_str(pars, kv[0], kv[1]); } @@ -776,18 +816,18 @@ static void apply_target_contrast(struct priv *p, struct pl_color_space *color) static void apply_target_options(struct priv *p, struct pl_frame *target) { - update_lut(p, &p->target_lut); - target->lut = p->target_lut.lut; - target->lut_type = p->target_lut.type; + update_lut(p, &p->next_opts->target_lut); + target->lut = p->next_opts->target_lut.lut; + target->lut_type = p->next_opts->target_lut.type; // Colorspace overrides const struct gl_video_opts *opts = p->opts_cache->opts; if (p->output_levels) - target->repr.levels = mp_levels_to_pl(p->output_levels); + target->repr.levels = p->output_levels; if (opts->target_prim) - target->color.primaries = mp_prim_to_pl(opts->target_prim); + target->color.primaries = opts->target_prim; if (opts->target_trc) - target->color.transfer = mp_trc_to_pl(opts->target_trc); + target->color.transfer = opts->target_trc; // If swapchain returned a value use this, override is used in hint if (opts->target_peak && !target->color.hdr.max_luma) target->color.hdr.max_luma = opts->target_peak; @@ -796,14 +836,23 @@ static void apply_target_options(struct priv *p, struct pl_frame *target) if (opts->target_gamut) { // Ensure resulting gamut still fits inside container const struct pl_raw_primaries *gamut, *container; - gamut = pl_raw_primaries_get(mp_prim_to_pl(opts->target_gamut)); + gamut = pl_raw_primaries_get(opts->target_gamut); container = pl_raw_primaries_get(target->color.primaries); target->color.hdr.prim = pl_primaries_clip(gamut, container); } - if (opts->dither_depth > 0) { + int dither_depth = opts->dither_depth; + if (dither_depth == 0) { + struct ra_swapchain *sw = p->ra_ctx->swapchain; + if (sw->fns->color_depth) { + dither_depth = sw->fns->color_depth(sw); + } else if (!pl_color_transfer_is_hdr(target->color.transfer)) { + dither_depth = 8; + } + } + if (dither_depth > 0) { struct pl_bit_encoding *tbits = &target->repr.bits; - tbits->color_depth += opts->dither_depth - tbits->sample_depth; - tbits->sample_depth = opts->dither_depth; + tbits->color_depth += dither_depth - tbits->sample_depth; + tbits->sample_depth = dither_depth; } if (opts->icc_opts->icc_use_luma) { @@ -882,7 +931,7 @@ static void draw_frame(struct vo *vo, struct vo_frame *frame) params.info_callback = info_callback; params.info_priv = vo; params.skip_caching_single_frame = !cache_frame; - params.preserve_mixing_cache = p->inter_preserve && !frame->still; + params.preserve_mixing_cache = p->next_opts->inter_preserve && !frame->still; if (frame->still) params.frame_mixer = NULL; @@ -939,17 +988,17 @@ static void draw_frame(struct vo *vo, struct vo_frame *frame) p->last_id = id; } - if (p->target_hint && frame->current) { - struct pl_color_space hint = get_mpi_csp(vo, frame->current); + if (p->next_opts->target_hint && frame->current) { + struct pl_color_space hint = frame->current->params.color; if (opts->target_prim) - hint.primaries = mp_prim_to_pl(opts->target_prim); + hint.primaries = opts->target_prim; if (opts->target_trc) - hint.transfer = mp_trc_to_pl(opts->target_trc); + hint.transfer = opts->target_trc; if (opts->target_peak) hint.hdr.max_luma = opts->target_peak; apply_target_contrast(p, &hint); pl_swapchain_colorspace_hint(p->sw, &hint); - } else if (!p->target_hint) { + } else if (!p->next_opts->target_hint) { pl_swapchain_colorspace_hint(p->sw, NULL); } @@ -959,14 +1008,15 @@ static void draw_frame(struct vo *vo, struct vo_frame *frame) if (!should_draw || !pl_swapchain_start_frame(p->sw, &swframe)) { if (frame->current) { // Advance the queue state to the current PTS to discard unused frames - pl_queue_update(p->queue, NULL, pl_queue_params( + struct pl_queue_params qparams = *pl_queue_params( .pts = frame->current->pts + pts_offset, .radius = pl_frame_mix_radius(¶ms), .vsync_duration = can_interpolate ? frame->ideal_frame_vsync_duration : 0, + ); #if PL_API_VER >= 340 - .drift_compensation = 0, + qparams.drift_compensation = 0; #endif - )); + pl_queue_update(p->queue, NULL, &qparams); } return; } @@ -992,10 +1042,10 @@ static void draw_frame(struct vo *vo, struct vo_frame *frame) .radius = pl_frame_mix_radius(¶ms), .vsync_duration = can_interpolate ? frame->ideal_frame_vsync_duration : 0, .interpolation_threshold = opts->interpolation_threshold, + ); #if PL_API_VER >= 340 - .drift_compensation = 0, + qparams.drift_compensation = 0; #endif - ); // Depending on the vsync ratio, we may be up to half of the vsync // duration before the current frame time. This works fine because @@ -1035,7 +1085,9 @@ static void draw_frame(struct vo *vo, struct vo_frame *frame) struct frame_priv *fp = mpi->priv; apply_crop(image, p->src, vo->params->w, vo->params->h); if (opts->blend_subs) { - if (frame->redraw || fp->osd_sync < p->osd_sync) { + if (frame->redraw) + p->osd_sync++; + if (fp->osd_sync < p->osd_sync) { float rx = pl_rect_w(p->dst) / pl_rect_w(image->crop); float ry = pl_rect_h(p->dst) / pl_rect_h(image->crop); struct mp_osd_res res = { @@ -1047,9 +1099,6 @@ static void draw_frame(struct vo *vo, struct vo_frame *frame) .mb = (image->crop.y1 - vo->params->h) * ry, .display_par = 1.0, }; - // TODO: fix this doing pointless updates - if (frame->redraw) - p->osd_sync++; update_overlays(vo, res, OSD_DRAW_SUB_ONLY, PL_OVERLAY_COORDS_DST_CROP, &fp->subs, image, mpi); @@ -1078,12 +1127,27 @@ static void draw_frame(struct vo *vo, struct vo_frame *frame) goto done; } - const struct pl_frame *cur_frame = pl_frame_mix_nearest(&mix); - if (cur_frame && vo->params) { - vo->params->color.hdr = cur_frame->color.hdr; + struct pl_frame ref_frame; + pl_frames_infer_mix(p->rr, &mix, &target, &ref_frame); + + mp_mutex_lock(&vo->params_mutex); + p->target_params = (struct mp_image_params){ + .imgfmt_name = swframe.fbo->params.format + ? swframe.fbo->params.format->name : NULL, + .w = mp_rect_w(p->dst), + .h = mp_rect_h(p->dst), + .color = target.color, + .repr = target.repr, + .rotate = target.rotation, + }; + vo->target_params = &p->target_params; + + if (vo->params) { + vo->params->color.hdr = ref_frame.color.hdr; // Augment metadata with peak detection max_pq_y / avg_pq_y pl_renderer_get_hdr_metadata(p->rr, &vo->params->color.hdr); } + mp_mutex_unlock(&vo->params_mutex); p->is_interpolated = pts_offset != 0 && mix.num_frames > 1; valid = true; @@ -1168,6 +1232,9 @@ static int reconfig(struct vo *vo, struct mp_image_params *params) return -1; resize(vo); + mp_mutex_lock(&vo->params_mutex); + vo->target_params = NULL; + mp_mutex_unlock(&vo->params_mutex); return 0; } @@ -1236,12 +1303,13 @@ static void video_screenshot(struct vo *vo, struct voctrl_screenshot *args) // Retrieve the current frame from the frame queue struct pl_frame_mix mix; enum pl_queue_status status; - status = pl_queue_update(p->queue, &mix, pl_queue_params( + struct pl_queue_params qparams = *pl_queue_params( .pts = p->last_pts, + ); #if PL_API_VER >= 340 - .drift_compensation = 0, + qparams.drift_compensation = 0; #endif - )); + status = pl_queue_update(p->queue, &mix, &qparams); assert(status != PL_QUEUE_EOF); if (status == PL_QUEUE_ERR) { MP_ERR(vo, "Unknown error occurred while trying to take screenshot!\n"); @@ -1379,9 +1447,9 @@ static void video_screenshot(struct vo *vo, struct voctrl_screenshot *args) if (!args->res) goto done; - args->res->params.color.primaries = mp_prim_from_pl(target.color.primaries); - args->res->params.color.gamma = mp_trc_from_pl(target.color.transfer); - args->res->params.color.levels = mp_levels_from_pl(target.repr.levels); + args->res->params.color.primaries = target.color.primaries; + args->res->params.color.transfer = target.color.transfer; + args->res->params.repr.levels = target.repr.levels; args->res->params.color.hdr = target.color.hdr; if (args->scaled) args->res->params.p_w = args->res->params.p_h = 1; @@ -1424,6 +1492,19 @@ static inline void copy_frame_info_to_mp(struct frame_info *pl, } } +static void update_ra_ctx_options(struct vo *vo, struct ra_ctx_opts *ctx_opts) +{ + struct priv *p = vo->priv; + struct gl_video_opts *gl_opts = p->opts_cache->opts; + bool border_alpha = (p->next_opts->border_background == BACKGROUND_COLOR && + gl_opts->background_color.a != 255) || + p->next_opts->border_background == BACKGROUND_NONE; + ctx_opts->want_alpha = (gl_opts->background == BACKGROUND_COLOR && + gl_opts->background_color.a != 255) || + gl_opts->background == BACKGROUND_NONE || + border_alpha; +} + static int control(struct vo *vo, uint32_t request, void *data) { struct priv *p = vo->priv; @@ -1432,7 +1513,6 @@ static int control(struct vo *vo, uint32_t request, void *data) case VOCTRL_SET_PANSCAN: resize(vo); return VO_TRUE; - case VOCTRL_SET_EQUALIZER: case VOCTRL_PAUSE: if (p->is_interpolated) vo->want_redraw = true; @@ -1440,8 +1520,7 @@ static int control(struct vo *vo, uint32_t request, void *data) case VOCTRL_UPDATE_RENDER_OPTS: { m_config_cache_update(p->opts_cache); - const struct gl_video_opts *opts = p->opts_cache->opts; - p->ra_ctx->opts.want_alpha = opts->alpha_mode == ALPHA_YES; + update_ra_ctx_options(vo, &p->ra_ctx->opts); if (p->ra_ctx->fns->update_render_opts) p->ra_ctx->fns->update_render_opts(p->ra_ctx); update_render_options(vo); @@ -1513,84 +1592,190 @@ static void wait_events(struct vo *vo, int64_t until_time_ns) } } -#if PL_API_VER < 342 -static inline void xor_hash(void *hash, pl_cache_obj obj) +static char *cache_filepath(void *ta_ctx, char *dir, const char *prefix, uint64_t key) { - *((uint64_t *) hash) ^= obj.key; + bstr filename = {0}; + bstr_xappend_asprintf(ta_ctx, &filename, "%s_%016" PRIx64, prefix, key); + return mp_path_join_bstr(ta_ctx, bstr0(dir), filename); } -static inline uint64_t pl_cache_signature(pl_cache cache) +static pl_cache_obj cache_load_obj(void *p, uint64_t key) { - uint64_t hash = 0; - pl_cache_iterate(cache, xor_hash, &hash); - return hash; + struct cache *c = p; + void *ta_ctx = talloc_new(NULL); + pl_cache_obj obj = {0}; + + if (!c->dir) + goto done; + + char *filepath = cache_filepath(ta_ctx, c->dir, c->name, key); + if (!filepath) + goto done; + + if (stat(filepath, &(struct stat){0})) + goto done; + + int64_t load_start = mp_time_ns(); + struct bstr data = stream_read_file(filepath, ta_ctx, c->global, STREAM_MAX_READ_SIZE); + int64_t load_end = mp_time_ns(); + MP_DBG(c, "%s: key(%" PRIx64 "), size(%zu), load time(%.3f ms)\n", + __func__, key, data.len, + MP_TIME_NS_TO_MS(load_end - load_start)); + + obj = (pl_cache_obj){ + .key = key, + .data = talloc_steal(NULL, data.start), + .size = data.len, + .free = talloc_free, + }; + +done: + talloc_free(ta_ctx); + return obj; +} + +static void cache_save_obj(void *p, pl_cache_obj obj) +{ + if (!obj.data || !obj.size) + return; + + const struct cache *c = p; + void *ta_ctx = talloc_new(NULL); + + if (!c->dir) + goto done; + + char *filepath = cache_filepath(ta_ctx, c->dir, c->name, obj.key); + if (!filepath) + goto done; + + // Don't save if already exists + if (!stat(filepath, &(struct stat){0})) { + MP_DBG(c, "%s: key(%"PRIx64"), size(%zu)\n", __func__, obj.key, obj.size); + goto done; + } + + int64_t save_start = mp_time_ns(); + mp_save_to_file(filepath, obj.data, obj.size); + int64_t save_end = mp_time_ns(); + MP_DBG(c, "%s: key(%" PRIx64 "), size(%zu), save time(%.3f ms)\n", + __func__, obj.key, obj.size, + MP_TIME_NS_TO_MS(save_end - save_start)); + +done: + talloc_free(ta_ctx); } -#endif static void cache_init(struct vo *vo, struct cache *cache, size_t max_size, const char *dir_opt) { struct priv *p = vo->priv; - const char *name = cache == &p->shader_cache ? "shader.cache" : "icc.cache"; + const char *name = cache == &p->shader_cache ? "shader" : "icc"; + const size_t limit = cache == &p->shader_cache ? 128 << 20 : 1536 << 20; char *dir; if (dir_opt && dir_opt[0]) { - dir = mp_get_user_path(NULL, p->global, dir_opt); + dir = mp_get_user_path(vo, p->global, dir_opt); } else { - dir = mp_find_user_file(NULL, p->global, "cache", ""); + dir = mp_find_user_file(vo, p->global, "cache", ""); } if (!dir || !dir[0]) - goto done; + return; mp_mkdirp(dir); - cache->path = mp_path_join(vo, dir, name); - cache->cache = pl_cache_create(pl_cache_params( - .log = p->pllog, - .max_total_size = max_size, - )); + *cache = (struct cache){ + .log = p->log, + .global = p->global, + .dir = dir, + .name = name, + .size_limit = limit, + .cache = pl_cache_create(pl_cache_params( + .log = p->pllog, + .get = cache_load_obj, + .set = cache_save_obj, + .priv = cache + )), + }; +} - FILE *file = fopen(cache->path, "rb"); - if (file) { - int ret = pl_cache_load_file(cache->cache, file); - fclose(file); - if (ret < 0) - MP_WARN(p, "Failed loading cache from %s\n", cache->path); - } +struct file_entry { + char *filepath; + size_t size; + time_t atime; +}; - cache->sig = pl_cache_signature(cache->cache); -done: - talloc_free(dir); +static int compare_atime(const void *a, const void *b) +{ + return (((struct file_entry *)b)->atime - ((struct file_entry *)a)->atime); } static void cache_uninit(struct priv *p, struct cache *cache) { if (!cache->cache) - goto done; - if (pl_cache_signature(cache->cache) == cache->sig) - goto done; // skip re-saving identical cache + return; - assert(cache->path); - char *tmp = talloc_asprintf(cache->path, "%sXXXXXX", cache->path); - int fd = mkstemp(tmp); - if (fd < 0) - goto done; - FILE *file = fdopen(fd, "wb"); - if (!file) { - close(fd); - unlink(tmp); + void *ta_ctx = talloc_new(NULL); + struct file_entry *files = NULL; + size_t num_files = 0; + assert(cache->dir); + assert(cache->name); + + DIR *d = opendir(cache->dir); + if (!d) goto done; + + struct dirent *dir; + while ((dir = readdir(d)) != NULL) { + char *filepath = mp_path_join(ta_ctx, cache->dir, dir->d_name); + if (!filepath) + continue; + struct stat filestat; + if (stat(filepath, &filestat)) + continue; + if (!S_ISREG(filestat.st_mode)) + continue; + bstr fname = bstr0(dir->d_name); + if (!bstr_eatstart0(&fname, cache->name)) + continue; + if (!bstr_eatstart0(&fname, "_")) + continue; + if (fname.len != 16) // %016x + continue; + MP_TARRAY_APPEND(ta_ctx, files, num_files, + (struct file_entry){ + .filepath = filepath, + .size = filestat.st_size, + .atime = filestat.st_atime, + }); } - int ret = pl_cache_save_file(cache->cache, file); - fclose(file); - if (ret >= 0) - ret = rename(tmp, cache->path); - if (ret < 0) { - MP_WARN(p, "Failed saving cache to %s\n", cache->path); - unlink(tmp); + closedir(d); + + if (!num_files) + goto done; + + qsort(files, num_files, sizeof(struct file_entry), compare_atime); + + time_t t = time(NULL); + size_t cache_size = 0; + size_t cache_limit = cache->size_limit ? cache->size_limit : SIZE_MAX; + for (int i = 0; i < num_files; i++) { + // Remove files that exceed the size limit but are older than one day. + // This allows for temporary maintaining a larger cache size while + // adjusting the configuration. The cache will be cleared the next day + // for unused entries. We don't need to be overly aggressive with cache + // cleaning; in most cases, it will not grow much, and in others, it may + // actually be useful to cache more. + cache_size += files[i].size; + double rel_use = difftime(t, files[i].atime); + if (cache_size > cache_limit && rel_use > 60 * 60 * 24) { + MP_VERBOSE(p, "Removing %s | size: %9zu bytes | last used: %9d seconds ago\n", + files[i].filepath, files[i].size, (int)rel_use); + unlink(files[i].filepath); + } } - // fall through done: + talloc_free(ta_ctx); pl_cache_destroy(&cache->cache); } @@ -1618,6 +1803,10 @@ static void uninit(struct vo *vo) cache_uninit(p, &p->shader_cache); cache_uninit(p, &p->icc_cache); + pl_lut_free(&p->next_opts->image_lut.lut); + pl_lut_free(&p->next_opts->lut.lut); + pl_lut_free(&p->next_opts->target_lut.lut); + pl_icc_close(&p->icc_profile); pl_renderer_destroy(&p->rr); @@ -1644,12 +1833,17 @@ static int preinit(struct vo *vo) { struct priv *p = vo->priv; p->opts_cache = m_config_cache_alloc(p, vo->global, &gl_video_conf); + p->next_opts_cache = m_config_cache_alloc(p, vo->global, &gl_next_conf); + p->next_opts = p->next_opts_cache->opts; p->video_eq = mp_csp_equalizer_create(p, vo->global); p->global = vo->global; p->log = vo->log; struct gl_video_opts *gl_opts = p->opts_cache->opts; - p->context = gpu_ctx_create(vo, gl_opts); + struct ra_ctx_opts *ctx_opts = mp_get_config_group(vo, vo->global, &ra_ctx_conf); + update_ra_ctx_options(vo, ctx_opts); + p->context = gpu_ctx_create(vo, ctx_opts); + talloc_free(ctx_opts); if (!p->context) goto err_out; // For the time being @@ -1857,6 +2051,7 @@ static void update_lut(struct priv *p, struct user_lut *lut) MP_VERBOSE(p, "Loading custom LUT '%s'\n", fname); struct bstr lutdata = stream_read_file(fname, p, p->global, 100000000); // 100 MB lut->lut = pl_lut_parse_cube(p->pllog, lutdata.start, lutdata.len); + talloc_free(fname); talloc_free(lutdata.start); } @@ -1934,15 +2129,27 @@ static void update_render_options(struct vo *vo) pl_options pars = p->pars; const struct gl_video_opts *opts = p->opts_cache->opts; pars->params.antiringing_strength = opts->scaler[0].antiring; - pars->params.background_color[0] = opts->background.r / 255.0; - pars->params.background_color[1] = opts->background.g / 255.0; - pars->params.background_color[2] = opts->background.b / 255.0; - pars->params.background_transparency = 1.0 - opts->background.a / 255.0; + pars->params.background_color[0] = opts->background_color.r / 255.0; + pars->params.background_color[1] = opts->background_color.g / 255.0; + pars->params.background_color[2] = opts->background_color.b / 255.0; + pars->params.background_transparency = 1 - opts->background_color.a / 255.0; pars->params.skip_anti_aliasing = !opts->correct_downscaling; pars->params.disable_linear_scaling = !opts->linear_downscaling && !opts->linear_upscaling; pars->params.disable_fbos = opts->dumb_mode == 1; - pars->params.blend_against_tiles = opts->alpha_mode == ALPHA_BLEND_TILES; - pars->params.corner_rounding = p->corner_rounding; + +#if PL_API_VER >= 346 + int map_background_types[3] = { + PL_CLEAR_SKIP, // BACKGROUND_NONE + PL_CLEAR_COLOR, // BACKGROUND_COLOR + PL_CLEAR_TILES, // BACKGROUND_TILES + }; + pars->params.background = map_background_types[opts->background]; + pars->params.border = map_background_types[p->next_opts->border_background]; +#else + pars->params.blend_against_tiles = opts->background == BACKGROUND_TILES; +#endif + + pars->params.corner_rounding = p->next_opts->corner_rounding; pars->params.correct_subpixel_offsets = !opts->scaler_resizes_only; // Map scaler options as best we can @@ -1976,7 +2183,7 @@ static void update_render_options(struct vo *vo) pars->peak_detect_params.scene_threshold_low = opts->tone_map.scene_threshold_low; pars->peak_detect_params.scene_threshold_high = opts->tone_map.scene_threshold_high; pars->peak_detect_params.percentile = opts->tone_map.peak_percentile; - pars->peak_detect_params.allow_delayed = p->delayed_peak; + pars->peak_detect_params.allow_delayed = p->next_opts->delayed_peak; const struct pl_tone_map_function * const tone_map_funs[] = { [TONE_MAPPING_AUTO] = &pl_tone_map_auto, @@ -2055,16 +2262,6 @@ static void update_render_options(struct vo *vo) pars->params.hooks = p->hooks; } -#define OPT_BASE_STRUCT struct priv - -const struct m_opt_choice_alternatives lut_types[] = { - {"auto", PL_LUT_UNKNOWN}, - {"native", PL_LUT_NATIVE}, - {"normalized", PL_LUT_NORMALIZED}, - {"conversion", PL_LUT_CONVERSION}, - {0} -}; - const struct vo_driver video_out_gpu_next = { .description = "Video output based on libplacebo", .name = "gpu-next", @@ -2083,22 +2280,4 @@ const struct vo_driver video_out_gpu_next = { .wakeup = wakeup, .uninit = uninit, .priv_size = sizeof(struct priv), - .priv_defaults = &(const struct priv) { - .inter_preserve = true, - }, - - .options = (const struct m_option[]) { - {"allow-delayed-peak-detect", OPT_BOOL(delayed_peak)}, - {"corner-rounding", OPT_FLOAT(corner_rounding), M_RANGE(0, 1)}, - {"interpolation-preserve", OPT_BOOL(inter_preserve)}, - {"lut", OPT_STRING(lut.opt), .flags = M_OPT_FILE}, - {"lut-type", OPT_CHOICE_C(lut.type, lut_types)}, - {"image-lut", OPT_STRING(image_lut.opt), .flags = M_OPT_FILE}, - {"image-lut-type", OPT_CHOICE_C(image_lut.type, lut_types)}, - {"target-lut", OPT_STRING(target_lut.opt), .flags = M_OPT_FILE}, - {"target-colorspace-hint", OPT_BOOL(target_hint)}, - // No `target-lut-type` because we don't support non-RGB targets - {"libplacebo-opts", OPT_KEYVALUELIST(raw_opts)}, - {0} - }, }; diff --git a/video/out/vo_image.c b/video/out/vo_image.c index cc48ab3..84e164f 100644 --- a/video/out/vo_image.c +++ b/video/out/vo_image.c @@ -118,7 +118,7 @@ static void flip_page(struct vo *vo) filename = mp_path_join(t, p->opts->outdir, filename); MP_INFO(vo, "Saving %s\n", filename); - write_image(p->current, p->opts->opts, filename, vo->global, vo->log); + write_image(p->current, p->opts->opts, filename, vo->global, vo->log, true); talloc_free(t); } diff --git a/video/out/vo_kitty.c b/video/out/vo_kitty.c index 7d548c7..f8f0b07 100644 --- a/video/out/vo_kitty.c +++ b/video/out/vo_kitty.c @@ -340,8 +340,9 @@ static int preinit(struct vo *vo) mp_sws_enable_cmdline_opts(p->sws, vo->global); #if HAVE_POSIX - struct sigaction sa; - sa.sa_handler = handle_winch; + struct sigaction sa = { + .sa_handler = handle_winch, + }; sigaction(SIGWINCH, &sa, &saved_sigaction); #endif diff --git a/video/out/vo_lavc.c b/video/out/vo_lavc.c index 7170c1d..2842335 100644 --- a/video/out/vo_lavc.c +++ b/video/out/vo_lavc.c @@ -23,6 +23,8 @@ #include <stdio.h> #include <stdlib.h> +#include <libplacebo/utils/libav.h> + #include "common/common.h" #include "options/options.h" #include "video/fmt-conversion.h" @@ -112,8 +114,8 @@ static int reconfig2(struct vo *vo, struct mp_image *img) encoder->width = width; encoder->height = height; encoder->pix_fmt = pix_fmt; - encoder->colorspace = mp_csp_to_avcol_spc(params->color.space); - encoder->color_range = mp_csp_levels_to_avcol_range(params->color.levels); + encoder->colorspace = pl_system_to_av(params->repr.sys); + encoder->color_range = pl_levels_to_av(params->repr.levels); AVRational tb; diff --git a/video/out/vo_libmpv.c b/video/out/vo_libmpv.c index 972588e..7974eed 100644 --- a/video/out/vo_libmpv.c +++ b/video/out/vo_libmpv.c @@ -27,6 +27,10 @@ #include "libmpv.h" +#if HAVE_MACOS_COCOA_CB +#include "osdep/mac/app_bridge.h" +#endif + /* * mpv_render_context is managed by the host application - the host application * can access it any time, even if the VO is destroyed (or not created yet). @@ -608,9 +612,6 @@ static int control(struct vo *vo, uint32_t request, void *data) case VOCTRL_PAUSE: vo->want_redraw = true; return VO_TRUE; - case VOCTRL_SET_EQUALIZER: - vo->want_redraw = true; - return VO_TRUE; case VOCTRL_SET_PANSCAN: mp_mutex_lock(&ctx->lock); ctx->need_resize = true; @@ -708,6 +709,13 @@ static void uninit(struct vo *vo) static int preinit(struct vo *vo) { +#if HAVE_MACOS_COCOA_CB + cocoa_init_cocoa_cb(); +#else + if (vo->probing) + return -1; +#endif + struct vo_priv *p = vo->priv; struct mpv_render_context *ctx = diff --git a/video/out/vo_rpi.c b/video/out/vo_rpi.c deleted file mode 100644 index 55f1a68..0000000 --- a/video/out/vo_rpi.c +++ /dev/null @@ -1,938 +0,0 @@ -/* - * This file is part of mpv. - * - * mpv is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * mpv is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with mpv. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <math.h> -#include <stdbool.h> -#include <assert.h> - -#include <bcm_host.h> -#include <interface/mmal/mmal.h> -#include <interface/mmal/util/mmal_util.h> -#include <interface/mmal/util/mmal_default_components.h> -#include <interface/mmal/vc/mmal_vc_api.h> - -#include <EGL/egl.h> -#include <EGL/eglext.h> - -#include <libavutil/rational.h> - -#include "common/common.h" -#include "common/msg.h" -#include "opengl/common.h" -#include "options/m_config.h" -#include "osdep/timer.h" -#include "vo.h" -#include "win_state.h" -#include "video/mp_image.h" -#include "sub/osd.h" - -#include "opengl/ra_gl.h" -#include "gpu/video.h" - -struct mp_egl_rpi { - struct mp_log *log; - struct GL *gl; - struct ra *ra; - EGLDisplay egl_display; - EGLConfig egl_config; - EGLContext egl_context; - EGLSurface egl_surface; - // yep, the API keeps a pointer to it - EGL_DISPMANX_WINDOW_T egl_window; -}; - -struct priv { - DISPMANX_DISPLAY_HANDLE_T display; - DISPMANX_ELEMENT_HANDLE_T window; - DISPMANX_ELEMENT_HANDLE_T osd_overlay; - DISPMANX_UPDATE_HANDLE_T update; - uint32_t w, h; - uint32_t x, y; - double display_fps; - - double osd_pts; - struct mp_osd_res osd_res; - struct m_config_cache *opts_cache; - - struct mp_egl_rpi egl; - struct gl_video *gl_video; - struct mpgl_osd *osd; - - MMAL_COMPONENT_T *renderer; - bool renderer_enabled; - - bool display_synced, skip_osd; - struct mp_image *next_image; - - // for RAM input - MMAL_POOL_T *swpool; - - mp_mutex display_mutex; - mp_cond display_cond; - int64_t vsync_counter; - bool reload_display; - - int background_layer; - int video_layer; - int osd_layer; - - int display_nr; - int layer; - bool background; - bool enable_osd; -}; - -// Magic alignments (in pixels) expected by the MMAL internals. -#define ALIGN_W 32 -#define ALIGN_H 16 - -static void recreate_renderer(struct vo *vo); - -static void *get_proc_address(const GLubyte *name) -{ - void *p = eglGetProcAddress(name); - // EGL 1.4 (supported by the RPI firmware) does not necessarily return - // function pointers for core functions. - if (!p) { - void *h = dlopen("/opt/vc/lib/libbrcmGLESv2.so", RTLD_LAZY); - if (h) { - p = dlsym(h, name); - dlclose(h); - } - } - return p; -} - -static EGLConfig select_fb_config_egl(struct mp_egl_rpi *p) -{ - EGLint attributes[] = { - EGL_SURFACE_TYPE, EGL_WINDOW_BIT, - EGL_RED_SIZE, 8, - EGL_GREEN_SIZE, 8, - EGL_BLUE_SIZE, 8, - EGL_DEPTH_SIZE, 0, - EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, - EGL_NONE - }; - - EGLint config_count; - EGLConfig config; - - eglChooseConfig(p->egl_display, attributes, &config, 1, &config_count); - - if (!config_count) { - MP_FATAL(p, "Could find EGL configuration!\n"); - return NULL; - } - - return config; -} - -static void mp_egl_rpi_destroy(struct mp_egl_rpi *p) -{ - if (p->egl_display) { - eglMakeCurrent(p->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, - EGL_NO_CONTEXT); - } - if (p->egl_surface) - eglDestroySurface(p->egl_display, p->egl_surface); - if (p->egl_context) - eglDestroyContext(p->egl_display, p->egl_context); - p->egl_context = EGL_NO_CONTEXT; - eglReleaseThread(); - p->egl_display = EGL_NO_DISPLAY; - talloc_free(p->gl); - p->gl = NULL; -} - -static int mp_egl_rpi_init(struct mp_egl_rpi *p, DISPMANX_ELEMENT_HANDLE_T window, - int w, int h) -{ - p->egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY); - if (!eglInitialize(p->egl_display, NULL, NULL)) { - MP_FATAL(p, "EGL failed to initialize.\n"); - goto fail; - } - - eglBindAPI(EGL_OPENGL_ES_API); - - EGLConfig config = select_fb_config_egl(p); - if (!config) - goto fail; - - p->egl_window = (EGL_DISPMANX_WINDOW_T){ - .element = window, - .width = w, - .height = h, - }; - p->egl_surface = eglCreateWindowSurface(p->egl_display, config, - &p->egl_window, NULL); - - if (p->egl_surface == EGL_NO_SURFACE) { - MP_FATAL(p, "Could not create EGL surface!\n"); - goto fail; - } - - EGLint context_attributes[] = { - EGL_CONTEXT_CLIENT_VERSION, 2, - EGL_NONE - }; - p->egl_context = eglCreateContext(p->egl_display, config, - EGL_NO_CONTEXT, context_attributes); - - if (p->egl_context == EGL_NO_CONTEXT) { - MP_FATAL(p, "Could not create EGL context!\n"); - goto fail; - } - - eglMakeCurrent(p->egl_display, p->egl_surface, p->egl_surface, - p->egl_context); - - p->gl = talloc_zero(NULL, struct GL); - - const char *exts = eglQueryString(p->egl_display, EGL_EXTENSIONS); - mpgl_load_functions(p->gl, get_proc_address, exts, p->log); - - if (!p->gl->version && !p->gl->es) - goto fail; - - p->ra = ra_create_gl(p->gl, p->log); - if (!p->ra) - goto fail; - - return 0; - -fail: - mp_egl_rpi_destroy(p); - return -1; -} - -// Make mpi point to buffer, assuming MMAL_ENCODING_I420. -// buffer can be NULL. -// Return the required buffer space. -static size_t layout_buffer(struct mp_image *mpi, MMAL_BUFFER_HEADER_T *buffer, - struct mp_image_params *params) -{ - assert(params->imgfmt == IMGFMT_420P); - mp_image_set_params(mpi, params); - int w = MP_ALIGN_UP(params->w, ALIGN_W); - int h = MP_ALIGN_UP(params->h, ALIGN_H); - uint8_t *cur = buffer ? buffer->data : NULL; - size_t size = 0; - for (int i = 0; i < 3; i++) { - int div = i ? 2 : 1; - mpi->planes[i] = cur; - mpi->stride[i] = w / div; - size_t plane_size = h / div * mpi->stride[i]; - if (cur) - cur += plane_size; - size += plane_size; - } - return size; -} - -static void update_osd(struct vo *vo) -{ - struct priv *p = vo->priv; - if (!p->enable_osd) - return; - - if (!gl_video_check_osd_change(p->gl_video, &p->osd_res, p->osd_pts)) { - p->skip_osd = true; - return; - } - - MP_STATS(vo, "start rpi_osd"); - - struct vo_frame frame = {0}; - struct ra_fbo target = { - .tex = ra_create_wrapped_fb(p->egl.ra, 0, p->osd_res.w, p->osd_res.h), - .flip = true, - }; - gl_video_set_osd_pts(p->gl_video, p->osd_pts); - gl_video_render_frame(p->gl_video, &frame, target, RENDER_FRAME_DEF); - ra_tex_free(p->egl.ra, &target.tex); - - MP_STATS(vo, "stop rpi_osd"); -} - -static void resize(struct vo *vo) -{ - struct priv *p = vo->priv; - MMAL_PORT_T *input = p->renderer->input[0]; - - struct mp_rect src, dst; - - vo_get_src_dst_rects(vo, &src, &dst, &p->osd_res); - - int rotate[] = {MMAL_DISPLAY_ROT0, - MMAL_DISPLAY_ROT90, - MMAL_DISPLAY_ROT180, - MMAL_DISPLAY_ROT270}; - - - int src_w = src.x1 - src.x0, src_h = src.y1 - src.y0, - dst_w = dst.x1 - dst.x0, dst_h = dst.y1 - dst.y0; - int p_x, p_y; - av_reduce(&p_x, &p_y, dst_w * src_h, src_w * dst_h, 16000); - MMAL_DISPLAYREGION_T dr = { - .hdr = { .id = MMAL_PARAMETER_DISPLAYREGION, - .size = sizeof(MMAL_DISPLAYREGION_T), }, - .src_rect = { .x = src.x0, .y = src.y0, .width = src_w, .height = src_h }, - .dest_rect = { .x = dst.x0 + p->x, .y = dst.y0 + p->y, - .width = dst_w, .height = dst_h }, - .layer = p->video_layer, - .display_num = p->display_nr, - .pixel_x = p_x, - .pixel_y = p_y, - .transform = rotate[vo->params ? vo->params->rotate / 90 : 0], - .fullscreen = vo->opts->fullscreen, - .set = MMAL_DISPLAY_SET_SRC_RECT | MMAL_DISPLAY_SET_DEST_RECT | - MMAL_DISPLAY_SET_LAYER | MMAL_DISPLAY_SET_NUM | - MMAL_DISPLAY_SET_PIXEL | MMAL_DISPLAY_SET_TRANSFORM | - MMAL_DISPLAY_SET_FULLSCREEN, - }; - - if (vo->params && (vo->params->rotate % 180) == 90) { - MPSWAP(int, dr.src_rect.x, dr.src_rect.y); - MPSWAP(int, dr.src_rect.width, dr.src_rect.height); - } - - if (mmal_port_parameter_set(input, &dr.hdr)) - MP_WARN(vo, "could not set video rectangle\n"); - - if (p->gl_video) - gl_video_resize(p->gl_video, &src, &dst, &p->osd_res); -} - -static void destroy_overlays(struct vo *vo) -{ - struct priv *p = vo->priv; - - if (p->window) - vc_dispmanx_element_remove(p->update, p->window); - p->window = 0; - - gl_video_uninit(p->gl_video); - p->gl_video = NULL; - ra_free(&p->egl.ra); - mp_egl_rpi_destroy(&p->egl); - - if (p->osd_overlay) - vc_dispmanx_element_remove(p->update, p->osd_overlay); - p->osd_overlay = 0; -} - -static int update_display_size(struct vo *vo) -{ - struct priv *p = vo->priv; - - uint32_t n_w = 0, n_h = 0; - if (graphics_get_display_size(0, &n_w, &n_h) < 0) { - MP_FATAL(vo, "Could not get display size.\n"); - return -1; - } - - if (p->w == n_w && p->h == n_h) - return 0; - - p->w = n_w; - p->h = n_h; - - MP_VERBOSE(vo, "Display size: %dx%d\n", p->w, p->h); - - return 0; -} - -static int create_overlays(struct vo *vo) -{ - struct priv *p = vo->priv; - destroy_overlays(vo); - - if (!p->display) - return -1; - - if (vo->opts->fullscreen && p->background) { - // Use the whole screen. - VC_RECT_T dst = {.width = p->w, .height = p->h}; - VC_RECT_T src = {.width = 1 << 16, .height = 1 << 16}; - VC_DISPMANX_ALPHA_T alpha = { - .flags = DISPMANX_FLAGS_ALPHA_FIXED_ALL_PIXELS, - .opacity = 0xFF, - }; - - p->window = vc_dispmanx_element_add(p->update, p->display, - p->background_layer, - &dst, 0, &src, - DISPMANX_PROTECTION_NONE, - &alpha, 0, 0); - if (!p->window) { - MP_FATAL(vo, "Could not add DISPMANX element.\n"); - return -1; - } - } - - if (p->enable_osd) { - VC_RECT_T dst = {.x = p->x, .y = p->y, - .width = p->osd_res.w, .height = p->osd_res.h}; - VC_RECT_T src = {.width = p->osd_res.w << 16, .height = p->osd_res.h << 16}; - VC_DISPMANX_ALPHA_T alpha = { - .flags = DISPMANX_FLAGS_ALPHA_FROM_SOURCE, - .opacity = 0xFF, - }; - p->osd_overlay = vc_dispmanx_element_add(p->update, p->display, - p->osd_layer, - &dst, 0, &src, - DISPMANX_PROTECTION_NONE, - &alpha, 0, 0); - if (!p->osd_overlay) { - MP_FATAL(vo, "Could not add DISPMANX element.\n"); - return -1; - } - - if (mp_egl_rpi_init(&p->egl, p->osd_overlay, - p->osd_res.w, p->osd_res.h) < 0) - { - MP_FATAL(vo, "EGL/GLES initialization for OSD renderer failed.\n"); - return -1; - } - p->gl_video = gl_video_init(p->egl.ra, vo->log, vo->global); - gl_video_set_clear_color(p->gl_video, (struct m_color){.a = 0}); - gl_video_set_osd_source(p->gl_video, vo->osd); - } - - p->display_fps = 0; - TV_GET_STATE_RESP_T tvstate; - TV_DISPLAY_STATE_T tvstate_disp; - if (!vc_tv_get_state(&tvstate) && !vc_tv_get_display_state(&tvstate_disp)) { - if (tvstate_disp.state & (VC_HDMI_HDMI | VC_HDMI_DVI)) { - p->display_fps = tvstate_disp.display.hdmi.frame_rate; - - HDMI_PROPERTY_PARAM_T param = { - .property = HDMI_PROPERTY_PIXEL_CLOCK_TYPE, - }; - if (!vc_tv_hdmi_get_property(¶m) && - param.param1 == HDMI_PIXEL_CLOCK_TYPE_NTSC) - p->display_fps = p->display_fps / 1.001; - } else { - p->display_fps = tvstate_disp.display.sdtv.frame_rate; - } - } - - resize(vo); - - vo_event(vo, VO_EVENT_WIN_STATE); - - vc_dispmanx_update_submit_sync(p->update); - p->update = vc_dispmanx_update_start(10); - - return 0; -} - -static int set_geometry(struct vo *vo) -{ - struct priv *p = vo->priv; - - if (vo->opts->fullscreen) { - vo->dwidth = p->w; - vo->dheight = p->h; - p->x = p->y = 0; - } else { - struct vo_win_geometry geo; - struct mp_rect screenrc = {0, 0, p->w, p->h}; - - vo_calc_window_geometry(vo, &screenrc, &geo); - vo_apply_window_geometry(vo, &geo); - - p->x = geo.win.x0; - p->y = geo.win.y0; - } - - resize(vo); - - if (create_overlays(vo) < 0) - return -1; - - return 0; -} - -static void wait_next_vsync(struct vo *vo) -{ - struct priv *p = vo->priv; - mp_mutex_lock(&p->display_mutex); - int64_t end = mp_time_ns() + MP_TIME_MS_TO_NS(50); - int64_t old = p->vsync_counter; - while (old == p->vsync_counter && !p->reload_display) { - if (mp_cond_timedwait_until(&p->display_cond, &p->display_mutex, end)) - break; - } - mp_mutex_unlock(&p->display_mutex); -} - -static void flip_page(struct vo *vo) -{ - struct priv *p = vo->priv; - - if (!p->renderer_enabled) - return; - - struct mp_image *mpi = p->next_image; - p->next_image = NULL; - - // For OSD - if (!p->skip_osd && p->egl.gl) - eglSwapBuffers(p->egl.egl_display, p->egl.egl_surface); - p->skip_osd = false; - - if (mpi) { - MMAL_PORT_T *input = p->renderer->input[0]; - MMAL_BUFFER_HEADER_T *ref = (void *)mpi->planes[3]; - - // Assume this field is free for use by us. - ref->user_data = mpi; - - if (mmal_port_send_buffer(input, ref)) { - MP_ERR(vo, "could not queue picture!\n"); - talloc_free(mpi); - } - } - - if (p->display_synced) - wait_next_vsync(vo); -} - -static void free_mmal_buffer(void *arg) -{ - MMAL_BUFFER_HEADER_T *buffer = arg; - mmal_buffer_header_release(buffer); -} - -static void draw_frame(struct vo *vo, struct vo_frame *frame) -{ - struct priv *p = vo->priv; - - if (!p->renderer_enabled) - return; - - mp_image_t *mpi = NULL; - if (!frame->redraw && !frame->repeat) - mpi = mp_image_new_ref(frame->current); - - talloc_free(p->next_image); - p->next_image = NULL; - - if (mpi) - p->osd_pts = mpi->pts; - - // Redraw only if the OSD has meaningfully changed, which we assume it - // hasn't when a frame is merely repeated for display sync. - p->skip_osd = !frame->redraw && frame->repeat; - - if (!p->skip_osd && p->egl.gl) - update_osd(vo); - - p->display_synced = frame->display_synced; - - if (mpi && mpi->imgfmt != IMGFMT_MMAL) { - MMAL_BUFFER_HEADER_T *buffer = mmal_queue_wait(p->swpool->queue); - if (!buffer) { - talloc_free(mpi); - MP_ERR(vo, "Can't allocate buffer.\n"); - return; - } - mmal_buffer_header_reset(buffer); - - struct mp_image *new_ref = mp_image_new_custom_ref(NULL, buffer, - free_mmal_buffer); - if (!new_ref) { - mmal_buffer_header_release(buffer); - talloc_free(mpi); - MP_ERR(vo, "Out of memory.\n"); - return; - } - - mp_image_setfmt(new_ref, IMGFMT_MMAL); - new_ref->planes[3] = (void *)buffer; - - struct mp_image dmpi = {0}; - buffer->length = layout_buffer(&dmpi, buffer, vo->params); - mp_image_copy(&dmpi, mpi); - - talloc_free(mpi); - mpi = new_ref; - } - - p->next_image = mpi; -} - -static int query_format(struct vo *vo, int format) -{ - return format == IMGFMT_MMAL || format == IMGFMT_420P; -} - -static MMAL_FOURCC_T map_csp(enum mp_csp csp) -{ - switch (csp) { - case MP_CSP_BT_601: return MMAL_COLOR_SPACE_ITUR_BT601; - case MP_CSP_BT_709: return MMAL_COLOR_SPACE_ITUR_BT709; - case MP_CSP_SMPTE_240M: return MMAL_COLOR_SPACE_SMPTE240M; - default: return MMAL_COLOR_SPACE_UNKNOWN; - } -} - -static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) -{ - mmal_buffer_header_release(buffer); -} - -static void input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) -{ - struct mp_image *mpi = buffer->user_data; - talloc_free(mpi); -} - -static void disable_renderer(struct vo *vo) -{ - struct priv *p = vo->priv; - - if (p->renderer_enabled) { - mmal_port_disable(p->renderer->control); - mmal_port_disable(p->renderer->input[0]); - - mmal_port_flush(p->renderer->control); - mmal_port_flush(p->renderer->input[0]); - - mmal_component_disable(p->renderer); - } - mmal_pool_destroy(p->swpool); - p->swpool = NULL; - p->renderer_enabled = false; -} - -static int reconfig(struct vo *vo, struct mp_image_params *params) -{ - struct priv *p = vo->priv; - MMAL_PORT_T *input = p->renderer->input[0]; - bool opaque = params->imgfmt == IMGFMT_MMAL; - - if (!p->display) - return -1; - - disable_renderer(vo); - - input->format->encoding = opaque ? MMAL_ENCODING_OPAQUE : MMAL_ENCODING_I420; - input->format->es->video.width = MP_ALIGN_UP(params->w, ALIGN_W); - input->format->es->video.height = MP_ALIGN_UP(params->h, ALIGN_H); - input->format->es->video.crop = (MMAL_RECT_T){0, 0, params->w, params->h}; - input->format->es->video.par = (MMAL_RATIONAL_T){params->p_w, params->p_h}; - input->format->es->video.color_space = map_csp(params->color.space); - - if (mmal_port_format_commit(input)) - return -1; - - input->buffer_num = MPMAX(input->buffer_num_min, - input->buffer_num_recommended) + 3; - input->buffer_size = MPMAX(input->buffer_size_min, - input->buffer_size_recommended); - - if (!opaque) { - size_t size = layout_buffer(&(struct mp_image){0}, NULL, params); - if (input->buffer_size != size) { - MP_FATAL(vo, "We disagree with MMAL about buffer sizes.\n"); - return -1; - } - - p->swpool = mmal_pool_create(input->buffer_num, input->buffer_size); - if (!p->swpool) { - MP_FATAL(vo, "Could not allocate buffer pool.\n"); - return -1; - } - } - - if (set_geometry(vo) < 0) - return -1; - - p->renderer_enabled = true; - - if (mmal_port_enable(p->renderer->control, control_port_cb)) - return -1; - - if (mmal_port_enable(input, input_port_cb)) - return -1; - - if (mmal_component_enable(p->renderer)) { - MP_FATAL(vo, "Failed to enable video renderer.\n"); - return -1; - } - - resize(vo); - - return 0; -} - -static struct mp_image *take_screenshot(struct vo *vo) -{ - struct priv *p = vo->priv; - - if (!p->display) - return NULL; - - struct mp_image *img = mp_image_alloc(IMGFMT_BGR0, p->w, p->h); - if (!img) - return NULL; - - DISPMANX_RESOURCE_HANDLE_T resource = - vc_dispmanx_resource_create(VC_IMAGE_ARGB8888, - img->w | ((img->w * 4) << 16), img->h, - &(int32_t){0}); - if (!resource) - goto fail; - - if (vc_dispmanx_snapshot(p->display, resource, 0)) - goto fail; - - VC_RECT_T rc = {.width = img->w, .height = img->h}; - if (vc_dispmanx_resource_read_data(resource, &rc, img->planes[0], img->stride[0])) - goto fail; - - vc_dispmanx_resource_delete(resource); - return img; - -fail: - vc_dispmanx_resource_delete(resource); - talloc_free(img); - return NULL; -} - -static void set_fullscreen(struct vo *vo) { - struct priv *p = vo->priv; - - if (p->renderer_enabled) - set_geometry(vo); - vo->want_redraw = true; -} - -static int control(struct vo *vo, uint32_t request, void *data) -{ - struct priv *p = vo->priv; - - switch (request) { - case VOCTRL_VO_OPTS_CHANGED: { - void *opt; - while (m_config_cache_get_next_changed(p->opts_cache, &opt)) { - struct mp_vo_opts *opts = p->opts_cache->opts; - if (&opts->fullscreen == opt) - set_fullscreen(vo); - } - return VO_TRUE; - } - case VOCTRL_SET_PANSCAN: - if (p->renderer_enabled) - resize(vo); - vo->want_redraw = true; - return VO_TRUE; - case VOCTRL_REDRAW_FRAME: - update_osd(vo); - return VO_TRUE; - case VOCTRL_SCREENSHOT_WIN: - *(struct mp_image **)data = take_screenshot(vo); - return VO_TRUE; - case VOCTRL_CHECK_EVENTS: { - mp_mutex_lock(&p->display_mutex); - bool reload_required = p->reload_display; - p->reload_display = false; - mp_mutex_unlock(&p->display_mutex); - if (reload_required) - recreate_renderer(vo); - return VO_TRUE; - } - case VOCTRL_GET_DISPLAY_FPS: - *(double *)data = p->display_fps; - return VO_TRUE; - case VOCTRL_GET_DISPLAY_RES: - ((int *)data)[0] = p->w; - ((int *)data)[1] = p->h; - return VO_TRUE; - } - - return VO_NOTIMPL; -} - -static void tv_callback(void *callback_data, uint32_t reason, uint32_t param1, - uint32_t param2) -{ - struct vo *vo = callback_data; - struct priv *p = vo->priv; - mp_mutex_lock(&p->display_mutex); - p->reload_display = true; - mp_cond_signal(&p->display_cond); - mp_mutex_unlock(&p->display_mutex); - vo_wakeup(vo); -} - -static void vsync_callback(DISPMANX_UPDATE_HANDLE_T u, void *arg) -{ - struct vo *vo = arg; - struct priv *p = vo->priv; - mp_mutex_lock(&p->display_mutex); - p->vsync_counter += 1; - mp_cond_signal(&p->display_cond); - mp_mutex_unlock(&p->display_mutex); -} - -static void destroy_dispmanx(struct vo *vo) -{ - struct priv *p = vo->priv; - - disable_renderer(vo); - destroy_overlays(vo); - - if (p->update) - vc_dispmanx_update_submit_sync(p->update); - p->update = 0; - - if (p->display) { - vc_dispmanx_vsync_callback(p->display, NULL, NULL); - vc_dispmanx_display_close(p->display); - } - p->display = 0; -} - -static int recreate_dispmanx(struct vo *vo) -{ - struct priv *p = vo->priv; - - p->display = vc_dispmanx_display_open(p->display_nr); - p->update = vc_dispmanx_update_start(0); - if (!p->display || !p->update) { - MP_FATAL(vo, "Could not get DISPMANX objects.\n"); - if (p->display) - vc_dispmanx_display_close(p->display); - p->display = 0; - p->update = 0; - return -1; - } - - update_display_size(vo); - - vc_dispmanx_vsync_callback(p->display, vsync_callback, vo); - - return 0; -} - -static void recreate_renderer(struct vo *vo) -{ - MP_WARN(vo, "Recreating renderer after display change.\n"); - - destroy_dispmanx(vo); - recreate_dispmanx(vo); - - if (vo->params) { - if (reconfig(vo, vo->params) < 0) - MP_FATAL(vo, "Recreation failed.\n"); - } -} - -static void uninit(struct vo *vo) -{ - struct priv *p = vo->priv; - - vc_tv_unregister_callback_full(tv_callback, vo); - - talloc_free(p->next_image); - - destroy_dispmanx(vo); - - if (p->renderer) - mmal_component_release(p->renderer); - - mmal_vc_deinit(); - - mp_cond_destroy(&p->display_cond); - mp_mutex_destroy(&p->display_mutex); -} - -static int preinit(struct vo *vo) -{ - struct priv *p = vo->priv; - - p->background_layer = p->layer; - p->video_layer = p->layer + 1; - p->osd_layer = p->layer + 2; - - p->egl.log = vo->log; - - bcm_host_init(); - - if (mmal_vc_init()) { - MP_FATAL(vo, "Could not initialize MMAL.\n"); - return -1; - } - - mp_mutex_init(&p->display_mutex); - mp_cond_init(&p->display_cond); - - p->opts_cache = m_config_cache_alloc(p, vo->global, &vo_sub_opts); - - if (recreate_dispmanx(vo) < 0) - goto fail; - - if (update_display_size(vo) < 0) - goto fail; - - if (mmal_component_create(MMAL_COMPONENT_DEFAULT_VIDEO_RENDERER, &p->renderer)) - { - MP_FATAL(vo, "Could not create MMAL renderer.\n"); - goto fail; - } - - vc_tv_register_callback(tv_callback, vo); - - return 0; - -fail: - uninit(vo); - return -1; -} - -#define OPT_BASE_STRUCT struct priv -static const struct m_option options[] = { - {"display", OPT_INT(display_nr)}, - {"layer", OPT_INT(layer), OPTDEF_INT(-10)}, - {"background", OPT_BOOL(background)}, - {"osd", OPT_BOOL(enable_osd), OPTDEF_INT(1)}, - {0}, -}; - -const struct vo_driver video_out_rpi = { - .description = "Raspberry Pi (MMAL)", - .name = "rpi", - .caps = VO_CAP_ROTATE90, - .preinit = preinit, - .query_format = query_format, - .reconfig = reconfig, - .control = control, - .draw_frame = draw_frame, - .flip_page = flip_page, - .uninit = uninit, - .priv_size = sizeof(struct priv), - .options = options, - .options_prefix = "rpi", -}; diff --git a/video/out/vo_sdl.c b/video/out/vo_sdl.c index 5f4c027..71791e2 100644 --- a/video/out/vo_sdl.c +++ b/video/out/vo_sdl.c @@ -547,6 +547,10 @@ static void wait_events(struct vo *vo, int64_t until_time_ns) case SDL_WINDOWEVENT_LEAVE: mp_input_put_key(vo->input_ctx, MP_KEY_MOUSE_LEAVE); break; + case SDL_WINDOWEVENT_FOCUS_LOST: + case SDL_WINDOWEVENT_FOCUS_GAINED: + vo_event(vo, VO_EVENT_FOCUS); + break; } break; case SDL_QUIT: @@ -959,6 +963,9 @@ static int control(struct vo *vo, uint32_t request, void *data) case VOCTRL_UPDATE_WINDOW_TITLE: SDL_SetWindowTitle(vc->window, (char *)data); return true; + case VOCTRL_GET_FOCUSED: + *(bool *)data = SDL_GetWindowFlags(vc->window) & SDL_WINDOW_INPUT_FOCUS; + return VO_TRUE; } return VO_NOTIMPL; } diff --git a/video/out/vo_tct.c b/video/out/vo_tct.c index 8859095..0ca6ea6 100644 --- a/video/out/vo_tct.c +++ b/video/out/vo_tct.c @@ -39,17 +39,28 @@ #define ALGO_PLAIN 1 #define ALGO_HALF_BLOCKS 2 -#define TERM_ESC_CLEAR_COLORS "\033[0m" -#define TERM_ESC_COLOR256_BG "\033[48;5" -#define TERM_ESC_COLOR256_FG "\033[38;5" -#define TERM_ESC_COLOR24BIT_BG "\033[48;2" -#define TERM_ESC_COLOR24BIT_FG "\033[38;2" - #define DEFAULT_WIDTH 80 #define DEFAULT_HEIGHT 25 +static const bstr TERM_ESC_CLEAR_COLORS = bstr0_lit("\033[0m"); +static const bstr TERM_ESC_COLOR256_BG = bstr0_lit("\033[48;5"); +static const bstr TERM_ESC_COLOR256_FG = bstr0_lit("\033[38;5"); +static const bstr TERM_ESC_COLOR24BIT_BG = bstr0_lit("\033[48;2"); +static const bstr TERM_ESC_COLOR24BIT_FG = bstr0_lit("\033[38;2"); + +static const bstr UNICODE_LOWER_HALF_BLOCK = bstr0_lit("\xe2\x96\x84"); + +#define WRITE_STR(str) fwrite((str), strlen(str), 1, stdout) + +enum vo_tct_buffering { + VO_TCT_BUFFER_PIXEL, + VO_TCT_BUFFER_LINE, + VO_TCT_BUFFER_FRAME +}; + struct vo_tct_opts { int algo; + int buffering; int width; // 0 -> default int height; // 0 -> default bool term256; // 0 -> true color @@ -57,7 +68,7 @@ struct vo_tct_opts { struct lut_item { char str[4]; - int width; + uint8_t width; }; struct priv { @@ -69,6 +80,7 @@ struct priv { struct mp_rect src; struct mp_rect dst; struct mp_sws_context *sws; + bstr frame_buf; struct lut_item lut[256]; }; @@ -101,69 +113,65 @@ static int rgb_to_x256(uint8_t r, uint8_t g, uint8_t b) return color_err <= gray_err ? 16 + color_index() : 232 + gray_index; } -static void print_seq3(struct lut_item *lut, const char* prefix, +static void print_seq3(bstr *frame, struct lut_item *lut, bstr prefix, uint8_t r, uint8_t g, uint8_t b) { -// The fwrite implementation is about 25% faster than the printf code -// (even if we use *.s with the lut values), however, -// on windows we need to use printf in order to translate escape sequences and -// UTF8 output for the console. -#ifndef _WIN32 - fputs(prefix, stdout); - fwrite(lut[r].str, lut[r].width, 1, stdout); - fwrite(lut[g].str, lut[g].width, 1, stdout); - fwrite(lut[b].str, lut[b].width, 1, stdout); - fputc('m', stdout); -#else - printf("%s;%d;%d;%dm", prefix, (int)r, (int)g, (int)b); -#endif + bstr_xappend(NULL, frame, prefix); + bstr_xappend(NULL, frame, (bstr){ lut[r].str, lut[r].width }); + bstr_xappend(NULL, frame, (bstr){ lut[g].str, lut[g].width }); + bstr_xappend(NULL, frame, (bstr){ lut[b].str, lut[b].width }); + bstr_xappend(NULL, frame, (bstr)bstr0_lit("m")); } -static void print_seq1(struct lut_item *lut, const char* prefix, uint8_t c) +static void print_seq1(bstr *frame, struct lut_item *lut, bstr prefix, uint8_t c) { -#ifndef _WIN32 - fputs(prefix, stdout); - fwrite(lut[c].str, lut[c].width, 1, stdout); - fputc('m', stdout); -#else - printf("%s;%dm", prefix, (int)c); -#endif + bstr_xappend(NULL, frame, prefix); + bstr_xappend(NULL, frame, (bstr){ lut[c].str, lut[c].width }); + bstr_xappend(NULL, frame, (bstr)bstr0_lit("m")); } +static void print_buffer(bstr *frame) +{ + fwrite(frame->start, frame->len, 1, stdout); + frame->len = 0; +} -static void write_plain( +static void write_plain(bstr *frame, const int dwidth, const int dheight, const int swidth, const int sheight, const unsigned char *source, const int source_stride, - bool term256, struct lut_item *lut) + bool term256, struct lut_item *lut, enum vo_tct_buffering buffering) { assert(source); const int tx = (dwidth - swidth) / 2; const int ty = (dheight - sheight) / 2; for (int y = 0; y < sheight; y++) { const unsigned char *row = source + y * source_stride; - printf(TERM_ESC_GOTO_YX, ty + y, tx); + bstr_xappend_asprintf(NULL, frame, TERM_ESC_GOTO_YX, ty + y, tx); for (int x = 0; x < swidth; x++) { unsigned char b = *row++; unsigned char g = *row++; unsigned char r = *row++; if (term256) { - print_seq1(lut, TERM_ESC_COLOR256_BG, rgb_to_x256(r, g, b)); + print_seq1(frame, lut, TERM_ESC_COLOR256_BG, rgb_to_x256(r, g, b)); } else { - print_seq3(lut, TERM_ESC_COLOR24BIT_BG, r, g, b); + print_seq3(frame, lut, TERM_ESC_COLOR24BIT_BG, r, g, b); } - printf(" "); + bstr_xappend(NULL, frame, (bstr)bstr0_lit(" ")); + if (buffering <= VO_TCT_BUFFER_PIXEL) + print_buffer(frame); } - printf(TERM_ESC_CLEAR_COLORS); + bstr_xappend(NULL, frame, TERM_ESC_CLEAR_COLORS); + if (buffering <= VO_TCT_BUFFER_LINE) + print_buffer(frame); } - printf("\n"); } -static void write_half_blocks( +static void write_half_blocks(bstr *frame, const int dwidth, const int dheight, const int swidth, const int sheight, unsigned char *source, int source_stride, - bool term256, struct lut_item *lut) + bool term256, struct lut_item *lut, enum vo_tct_buffering buffering) { assert(source); const int tx = (dwidth - swidth) / 2; @@ -171,7 +179,7 @@ static void write_half_blocks( for (int y = 0; y < sheight * 2; y += 2) { const unsigned char *row_up = source + y * source_stride; const unsigned char *row_down = source + (y + 1) * source_stride; - printf(TERM_ESC_GOTO_YX, ty + y / 2, tx); + bstr_xappend_asprintf(NULL, frame, TERM_ESC_GOTO_YX, ty + y / 2, tx); for (int x = 0; x < swidth; x++) { unsigned char b_up = *row_up++; unsigned char g_up = *row_up++; @@ -180,17 +188,20 @@ static void write_half_blocks( unsigned char g_down = *row_down++; unsigned char r_down = *row_down++; if (term256) { - print_seq1(lut, TERM_ESC_COLOR256_BG, rgb_to_x256(r_up, g_up, b_up)); - print_seq1(lut, TERM_ESC_COLOR256_FG, rgb_to_x256(r_down, g_down, b_down)); + print_seq1(frame, lut, TERM_ESC_COLOR256_BG, rgb_to_x256(r_up, g_up, b_up)); + print_seq1(frame, lut, TERM_ESC_COLOR256_FG, rgb_to_x256(r_down, g_down, b_down)); } else { - print_seq3(lut, TERM_ESC_COLOR24BIT_BG, r_up, g_up, b_up); - print_seq3(lut, TERM_ESC_COLOR24BIT_FG, r_down, g_down, b_down); + print_seq3(frame, lut, TERM_ESC_COLOR24BIT_BG, r_up, g_up, b_up); + print_seq3(frame, lut, TERM_ESC_COLOR24BIT_FG, r_down, g_down, b_down); } - printf("\xe2\x96\x84"); // UTF8 bytes of U+2584 (lower half block) + bstr_xappend(NULL, frame, UNICODE_LOWER_HALF_BLOCK); + if (buffering <= VO_TCT_BUFFER_PIXEL) + print_buffer(frame); } - printf(TERM_ESC_CLEAR_COLORS); + bstr_xappend(NULL, frame, TERM_ESC_CLEAR_COLORS); + if (buffering <= VO_TCT_BUFFER_LINE) + print_buffer(frame); } - printf("\n"); } static void get_win_size(struct vo *vo, int *out_width, int *out_height) { @@ -236,7 +247,7 @@ static int reconfig(struct vo *vo, struct mp_image_params *params) if (mp_sws_reinit(p->sws) < 0) return -1; - printf(TERM_ESC_CLEAR_SCREEN); + WRITE_STR(TERM_ESC_CLEAR_SCREEN); vo->want_redraw = true; return 0; @@ -262,27 +273,36 @@ static void flip_page(struct vo *vo) if (vo->dwidth != width || vo->dheight != height) reconfig(vo, vo->params); + WRITE_STR(TERM_ESC_SYNC_UPDATE_BEGIN); + + p->frame_buf.len = 0; if (p->opts.algo == ALGO_PLAIN) { - write_plain( + write_plain(&p->frame_buf, vo->dwidth, vo->dheight, p->swidth, p->sheight, p->frame->planes[0], p->frame->stride[0], - p->opts.term256, p->lut); + p->opts.term256, p->lut, p->opts.buffering); } else { - write_half_blocks( + write_half_blocks(&p->frame_buf, vo->dwidth, vo->dheight, p->swidth, p->sheight, p->frame->planes[0], p->frame->stride[0], - p->opts.term256, p->lut); + p->opts.term256, p->lut, p->opts.buffering); } + + bstr_xappend(NULL, &p->frame_buf, (bstr)bstr0_lit("\n")); + if (p->opts.buffering <= VO_TCT_BUFFER_FRAME) + print_buffer(&p->frame_buf); + + WRITE_STR(TERM_ESC_SYNC_UPDATE_END); fflush(stdout); } static void uninit(struct vo *vo) { - printf(TERM_ESC_RESTORE_CURSOR); - printf(TERM_ESC_NORMAL_SCREEN); + WRITE_STR(TERM_ESC_RESTORE_CURSOR); + WRITE_STR(TERM_ESC_NORMAL_SCREEN); struct priv *p = vo->priv; - if (p->frame) - talloc_free(p->frame); + talloc_free(p->frame); + talloc_free(p->frame_buf.start); } static int preinit(struct vo *vo) @@ -296,14 +316,19 @@ static int preinit(struct vo *vo) p->sws->log = vo->log; mp_sws_enable_cmdline_opts(p->sws, vo->global); - for (int i = 0; i < 256; ++i) { - char buff[8]; - p->lut[i].width = snprintf(buff, sizeof(buff), ";%d", i); - memcpy(p->lut[i].str, buff, 4); // some strings may not end on a null byte, but that's ok. + for (int i = 0; i < MP_ARRAY_SIZE(p->lut); ++i) { + char* out = p->lut[i].str; + *out++ = ';'; + if (i >= 100) + *out++ = '0' + (i / 100); + if (i >= 10) + *out++ = '0' + ((i / 10) % 10); + *out++ = '0' + (i % 10); + p->lut[i].width = out - p->lut[i].str; } - printf(TERM_ESC_HIDE_CURSOR); - printf(TERM_ESC_ALT_SCREEN); + WRITE_STR(TERM_ESC_HIDE_CURSOR); + WRITE_STR(TERM_ESC_ALT_SCREEN); return 0; } @@ -333,6 +358,7 @@ const struct vo_driver video_out_tct = { .priv_size = sizeof(struct priv), .priv_defaults = &(const struct priv) { .opts.algo = ALGO_HALF_BLOCKS, + .opts.buffering = VO_TCT_BUFFER_LINE, }, .options = (const m_option_t[]) { {"algo", OPT_CHOICE(opts.algo, @@ -341,6 +367,10 @@ const struct vo_driver video_out_tct = { {"width", OPT_INT(opts.width)}, {"height", OPT_INT(opts.height)}, {"256", OPT_BOOL(opts.term256)}, + {"buffering", OPT_CHOICE(opts.buffering, + {"pixel", VO_TCT_BUFFER_PIXEL}, + {"line", VO_TCT_BUFFER_LINE}, + {"frame", VO_TCT_BUFFER_FRAME})}, {0} }, .options_prefix = "vo-tct", diff --git a/video/out/vo_vaapi.c b/video/out/vo_vaapi.c index 12888fe..2a24130 100644 --- a/video/out/vo_vaapi.c +++ b/video/out/vo_vaapi.c @@ -519,7 +519,7 @@ static bool render_to_screen(struct priv *p, struct mp_image *mpi) CHECK_VA_STATUS(p, "vaAssociateSubpicture()"); } - int flags = va_get_colorspace_flag(p->image_params.color.space) | + int flags = va_get_colorspace_flag(p->image_params.repr.sys) | p->scaling | VA_FRAME_PICTURE; status = vaPutSurface(p->display, surface, diff --git a/video/out/vo_vdpau.c b/video/out/vo_vdpau.c index d6b261f..b22dcba 100644 --- a/video/out/vo_vdpau.c +++ b/video/out/vo_vdpau.c @@ -80,7 +80,7 @@ struct vdpctx { struct mp_image *current_image; int64_t current_pts; - int current_duration; + int64_t current_duration; int output_surface_w, output_surface_h; int rotation; @@ -104,9 +104,9 @@ struct vdpctx { int surface_num; // indexes output_surfaces int query_surface_num; VdpTime recent_vsync_time; - float user_fps; + double user_fps; bool composite_detect; - int vsync_interval; + int64_t vsync_interval; uint64_t last_queue_time; uint64_t queue_time[MAX_OUTPUT_SURFACES]; uint64_t last_ideal_time; @@ -721,12 +721,12 @@ static int update_presentation_queue_status(struct vo *vo) break; if (vc->vsync_interval > 1) { uint64_t qtime = vc->queue_time[vc->query_surface_num]; - int diff = ((int64_t)vtime - (int64_t)qtime) / 1e6; - MP_TRACE(vo, "Queue time difference: %d ms\n", diff); + double diff = MP_TIME_NS_TO_MS((int64_t)vtime - (int64_t)qtime); + MP_TRACE(vo, "Queue time difference: %.4f ms\n", diff); if (vtime < qtime + vc->vsync_interval / 2) - MP_VERBOSE(vo, "Frame shown too early (%d ms)\n", diff); + MP_VERBOSE(vo, "Frame shown too early (%.4f ms)\n", diff); if (vtime > qtime + vc->vsync_interval) - MP_VERBOSE(vo, "Frame shown late (%d ms)\n", diff); + MP_VERBOSE(vo, "Frame shown late (%.4f ms)\n", diff); } vc->query_surface_num = WRAP_ADD(vc->query_surface_num, 1, vc->num_output_surfaces); @@ -754,8 +754,8 @@ static void flip_page(struct vo *vo) struct vdp_functions *vdp = vc->vdp; VdpStatus vdp_st; - int64_t pts_us = vc->current_pts; - int duration = vc->current_duration; + int64_t pts_ns = vc->current_pts; + int64_t duration = vc->current_duration; vc->dropped_frame = true; // changed at end if false @@ -770,11 +770,6 @@ static void flip_page(struct vo *vo) } vc->vsync_interval = MPMAX(vc->vsync_interval, 1); - if (duration > INT_MAX / 1000) - duration = -1; - else - duration *= 1000; - if (vc->vsync_interval == 1) duration = -1; // Make sure drop logic is disabled @@ -782,8 +777,8 @@ static void flip_page(struct vo *vo) vdp_st = vdp->presentation_queue_get_time(vc->flip_queue, &vdp_time); CHECK_VDP_WARNING(vo, "Error when calling vdp_presentation_queue_get_time"); - int64_t rel_pts_ns = (pts_us * 1000) - mp_time_ns(); - if (!pts_us || rel_pts_ns < 0) + int64_t rel_pts_ns = pts_ns - mp_time_ns(); + if (!pts_ns || rel_pts_ns < 0) rel_pts_ns = 0; uint64_t now = vdp_time; @@ -1076,9 +1071,6 @@ static int control(struct vo *vo, uint32_t request, void *data) case VOCTRL_SET_PANSCAN: checked_resize(vo); return VO_TRUE; - case VOCTRL_SET_EQUALIZER: - vo->want_redraw = true; - return true; case VOCTRL_RESET: forget_frames(vo, true); return true; @@ -1124,7 +1116,7 @@ const struct vo_driver video_out_vdpau = { {"denoise", OPT_FLOAT(denoise), M_RANGE(0, 1)}, {"sharpen", OPT_FLOAT(sharpen), M_RANGE(-1, 1)}, {"hqscaling", OPT_INT(hqscaling), M_RANGE(0, 9)}, - {"fps", OPT_FLOAT(user_fps)}, + {"fps", OPT_DOUBLE(user_fps)}, {"composite-detect", OPT_BOOL(composite_detect), OPTDEF_INT(1)}, {"queuetime-windowed", OPT_INT(flip_offset_window), OPTDEF_INT(50)}, {"queuetime-fs", OPT_INT(flip_offset_fs), OPTDEF_INT(50)}, diff --git a/video/out/vo_wlshm.c b/video/out/vo_wlshm.c index 1e5e009..0b63426 100644 --- a/video/out/vo_wlshm.c +++ b/video/out/vo_wlshm.c @@ -21,8 +21,6 @@ #include <time.h> #include <unistd.h> -#include <libswscale/swscale.h> - #include "osdep/endian.h" #include "present_sync.h" #include "sub/osd.h" @@ -32,6 +30,8 @@ #include "vo.h" #include "wayland_common.h" +#define IMGFMT_WL_RGB MP_SELECT_LE_BE(IMGFMT_BGR0, IMGFMT_0RGB) + struct buffer { struct vo *vo; size_t size; @@ -164,7 +164,8 @@ err: static int query_format(struct vo *vo, int format) { - return sws_isSupportedInput(imgfmt2pixfmt(format)); + struct priv *p = vo->priv; + return mp_sws_supports_formats(p->sws, IMGFMT_WL_RGB, format) ? 1 : 0; } static int reconfig(struct vo *vo, struct mp_image_params *params) @@ -195,21 +196,26 @@ static int resize(struct vo *vo) vo->dwidth = width; vo->dheight = height; vo_get_src_dst_rects(vo, &p->src, &p->dst, &p->osd); + p->sws->dst = (struct mp_image_params) { - .imgfmt = MP_SELECT_LE_BE(IMGFMT_BGR0, IMGFMT_0RGB), + .imgfmt = IMGFMT_WL_RGB, .w = width, .h = height, .p_w = 1, .p_h = 1, }; mp_image_params_guess_csp(&p->sws->dst); + mp_mutex_lock(&vo->params_mutex); + vo->target_params = &p->sws->dst; + mp_mutex_unlock(&vo->params_mutex); + while (p->free_buffers) { buf = p->free_buffers; p->free_buffers = buf->next; talloc_free(buf); } - vo_wayland_handle_fractional_scale(wl); + vo_wayland_handle_scale(wl); return mp_sws_reinit(p->sws); } diff --git a/video/out/vo_x11.c b/video/out/vo_x11.c index fa93157..b637b59 100644 --- a/video/out/vo_x11.c +++ b/video/out/vo_x11.c @@ -266,6 +266,10 @@ static bool resize(struct vo *vo) if (mp_sws_reinit(p->sws) < 0) return false; + + mp_mutex_lock(&vo->params_mutex); + vo->target_params = &p->sws->dst; + mp_mutex_unlock(&vo->params_mutex); } vo->want_redraw = true; diff --git a/video/out/vo_xv.c b/video/out/vo_xv.c index 6c776c5..d2d5b73 100644 --- a/video/out/vo_xv.c +++ b/video/out/vo_xv.c @@ -92,6 +92,7 @@ struct xvctx { int Shmem_Flag; XShmSegmentInfo Shminfo[MAX_BUFFERS]; int Shm_Warned_Slow; + struct mp_image_params dst_params; }; #define MP_FOURCC(a,b,c,d) ((a) | ((b)<<8) | ((c)<<16) | ((unsigned)(d)<<24)) @@ -401,7 +402,7 @@ static void read_xv_csp(struct vo *vo) ctx->cached_csp = 0; int bt709_enabled; if (xv_get_eq(vo, ctx->xv_port, "bt_709", &bt709_enabled)) - ctx->cached_csp = bt709_enabled == 100 ? MP_CSP_BT_709 : MP_CSP_BT_601; + ctx->cached_csp = bt709_enabled == 100 ? PL_COLOR_SYSTEM_BT_709 : PL_COLOR_SYSTEM_BT_601; } @@ -519,10 +520,17 @@ static int reconfig(struct vo *vo, struct mp_image_params *params) ctx->current_buf = 0; ctx->current_ip_buf = 0; - int is_709 = params->color.space == MP_CSP_BT_709; + int is_709 = params->repr.sys == PL_COLOR_SYSTEM_BT_709; xv_set_eq(vo, ctx->xv_port, "bt_709", is_709 * 200 - 100); read_xv_csp(vo); + ctx->dst_params = *params; + if (ctx->cached_csp) + ctx->dst_params.repr.sys = ctx->cached_csp; + mp_mutex_lock(&vo->params_mutex); + vo->target_params = &ctx->dst_params; + mp_mutex_unlock(&vo->params_mutex); + resize(vo); return 0; @@ -652,7 +660,7 @@ static struct mp_image get_xv_buffer(struct vo *vo, int buf_index) if (vo->params) { struct mp_image_params params = *vo->params; if (ctx->cached_csp) - params.color.space = ctx->cached_csp; + params.repr.sys = ctx->cached_csp; mp_image_set_attributes(&img, ¶ms); } diff --git a/video/out/vulkan/common.h b/video/out/vulkan/common.h index d006942..e75cb22 100644 --- a/video/out/vulkan/common.h +++ b/video/out/vulkan/common.h @@ -23,7 +23,6 @@ #define VK_USE_PLATFORM_WIN32_KHR #endif #if HAVE_COCOA -#define VK_USE_PLATFORM_MACOS_MVK #define VK_USE_PLATFORM_METAL_EXT #endif diff --git a/video/out/vulkan/context.c b/video/out/vulkan/context.c index 5087403..82c878a 100644 --- a/video/out/vulkan/context.c +++ b/video/out/vulkan/context.c @@ -25,6 +25,7 @@ #include "options/m_config.h" #include "video/out/placebo/ra_pl.h" +#include "video/out/placebo/utils.h" #include "context.h" #include "utils.h" @@ -37,39 +38,32 @@ struct vulkan_opts { bool async_compute; }; -static int vk_validate_dev(struct mp_log *log, const struct m_option *opt, - struct bstr name, const char **value) +static inline OPT_STRING_VALIDATE_FUNC(vk_validate_dev) { - struct bstr param = bstr0(*value); int ret = M_OPT_INVALID; - VkResult res; + void *ta_ctx = talloc_new(NULL); + pl_log pllog = mppl_log_create(ta_ctx, log); + if (!pllog) + goto done; // Create a dummy instance to validate/list the devices - VkInstanceCreateInfo info = { - .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, - .pApplicationInfo = &(VkApplicationInfo) { - .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, - .apiVersion = VK_API_VERSION_1_1, - } - }; - - VkInstance inst; - VkPhysicalDevice *devices = NULL; - uint32_t num = 0; - - res = vkCreateInstance(&info, NULL, &inst); - if (res != VK_SUCCESS) + mppl_log_set_probing(pllog, true); + pl_vk_inst inst = pl_vk_inst_create(pllog, pl_vk_inst_params()); + mppl_log_set_probing(pllog, false); + if (!inst) goto done; - res = vkEnumeratePhysicalDevices(inst, &num, NULL); + uint32_t num = 0; + VkResult res = vkEnumeratePhysicalDevices(inst->instance, &num, NULL); if (res != VK_SUCCESS) goto done; - devices = talloc_array(NULL, VkPhysicalDevice, num); - res = vkEnumeratePhysicalDevices(inst, &num, devices); + VkPhysicalDevice *devices = talloc_array(ta_ctx, VkPhysicalDevice, num); + res = vkEnumeratePhysicalDevices(inst->instance, &num, devices); if (res != VK_SUCCESS) goto done; + struct bstr param = bstr0(*value); bool help = bstr_equals0(param, "help"); if (help) { mp_info(log, "Available vulkan devices:\n"); @@ -111,7 +105,9 @@ static int vk_validate_dev(struct mp_log *log, const struct m_option *opt, BSTR_P(param)); done: - talloc_free(devices); + pl_vk_inst_destroy(&inst); + pl_log_destroy(&pllog); + talloc_free(ta_ctx); return ret; } @@ -207,8 +203,7 @@ bool ra_vk_ctx_init(struct ra_ctx *ctx, struct mpvk_ctx *vk, VK_KHR_VIDEO_DECODE_H264_EXTENSION_NAME, VK_KHR_VIDEO_DECODE_H265_EXTENSION_NAME, VK_KHR_VIDEO_QUEUE_EXTENSION_NAME, - // This is a literal string as it's not in the official headers yet. - "VK_MESA_video_decode_av1", + "VK_KHR_video_decode_av1", /* VK_KHR_VIDEO_DECODE_AV1_EXTENSION_NAME */ }; VkPhysicalDeviceDescriptorBufferFeaturesEXT descriptor_buffer_feature = { @@ -310,11 +305,6 @@ char *ra_vk_ctx_get_device_name(struct ra_ctx *ctx) return device_name; } -static int color_depth(struct ra_swapchain *sw) -{ - return 0; // TODO: implement this somehow? -} - static bool start_frame(struct ra_swapchain *sw, struct ra_fbo *out_fbo) { struct priv *p = sw->priv; @@ -364,7 +354,6 @@ static void get_vsync(struct ra_swapchain *sw, } static const struct ra_swapchain_fns vulkan_swapchain = { - .color_depth = color_depth, .start_frame = start_frame, .submit_frame = submit_frame, .swap_buffers = swap_buffers, diff --git a/video/out/vulkan/context_display.c b/video/out/vulkan/context_display.c index 84cef1e..72f73ad 100644 --- a/video/out/vulkan/context_display.c +++ b/video/out/vulkan/context_display.c @@ -15,9 +15,11 @@ * License along with mpv. If not, see <http://www.gnu.org/licenses/>. */ +#include "common/common.h" #include "context.h" #include "options/m_config.h" #include "utils.h" +#include "video/out/placebo/utils.h" #if HAVE_DRM #include <errno.h> @@ -214,35 +216,36 @@ done: } static int print_display_info(struct mp_log *log, const struct m_option *opt, - struct bstr name) { - VkResult res; - VkPhysicalDevice *devices = NULL; + struct bstr name) +{ + void *ta_ctx = talloc_new(NULL); + pl_log pllog = mppl_log_create(ta_ctx, log); + if (!pllog) + goto done; // Create a dummy instance to list the resources - VkInstanceCreateInfo info = { - .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, - .enabledExtensionCount = 1, - .ppEnabledExtensionNames = (const char*[]) { - VK_KHR_DISPLAY_EXTENSION_NAME + mppl_log_set_probing(pllog, true); + pl_vk_inst inst = pl_vk_inst_create(pllog, pl_vk_inst_params( + .extensions = (const char *[]){ + VK_KHR_DISPLAY_EXTENSION_NAME, }, - }; - - VkInstance inst = NULL; - res = vkCreateInstance(&info, NULL, &inst); - if (res != VK_SUCCESS) { + .num_extensions = 1, + )); + mppl_log_set_probing(pllog, false); + if (!inst) { mp_warn(log, "Unable to create Vulkan instance.\n"); goto done; } uint32_t num_devices = 0; - vkEnumeratePhysicalDevices(inst, &num_devices, NULL); - if (!num_devices) { + VkResult res = vkEnumeratePhysicalDevices(inst->instance, &num_devices, NULL); + if (res != VK_SUCCESS || !num_devices) { mp_info(log, "No Vulkan devices detected.\n"); goto done; } - devices = talloc_array(NULL, VkPhysicalDevice, num_devices); - vkEnumeratePhysicalDevices(inst, &num_devices, devices); + VkPhysicalDevice *devices = talloc_array(ta_ctx, VkPhysicalDevice, num_devices); + res = vkEnumeratePhysicalDevices(inst->instance, &num_devices, devices); if (res != VK_SUCCESS) { mp_warn(log, "Failed enumerating physical devices.\n"); goto done; @@ -254,8 +257,9 @@ static int print_display_info(struct mp_log *log, const struct m_option *opt, } done: - talloc_free(devices); - vkDestroyInstance(inst, NULL); + pl_vk_inst_destroy(&inst); + pl_log_destroy(&pllog); + talloc_free(ta_ctx); return M_OPT_EXIT; } @@ -296,7 +300,7 @@ static void open_render_fd(struct ra_ctx *ctx, const char *render_path) p->drm_params.render_fd = open(render_path, O_RDWR | O_CLOEXEC); if (p->drm_params.render_fd == -1) { MP_WARN(ctx, "Failed to open render node: %s\n", - strerror(errno)); + mp_strerror(errno)); } } diff --git a/video/out/vulkan/context_mac.m b/video/out/vulkan/context_mac.m index 8ac6e16..bedd0d4 100644 --- a/video/out/vulkan/context_mac.m +++ b/video/out/vulkan/context_mac.m @@ -15,8 +15,10 @@ * License along with mpv. If not, see <http://www.gnu.org/licenses/>. */ +#import <QuartzCore/QuartzCore.h> + #include "video/out/gpu/context.h" -#include "osdep/macOS_swift.h" +#include "osdep/mac/swift.h" #include "common.h" #include "context.h" @@ -42,6 +44,12 @@ static void mac_vk_swap_buffers(struct ra_ctx *ctx) [p->vo_mac swapBuffer]; } +static void mac_vk_get_vsync(struct ra_ctx *ctx, struct vo_vsync_info *info) +{ + struct priv *p = ctx->priv; + [p->vo_mac fillVsyncWithInfo:info]; +} + static bool mac_vk_init(struct ra_ctx *ctx) { struct priv *p = ctx->priv = talloc_zero(ctx, struct priv); @@ -56,7 +64,7 @@ static bool mac_vk_init(struct ra_ctx *ctx) goto error; VkMetalSurfaceCreateInfoEXT mac_info = { - .sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK, + .sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT, .pNext = NULL, .flags = 0, .pLayer = p->vo_mac.layer, @@ -64,6 +72,7 @@ static bool mac_vk_init(struct ra_ctx *ctx) struct ra_vk_ctx_params params = { .swap_buffers = mac_vk_swap_buffers, + .get_vsync = mac_vk_get_vsync, }; VkInstance inst = vk->vkinst->instance; @@ -85,7 +94,14 @@ error: static bool resize(struct ra_ctx *ctx) { - return ra_vk_ctx_resize(ctx, ctx->vo->dwidth, ctx->vo->dheight); + struct priv *p = ctx->priv; + + if (!p->vo_mac.window) { + return false; + } + CGSize size = p->vo_mac.window.framePixel.size; + + return ra_vk_ctx_resize(ctx, (int)size.width, (int)size.height); } static bool mac_vk_reconfig(struct ra_ctx *ctx) diff --git a/video/out/vulkan/context_wayland.c b/video/out/vulkan/context_wayland.c index 761ff5b..cdf1ba6 100644 --- a/video/out/vulkan/context_wayland.c +++ b/video/out/vulkan/context_wayland.c @@ -118,7 +118,7 @@ static bool resize(struct ra_ctx *ctx) const int32_t height = mp_rect_h(wl->geometry); vo_wayland_set_opaque_region(wl, ctx->opts.want_alpha); - vo_wayland_handle_fractional_scale(wl); + vo_wayland_handle_scale(wl); return ra_vk_ctx_resize(ctx, width, height); } diff --git a/video/out/vulkan/context_win.c b/video/out/vulkan/context_win.c index a89c644..328753f 100644 --- a/video/out/vulkan/context_win.c +++ b/video/out/vulkan/context_win.c @@ -50,6 +50,9 @@ static bool win_init(struct ra_ctx *ctx) if (!vo_w32_init(ctx->vo)) goto error; + if (ctx->opts.want_alpha) + vo_w32_set_transparency(ctx->vo, ctx->opts.want_alpha); + VkWin32SurfaceCreateInfoKHR wininfo = { .sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR, .hinstance = HINST_THISCOMPONENT, @@ -96,11 +99,17 @@ static int win_control(struct ra_ctx *ctx, int *events, int request, void *arg) return ret; } +static void win_update_render_opts(struct ra_ctx *ctx) +{ + vo_w32_set_transparency(ctx->vo, ctx->opts.want_alpha); +} + const struct ra_ctx_fns ra_ctx_vulkan_win = { - .type = "vulkan", - .name = "winvk", - .reconfig = win_reconfig, - .control = win_control, - .init = win_init, - .uninit = win_uninit, + .type = "vulkan", + .name = "winvk", + .reconfig = win_reconfig, + .control = win_control, + .update_render_opts = win_update_render_opts, + .init = win_init, + .uninit = win_uninit, }; diff --git a/video/out/w32_common.c b/video/out/w32_common.c index e6a4670..36f48b9 100644 --- a/video/out/w32_common.c +++ b/video/out/w32_common.c @@ -40,6 +40,7 @@ #include "w32_common.h" #include "win32/displayconfig.h" #include "win32/droptarget.h" +#include "win32/menu.h" #include "osdep/io.h" #include "osdep/threads.h" #include "osdep/w32_keyboard.h" @@ -58,10 +59,17 @@ EXTERN_C IMAGE_DOS_HEADER __ImageBase; #define DWMWA_USE_IMMERSIVE_DARK_MODE 20 #endif +#ifndef DWMWA_VISIBLE_FRAME_BORDER_THICKNESS +#define DWMWA_VISIBLE_FRAME_BORDER_THICKNESS 37 +#endif -//Older MinGW compatibility +#ifndef DWMWA_WINDOW_CORNER_PREFERENCE #define DWMWA_WINDOW_CORNER_PREFERENCE 33 +#endif + +#ifndef DWMWA_SYSTEMBACKDROP_TYPE #define DWMWA_SYSTEMBACKDROP_TYPE 38 +#endif #ifndef DPI_ENUMS_DECLARED typedef enum MONITOR_DPI_TYPE { @@ -75,10 +83,12 @@ typedef enum MONITOR_DPI_TYPE { #define rect_w(r) ((r).right - (r).left) #define rect_h(r) ((r).bottom - (r).top) +#define WM_SHOWMENU (WM_USER + 1) + struct w32_api { HRESULT (WINAPI *pGetDpiForMonitor)(HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*); - BOOL (WINAPI *pImmDisableIME)(DWORD); BOOL (WINAPI *pAdjustWindowRectExForDpi)(LPRECT lpRect, DWORD dwStyle, BOOL bMenu, DWORD dwExStyle, UINT dpi); + int (WINAPI *pGetSystemMetricsForDpi)(int nIndex, UINT dpi); BOOLEAN (WINAPI *pShouldAppsUseDarkMode)(void); DWORD (WINAPI *pSetPreferredAppMode)(DWORD mode); }; @@ -102,6 +112,8 @@ struct vo_w32_state { HHOOK parent_win_hook; HWINEVENTHOOK parent_evt_hook; + struct menu_ctx *menu_ctx; + HMONITOR monitor; // Handle of the current screen char *color_profile; // Path of the current screen's color profile @@ -132,7 +144,7 @@ struct vo_w32_state { atomic_uint event_flags; BOOL tracking; - TRACKMOUSEEVENT trackEvent; + TRACKMOUSEEVENT track_event; int mouse_x; int mouse_y; @@ -156,8 +168,8 @@ struct vo_w32_state { ITaskbarList2 *taskbar_list; ITaskbarList3 *taskbar_list3; - UINT tbtnCreatedMsg; - bool tbtnCreated; + UINT tbtn_created_msg; + bool tbtn_created; struct voctrl_playback_state current_pstate; @@ -181,8 +193,21 @@ struct vo_w32_state { HANDLE avrt_handle; bool cleared; + bool dragging; + bool start_dragging; + BOOL win_arranging; + + bool conversion_mode_init; + bool unmaximize; }; +static inline int get_system_metrics(struct vo_w32_state *w32, int metric) +{ + return w32->api.pGetSystemMetricsForDpi + ? w32->api.pGetSystemMetricsForDpi(metric, w32->dpi) + : GetSystemMetrics(metric); +} + static void adjust_window_rect(struct vo_w32_state *w32, HWND hwnd, RECT *rc) { if (!w32->opts->border) @@ -197,13 +222,67 @@ static void adjust_window_rect(struct vo_w32_state *w32, HWND hwnd, RECT *rc) } } +static bool check_windows10_build(DWORD build) +{ + OSVERSIONINFOEXW osvi = { + .dwOSVersionInfoSize = sizeof(osvi), + .dwMajorVersion = HIBYTE(_WIN32_WINNT_WIN10), + .dwMinorVersion = LOBYTE(_WIN32_WINNT_WIN10), + .dwBuildNumber = build, + }; + + DWORD type = VER_MAJORVERSION | VER_MINORVERSION | VER_BUILDNUMBER; + + ULONGLONG mask = 0; + mask = VerSetConditionMask(mask, VER_MAJORVERSION, VER_GREATER_EQUAL); + mask = VerSetConditionMask(mask, VER_MINORVERSION, VER_GREATER_EQUAL); + mask = VerSetConditionMask(mask, VER_BUILDNUMBER, VER_GREATER_EQUAL); + + return VerifyVersionInfoW(&osvi, type, mask); +} + +// Get adjusted title bar height, only relevant for --title-bar=no +static int get_title_bar_height(struct vo_w32_state *w32) +{ + assert(!w32->opts->title_bar && w32->opts->border); + UINT visible_border = 0; + // Only available on Windows 11, check in case it's backported and breaks + // WM_NCCALCSIZE exception for Windows 10. + if (check_windows10_build(22000)) { + DwmGetWindowAttribute(w32->window, DWMWA_VISIBLE_FRAME_BORDER_THICKNESS, + &visible_border, sizeof(visible_border)); + } + int top_bar = IsMaximized(w32->window) + ? get_system_metrics(w32, SM_CYFRAME) + + get_system_metrics(w32, SM_CXPADDEDBORDER) + : visible_border; + return top_bar; +} + static void add_window_borders(struct vo_w32_state *w32, HWND hwnd, RECT *rc) { RECT win = *rc; adjust_window_rect(w32, hwnd, rc); // Adjust for title bar height that will be hidden in WM_NCCALCSIZE - if (w32->opts->border && !w32->opts->title_bar && !w32->current_fs) - rc->top -= rc->top - win.top; + // Keep the frame border. On Windows 10 the top border is not retained. + // It appears that DWM draws the title bar with its full height, extending + // outside the window area. Essentially, there is a bug in DWM, preventing + // the adjustment of the title bar height. This issue occurs when both the + // top and left client areas are non-zero in WM_NCCALCSIZE. If the left NC + // area is set to 0, the title bar is drawn correctly with the adjusted + // height. To mitigate this problem, set the top NC area to zero. The issue + // doesn't happen on Windows 11 or when DWM NC drawing is disabled with + // DWMWA_NCRENDERING_POLICY. We aim to avoid the manual drawing the border + // and want the DWM look and feel, so skip the top border on Windows 10. + // Also DWMWA_VISIBLE_FRAME_BORDER_THICKNESS is available only on Windows 11, + // so it would be hard to guess this size correctly on Windows 10 anyway. + if (w32->opts->border && !w32->opts->title_bar && !w32->current_fs && + (GetWindowLongPtrW(w32->window, GWL_STYLE) & WS_CAPTION)) + { + if (!check_windows10_build(22000) && !IsMaximized(w32->window)) + *rc = win; + rc->top = win.top - get_title_bar_height(w32); + } } // basically a reverse AdjustWindowRect (win32 doesn't appear to have this) @@ -226,13 +305,13 @@ static LRESULT borderless_nchittest(struct vo_w32_state *w32, int x, int y) if (!GetWindowRect(w32->window, &rc)) return HTNOWHERE; - POINT frame = {GetSystemMetrics(SM_CXSIZEFRAME), - GetSystemMetrics(SM_CYSIZEFRAME)}; + POINT frame = {get_system_metrics(w32, SM_CXSIZEFRAME), + get_system_metrics(w32, SM_CYSIZEFRAME)}; if (w32->opts->border) { - frame.x += GetSystemMetrics(SM_CXPADDEDBORDER); - frame.y += GetSystemMetrics(SM_CXPADDEDBORDER); + frame.x += get_system_metrics(w32, SM_CXPADDEDBORDER); + frame.y += get_system_metrics(w32, SM_CXPADDEDBORDER); if (!w32->opts->title_bar) - rc.top -= GetSystemMetrics(SM_CXPADDEDBORDER); + rc.top -= get_system_metrics(w32, SM_CXPADDEDBORDER); } InflateRect(&rc, -frame.x, -frame.y); @@ -344,8 +423,8 @@ static void clear_keyboard_buffer(void) // Use the method suggested by Michael Kaplan to clear any pending dead // keys from the current keyboard layout. See: - // https://web.archive.org/web/20101004154432/http://blogs.msdn.com/b/michkap/archive/2006/04/06/569632.aspx - // https://web.archive.org/web/20100820152419/http://blogs.msdn.com/b/michkap/archive/2007/10/27/5717859.aspx + // <https://web.archive.org/web/20101004154432/http://blogs.msdn.com/b/michkap/archive/2006/04/06/569632.aspx> + // <https://web.archive.org/web/20100820152419/http://blogs.msdn.com/b/michkap/archive/2007/10/27/5717859.aspx> do { ret = ToUnicode(vkey, scancode, keys, buf, MP_ARRAY_SIZE(buf), 0); } while (ret < 0); @@ -356,7 +435,7 @@ static int to_unicode(UINT vkey, UINT scancode, const BYTE keys[256]) // This wraps ToUnicode to be stateless and to return only one character // Make the buffer 10 code units long to be safe, same as here: - // https://web.archive.org/web/20101013215215/http://blogs.msdn.com/b/michkap/archive/2006/03/24/559169.aspx + // <https://web.archive.org/web/20101013215215/http://blogs.msdn.com/b/michkap/archive/2006/03/24/559169.aspx> wchar_t buf[10] = { 0 }; // Dead keys aren't useful for key shortcuts, so clear the keyboard state @@ -428,10 +507,6 @@ static bool handle_appcommand(struct vo_w32_state *w32, UINT cmd) static void handle_key_down(struct vo_w32_state *w32, UINT vkey, UINT scancode) { - // Ignore key repeat - if (scancode & KF_REPEAT) - return; - int mpkey = mp_w32_vkey_to_mpkey(vkey, scancode & KF_EXTENDED); if (!mpkey) { mpkey = decode_key(w32, vkey, scancode & (0xff | KF_EXTENDED)); @@ -439,7 +514,8 @@ static void handle_key_down(struct vo_w32_state *w32, UINT vkey, UINT scancode) return; } - mp_input_put_key(w32->input_ctx, mpkey | mod_state(w32) | MP_KEY_STATE_DOWN); + int state = w32->opts->native_keyrepeat ? 0 : MP_KEY_STATE_DOWN; + mp_input_put_key(w32->input_ctx, mpkey | mod_state(w32) | state); } static void handle_key_up(struct vo_w32_state *w32, UINT vkey, UINT scancode) @@ -456,9 +532,9 @@ static void handle_key_up(struct vo_w32_state *w32, UINT vkey, UINT scancode) } } -static bool handle_char(struct vo_w32_state *w32, wchar_t wc) +static bool handle_char(struct vo_w32_state *w32, WPARAM wc, bool decode) { - int c = decode_utf16(w32, wc); + int c = decode ? decode_utf16(w32, wc) : wc; if (c == 0) return true; @@ -469,23 +545,33 @@ static bool handle_char(struct vo_w32_state *w32, wchar_t wc) return true; } +static void begin_dragging(struct vo_w32_state *w32) +{ + if (w32->current_fs || + mp_input_test_dragging(w32->input_ctx, w32->mouse_x, w32->mouse_y)) + return; + // Window dragging hack + ReleaseCapture(); + // The dragging model loop is entered at SendMessage() here. + // Unfortunately, the w32->current_fs value is stale because the + // input is handled in a different thread, and we cannot wait for + // an up-to-date value before entering the model loop if dragging + // needs to be kept resonsive. + // Workaround this by intercepting the loop in the WM_MOVING message, + // where the up-to-date value is available. + SystemParametersInfoW(SPI_GETWINARRANGING, 0, &w32->win_arranging, 0); + w32->dragging = true; + SendMessage(w32->window, WM_NCLBUTTONDOWN, HTCAPTION, 0); + w32->dragging = false; + SystemParametersInfoW(SPI_SETWINARRANGING, w32->win_arranging, 0, 0); + + mp_input_put_key(w32->input_ctx, MP_INPUT_RELEASE_ALL); +} + static bool handle_mouse_down(struct vo_w32_state *w32, int btn, int x, int y) { btn |= mod_state(w32); mp_input_put_key(w32->input_ctx, btn | MP_KEY_STATE_DOWN); - - if (btn == MP_MBTN_LEFT && !w32->current_fs && - !mp_input_test_dragging(w32->input_ctx, x, y)) - { - // Window dragging hack - ReleaseCapture(); - SendMessage(w32->window, WM_NCLBUTTONDOWN, HTCAPTION, 0); - mp_input_put_key(w32->input_ctx, MP_MBTN_LEFT | MP_KEY_STATE_UP); - - // Indicate the message was handled, so DefWindowProc won't be called - return true; - } - SetCapture(w32->window); return false; } @@ -566,18 +652,20 @@ static double get_refresh_rate_from_gdi(const wchar_t *device) static char *get_color_profile(void *ctx, const wchar_t *device) { char *name = NULL; + wchar_t *wname = NULL; HDC ic = CreateICW(device, NULL, NULL, NULL); if (!ic) goto done; - wchar_t wname[MAX_PATH + 1]; - if (!GetICMProfileW(ic, &(DWORD){ MAX_PATH }, wname)) + wname = talloc_array(NULL, wchar_t, MP_PATH_MAX); + if (!GetICMProfileW(ic, &(DWORD){ MP_PATH_MAX - 1 }, wname)) goto done; name = mp_to_utf8(ctx, wname); done: if (ic) DeleteDC(ic); + talloc_free(wname); return name; } @@ -603,7 +691,7 @@ static void update_dpi(struct vo_w32_state *w32) } w32->dpi = dpi; - w32->dpi_scale = w32->opts->hidpi_window_scale ? w32->dpi / 96.0 : 1.0; + w32->dpi_scale = w32->dpi / 96.0; signal_events(w32, VO_EVENT_DPI); } @@ -660,7 +748,7 @@ static void update_playback_state(struct vo_w32_state *w32) { struct voctrl_playback_state *pstate = &w32->current_pstate; - if (!w32->taskbar_list3 || !w32->tbtnCreated) + if (!w32->taskbar_list3 || !w32->tbtn_created) return; if (!pstate->playing || !pstate->taskbar_progress) { @@ -743,10 +831,10 @@ static RECT get_screen_area(struct vo_w32_state *w32) { // Handle --fs-screen=all if (w32->current_fs && w32->opts->fsscreen_id == -2) { - const int x = GetSystemMetrics(SM_XVIRTUALSCREEN); - const int y = GetSystemMetrics(SM_YVIRTUALSCREEN); - return (RECT) { x, y, x + GetSystemMetrics(SM_CXVIRTUALSCREEN), - y + GetSystemMetrics(SM_CYVIRTUALSCREEN) }; + const int x = get_system_metrics(w32, SM_XVIRTUALSCREEN); + const int y = get_system_metrics(w32, SM_YVIRTUALSCREEN); + return (RECT) { x, y, x + get_system_metrics(w32, SM_CXVIRTUALSCREEN), + y + get_system_metrics(w32, SM_CYVIRTUALSCREEN) }; } return get_monitor_info(w32).rcMonitor; } @@ -842,6 +930,13 @@ static bool snap_to_screen_edges(struct vo_w32_state *w32, RECT *rc) return true; } +static bool is_high_contrast(void) +{ + HIGHCONTRAST hc = {sizeof(hc)}; + SystemParametersInfo(SPI_GETHIGHCONTRAST, sizeof(hc), &hc, 0); + return hc.dwFlags & HCF_HIGHCONTRASTON; +} + static DWORD update_style(struct vo_w32_state *w32, DWORD style) { const DWORD NO_FRAME = WS_OVERLAPPED | WS_MINIMIZEBOX | WS_THICKFRAME; @@ -853,17 +948,12 @@ static DWORD update_style(struct vo_w32_state *w32, DWORD style) style |= FULLSCREEN; } else { style |= w32->opts->border ? FRAME : NO_FRAME; + if (!w32->opts->title_bar && is_high_contrast()) + style &= ~WS_CAPTION; } return style; } -static LONG get_title_bar_height(struct vo_w32_state *w32) -{ - RECT rc = {0}; - adjust_window_rect(w32, w32->window, &rc); - return -rc.top; -} - static void update_window_style(struct vo_w32_state *w32) { if (w32->parent) @@ -1006,14 +1096,13 @@ static void update_minimized_state(struct vo_w32_state *w32) } } -static void update_maximized_state(struct vo_w32_state *w32) +static void update_maximized_state(struct vo_w32_state *w32, bool leaving_fullscreen) { if (w32->parent) return; - // Don't change the maximized state in fullscreen for now. In future, this - // should be made to apply the maximized state on leaving fullscreen. - if (w32->current_fs) + // Apply the maximized state on leaving fullscreen. + if (w32->current_fs && !leaving_fullscreen) return; WINDOWPLACEMENT wp = { .length = sizeof wp }; @@ -1065,11 +1154,24 @@ static void update_window_state(struct vo_w32_state *w32) wr.left, wr.top, rect_w(wr), rect_h(wr), SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOOWNERZORDER); + // Unmaximize the window if a size change is requested because SetWindowPos + // doesn't change the window maximized state. + // ShowWindow(SW_SHOWNOACTIVATE) can't be used here because it tries to + // "restore" the window to its size before it's maximized. + if (w32->unmaximize) { + WINDOWPLACEMENT wp = { .length = sizeof wp }; + GetWindowPlacement(w32->window, &wp); + wp.showCmd = SW_SHOWNOACTIVATE; + wp.rcNormalPosition = wr; + SetWindowPlacement(w32->window, &wp); + w32->unmaximize = false; + } + // Show the window if it's not yet visible if (!is_visible(w32->window)) { if (w32->opts->window_minimized) { ShowWindow(w32->window, SW_SHOWMINNOACTIVE); - update_maximized_state(w32); // Set the WPF_RESTORETOMAXIMIZED flag + update_maximized_state(w32, false); // Set the WPF_RESTORETOMAXIMIZED flag } else if (w32->opts->window_maximized) { ShowWindow(w32->window, SW_SHOWMAXIMIZED); } else { @@ -1166,13 +1268,9 @@ static void update_dark_mode(const struct vo_w32_state *w32) if (w32->api.pSetPreferredAppMode) w32->api.pSetPreferredAppMode(1); // allow dark mode - HIGHCONTRAST hc = {sizeof(hc)}; - SystemParametersInfo(SPI_GETHIGHCONTRAST, sizeof(hc), &hc, 0); - bool high_contrast = hc.dwFlags & HCF_HIGHCONTRASTON; - // if pShouldAppsUseDarkMode is not available, just assume it to be true - const BOOL use_dark_mode = !high_contrast && (!w32->api.pShouldAppsUseDarkMode || - w32->api.pShouldAppsUseDarkMode()); + const BOOL use_dark_mode = !is_high_contrast() && (!w32->api.pShouldAppsUseDarkMode || + w32->api.pShouldAppsUseDarkMode()); SetWindowTheme(w32->window, use_dark_mode ? L"DarkMode_Explorer" : L"", NULL); @@ -1190,6 +1288,37 @@ static void update_backdrop(const struct vo_w32_state *w32) &backdropType, sizeof(backdropType)); } +static void update_cursor_passthrough(const struct vo_w32_state *w32) +{ + if (w32->parent) + return; + + LONG_PTR exstyle = GetWindowLongPtrW(w32->window, GWL_EXSTYLE); + if (exstyle) { + if (w32->opts->cursor_passthrough) { + SetWindowLongPtrW(w32->window, GWL_EXSTYLE, exstyle | WS_EX_LAYERED | WS_EX_TRANSPARENT); + // This is required, otherwise the titlebar disappears. + SetLayeredWindowAttributes(w32->window, 0, 255, LWA_ALPHA); + } else { + SetWindowLongPtrW(w32->window, GWL_EXSTYLE, exstyle & ~(WS_EX_LAYERED | WS_EX_TRANSPARENT)); + } + } +} + +static void set_ime_conversion_mode(const struct vo_w32_state *w32, DWORD mode) +{ + if (w32->parent) + return; + + HIMC imc = ImmGetContext(w32->window); + if (imc) { + DWORD sentence_mode; + if (ImmGetConversionStatus(imc, NULL, &sentence_mode)) + ImmSetConversionStatus(imc, mode, sentence_mode); + ImmReleaseContext(w32->window, imc); + } +} + static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { @@ -1216,6 +1345,12 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, mp_dispatch_queue_process(w32->dispatch, 0); w32->in_dispatch = false; } + // Start window dragging if the flag is set by the voctrl. + // This is processed here to avoid blocking the dispatch queue. + if (w32->start_dragging) { + w32->start_dragging = false; + begin_dragging(w32); + } switch (message) { case WM_ERASEBKGND: @@ -1241,6 +1376,18 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, case WM_MOVING: { w32->moving = true; RECT *rc = (RECT*)lParam; + // Prevent the window from being moved if the window dragging hack + // is active, and the window is currently in fullscreen. + if (w32->dragging && w32->current_fs) { + // Temporarily disable window arrangement to prevent aero shake + // from being activated. The original system setting will be restored + // after the dragging hack ends. + if (w32->win_arranging) { + SystemParametersInfoW(SPI_SETWINARRANGING, FALSE, 0, 0); + } + *rc = w32->windowrc; + return TRUE; + } if (snap_to_screen_edges(w32, rc)) return TRUE; break; @@ -1344,6 +1491,14 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, w32->window = NULL; PostQuitMessage(0); break; + case WM_COMMAND: { + const char *cmd = mp_win32_menu_get_cmd(w32->menu_ctx, LOWORD(wParam)); + if (cmd) { + mp_cmd_t *cmdt = mp_input_parse_cmd(w32->input_ctx, bstr0(cmd), ""); + mp_input_queue_cmd(w32->input_ctx, cmdt); + } + break; + } case WM_SYSCOMMAND: switch (wParam & 0xFFF0) { case SC_SCREENSAVE: @@ -1404,9 +1559,16 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, break; case WM_CHAR: case WM_SYSCHAR: - if (handle_char(w32, wParam)) + if (handle_char(w32, wParam, true)) return 0; break; + case WM_UNICHAR: + if (wParam == UNICODE_NOCHAR) { + return TRUE; + } else if (handle_char(w32, wParam, false)) { + return 0; + } + break; case WM_KILLFOCUS: mp_input_put_key(w32->input_ctx, MP_INPUT_RELEASE_ALL); w32->focused = false; @@ -1431,12 +1593,13 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, break; case WM_MOUSEMOVE: { if (!w32->tracking) { - w32->tracking = TrackMouseEvent(&w32->trackEvent); + w32->tracking = TrackMouseEvent(&w32->track_event); mp_input_put_key(w32->input_ctx, MP_KEY_MOUSE_ENTER); } // Windows can send spurious mouse events, which would make the mpv // core unhide the mouse cursor on completely unrelated events. See: - // https://blogs.msdn.com/b/oldnewthing/archive/2003/10/01/55108.aspx + // <https://web.archive.org/web/20100821161603/ + // https://blogs.msdn.com/b/oldnewthing/archive/2003/10/01/55108.aspx> int x = GET_X_LPARAM(lParam); int y = GET_Y_LPARAM(lParam); if (x != w32->mouse_x || y != w32->mouse_y) { @@ -1480,16 +1643,18 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, handle_mouse_down(w32, HIWORD(wParam) == 1 ? MP_MBTN_BACK : MP_MBTN_FORWARD, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); - break; + return TRUE; case WM_XBUTTONUP: handle_mouse_up(w32, HIWORD(wParam) == 1 ? MP_MBTN_BACK : MP_MBTN_FORWARD); - break; + return TRUE; case WM_DISPLAYCHANGE: force_update_display_info(w32); break; case WM_SETTINGCHANGE: update_dark_mode(w32); + update_window_style(w32); + update_window_state(w32); break; case WM_NCCALCSIZE: if (!w32->opts->border) @@ -1497,15 +1662,51 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, // Apparently removing WS_CAPTION disables some window animation, instead // just reduce non-client size to remove title bar. if (wParam && lParam && w32->opts->border && !w32->opts->title_bar && - !w32->current_fs && !w32->parent) + !w32->current_fs && !w32->parent && + (GetWindowLongPtrW(w32->window, GWL_STYLE) & WS_CAPTION)) { - ((LPNCCALCSIZE_PARAMS) lParam)->rgrc[0].top -= get_title_bar_height(w32); + // Remove all NC area on Windows 10 due to inability to control the + // top bar height before Windows 11. + if (!check_windows10_build(22000) && !IsMaximized(w32->window)) + return 0; + RECT r = {0}; + adjust_window_rect(w32, w32->window, &r); + NCCALCSIZE_PARAMS *p = (LPNCCALCSIZE_PARAMS)lParam; + p->rgrc[0].top += r.top + get_title_bar_height(w32); + } + break; + case WM_IME_STARTCOMPOSITION: { + HIMC imc = ImmGetContext(w32->window); + if (imc) { + COMPOSITIONFORM cf = {.dwStyle = CFS_POINT, .ptCurrentPos = {0, 0}}; + ImmSetCompositionWindow(imc, &cf); + ImmReleaseContext(w32->window, imc); } break; } + case WM_CREATE: + // The IME can only be changed to alphanumeric input after it's initialized. + // Unfortunately, there is no way to know when this happens, as + // none of the WM_CREATE, WM_INPUTLANGCHANGE, or WM_IME_* messages work. + // This works if the IME is initialized within a short time after + // the window is created. Otherwise, fallback to setting alphanumeric mode on + // the first keypress. + SetTimer(w32->window, (UINT_PTR)WM_CREATE, 250, NULL); + break; + case WM_TIMER: + if (wParam == WM_CREATE) { + // Default to alphanumeric input when the IME is first initialized. + set_ime_conversion_mode(w32, IME_CMODE_ALPHANUMERIC); + return 0; + } + break; + case WM_SHOWMENU: + mp_win32_menu_show(w32->menu_ctx, w32->window); + break; + } - if (message == w32->tbtnCreatedMsg) { - w32->tbtnCreated = true; + if (message == w32->tbtn_created_msg) { + w32->tbtn_created = true; update_playback_state(w32); return 0; } @@ -1609,13 +1810,31 @@ static void remove_parent_hook(struct vo_w32_state *w32) UnhookWinEvent(w32->parent_evt_hook); } +static bool is_key_message(UINT msg) +{ + return msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN || + msg == WM_KEYUP || msg == WM_SYSKEYUP; +} + // Dispatch incoming window events and handle them. // This returns only when the thread is asked to terminate. static void run_message_loop(struct vo_w32_state *w32) { MSG msg; - while (GetMessageW(&msg, 0, 0, 0) > 0) + while (!w32->destroyed && GetMessageW(&msg, 0, 0, 0) > 0) { + // Change the conversion mode on the first keypress, in case the timer + // solution fails. Note that this leaves the mode indicator in the language + // bar showing the original mode until a key is pressed. + if (is_key_message(msg.message) && !w32->conversion_mode_init) { + set_ime_conversion_mode(w32, IME_CMODE_ALPHANUMERIC); + w32->conversion_mode_init = true; + KillTimer(w32->window, (UINT_PTR)WM_CREATE); + } + // Only send IME messages to TranslateMessage + if (is_key_message(msg.message) && msg.wParam == VK_PROCESSKEY) + TranslateMessage(&msg); DispatchMessageW(&msg); + } // Even if the message loop somehow exits, we still have to respond to // external requests until termination is requested. @@ -1623,9 +1842,8 @@ static void run_message_loop(struct vo_w32_state *w32) mp_dispatch_queue_process(w32->dispatch, 1000); } -static void gui_thread_reconfig(void *ptr) +static void window_reconfig(struct vo_w32_state *w32, bool force) { - struct vo_w32_state *w32 = ptr; struct vo *vo = w32->vo; RECT r = get_working_area(w32); @@ -1648,14 +1866,14 @@ static void gui_thread_reconfig(void *ptr) vo_calc_window_geometry3(vo, &screen, &mon, w32->dpi_scale, &geo); vo_apply_window_geometry(vo, &geo); - bool reset_size = (w32->o_dwidth != vo->dwidth || + bool reset_size = ((w32->o_dwidth != vo->dwidth || w32->o_dheight != vo->dheight) && - w32->opts->auto_window_resize; + w32->opts->auto_window_resize) || force; w32->o_dwidth = vo->dwidth; w32->o_dheight = vo->dheight; - if (!w32->parent && !w32->window_bounds_initialized) { + if (!w32->parent && (!w32->window_bounds_initialized || force)) { SetRect(&w32->windowrc, geo.win.x0, geo.win.y0, geo.win.x0 + vo->dwidth, geo.win.y0 + vo->dheight); w32->prev_windowrc = w32->windowrc; @@ -1686,6 +1904,11 @@ finish: reinit_window_state(w32); } +static void gui_thread_reconfig(void *ptr) +{ + window_reconfig(ptr, false); +} + // Resize the window. On the first call, it's also made visible. void vo_w32_config(struct vo *vo) { @@ -1704,26 +1927,15 @@ static void w32_api_load(struct vo_w32_state *w32) // Available since Win10 w32->api.pAdjustWindowRectExForDpi = !user32_dll ? NULL : (void *)GetProcAddress(user32_dll, "AdjustWindowRectExForDpi"); - - // imm32.dll must be loaded dynamically - // to account for machines without East Asian language support - HMODULE imm32_dll = LoadLibraryW(L"imm32.dll"); - w32->api.pImmDisableIME = !imm32_dll ? NULL : - (void *)GetProcAddress(imm32_dll, "ImmDisableIME"); + w32->api.pGetSystemMetricsForDpi = !user32_dll ? NULL : + (void *)GetProcAddress(user32_dll, "GetSystemMetricsForDpi"); // Dark mode related functions, available since the 1809 Windows 10 update // Check the Windows build version as on previous versions used ordinals // may point to unexpected code/data. Alternatively could check uxtheme.dll // version directly, but it is little bit more boilerplate code, and build // number is good enough check. - void (WINAPI *pRtlGetNtVersionNumbers)(LPDWORD, LPDWORD, LPDWORD) = - (void *)GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "RtlGetNtVersionNumbers"); - - DWORD major, build; - pRtlGetNtVersionNumbers(&major, NULL, &build); - build &= ~0xF0000000; - - HMODULE uxtheme_dll = (major < 10 || build < 17763) ? NULL : + HMODULE uxtheme_dll = !check_windows10_build(17763) ? NULL : GetModuleHandle(L"uxtheme.dll"); w32->api.pShouldAppsUseDarkMode = !uxtheme_dll ? NULL : (void *)GetProcAddress(uxtheme_dll, MAKEINTRESOURCEA(132)); @@ -1741,10 +1953,6 @@ static MP_THREAD_VOID gui_thread(void *ptr) w32_api_load(w32); - // Disables the IME for windows on this thread - if (w32->api.pImmDisableIME) - w32->api.pImmDisableIME(0); - if (w32->opts->WinID >= 0) w32->parent = (HWND)(intptr_t)(w32->opts->WinID); @@ -1776,6 +1984,8 @@ static MP_THREAD_VOID gui_thread(void *ptr) update_affinity(w32); if (w32->opts->backdrop_type) update_backdrop(w32); + if (w32->opts->cursor_passthrough) + update_cursor_passthrough(w32); if (SUCCEEDED(OleInitialize(NULL))) { ole_ok = true; @@ -1805,7 +2015,7 @@ static MP_THREAD_VOID gui_thread(void *ptr) ITaskbarList3_Release(w32->taskbar_list3); w32->taskbar_list3 = NULL; } else { - w32->tbtnCreatedMsg = RegisterWindowMessage(L"TaskbarButtonCreated"); + w32->tbtn_created_msg = RegisterWindowMessage(L"TaskbarButtonCreated"); } } } else { @@ -1813,7 +2023,7 @@ static MP_THREAD_VOID gui_thread(void *ptr) } w32->tracking = FALSE; - w32->trackEvent = (TRACKMOUSEEVENT){ + w32->track_event = (TRACKMOUSEEVENT){ .cbSize = sizeof(TRACKMOUSEEVENT), .dwFlags = TME_LEAVE, .hwndTrack = w32->window, @@ -1866,6 +2076,7 @@ bool vo_w32_init(struct vo *vo) .dispatch = mp_dispatch_create(w32), }; w32->opts = w32->opts_cache->opts; + w32->menu_ctx = mp_win32_menu_init(); vo->w32 = w32; if (mp_thread_create(&w32->thread, gui_thread, w32)) @@ -1949,6 +2160,8 @@ static int gui_thread_control(struct vo_w32_state *w32, int request, void *arg) struct mp_vo_opts *vo_opts = w32->opts_cache->opts; if (changed_option == &vo_opts->fullscreen) { + if (!vo_opts->fullscreen) + update_maximized_state(w32, true); reinit_window_state(w32); } else if (changed_option == &vo_opts->window_affinity) { update_affinity(w32); @@ -1956,6 +2169,8 @@ static int gui_thread_control(struct vo_w32_state *w32, int request, void *arg) update_window_state(w32); } else if (changed_option == &vo_opts->backdrop_type) { update_backdrop(w32); + } else if (changed_option == &vo_opts->cursor_passthrough) { + update_cursor_passthrough(w32); } else if (changed_option == &vo_opts->border || changed_option == &vo_opts->title_bar) { @@ -1964,9 +2179,16 @@ static int gui_thread_control(struct vo_w32_state *w32, int request, void *arg) } else if (changed_option == &vo_opts->window_minimized) { update_minimized_state(w32); } else if (changed_option == &vo_opts->window_maximized) { - update_maximized_state(w32); + update_maximized_state(w32, false); } else if (changed_option == &vo_opts->window_corners) { update_corners_pref(w32); + } else if (changed_option == &vo_opts->geometry || changed_option == &vo_opts->autofit || + changed_option == &vo_opts->autofit_smaller || changed_option == &vo_opts->autofit_larger) + { + if (w32->opts->window_maximized) { + w32->unmaximize = true; + } + window_reconfig(w32, true); } } @@ -1989,8 +2211,8 @@ static int gui_thread_control(struct vo_w32_state *w32, int request, void *arg) return VO_FALSE; RECT *rc = w32->current_fs ? &w32->prev_windowrc : &w32->windowrc; - s[0] = rect_w(*rc) / w32->dpi_scale; - s[1] = rect_h(*rc) / w32->dpi_scale; + s[0] = rect_w(*rc); + s[1] = rect_h(*rc); return VO_TRUE; } case VOCTRL_SET_UNFS_WINDOW_SIZE: { @@ -1999,12 +2221,12 @@ static int gui_thread_control(struct vo_w32_state *w32, int request, void *arg) if (!w32->window_bounds_initialized) return VO_FALSE; - s[0] *= w32->dpi_scale; - s[1] *= w32->dpi_scale; - RECT *rc = w32->current_fs ? &w32->prev_windowrc : &w32->windowrc; resize_and_move_rect(w32, rc, s[0], s[1]); + if (w32->opts->window_maximized) { + w32->unmaximize = true; + } w32->fit_on_screen = true; reinit_window_state(w32); return VO_TRUE; @@ -2064,6 +2286,15 @@ static int gui_thread_control(struct vo_w32_state *w32, int request, void *arg) case VOCTRL_GET_FOCUSED: *(bool *)arg = w32->focused; return VO_TRUE; + case VOCTRL_BEGIN_DRAGGING: + w32->start_dragging = true; + return VO_TRUE; + case VOCTRL_SHOW_MENU: + PostMessageW(w32->window, WM_SHOWMENU, 0, 0); + return VO_TRUE; + case VOCTRL_UPDATE_MENU: + mp_win32_menu_update(w32->menu_ctx, (struct mpv_node *)arg); + return VO_TRUE; } return VO_NOTIMPL; } @@ -2127,6 +2358,7 @@ void vo_w32_uninit(struct vo *vo) AvRevertMmThreadCharacteristics(w32->avrt_handle); + mp_win32_menu_uninit(w32->menu_ctx); talloc_free(w32); vo->w32 = NULL; } @@ -2142,3 +2374,24 @@ void vo_w32_run_on_thread(struct vo *vo, void (*cb)(void *ctx), void *ctx) struct vo_w32_state *w32 = vo->w32; mp_dispatch_run(w32->dispatch, cb, ctx); } + +void vo_w32_set_transparency(struct vo *vo, bool enable) +{ + struct vo_w32_state *w32 = vo->w32; + if (w32->parent) + return; + + DWM_BLURBEHIND dbb = {0}; + if (enable) { + HRGN rgn = CreateRectRgn(0, 0, -1, -1); + dbb.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION; + dbb.hRgnBlur = rgn; + dbb.fEnable = TRUE; + DwmEnableBlurBehindWindow(w32->window, &dbb); + DeleteObject(rgn); + } else { + dbb.dwFlags = DWM_BB_ENABLE; + dbb.fEnable = FALSE; + DwmEnableBlurBehindWindow(w32->window, &dbb); + } +} diff --git a/video/out/w32_common.h b/video/out/w32_common.h index 528b216..bc22a2d 100644 --- a/video/out/w32_common.h +++ b/video/out/w32_common.h @@ -32,5 +32,6 @@ int vo_w32_control(struct vo *vo, int *events, int request, void *arg); void vo_w32_config(struct vo *vo); HWND vo_w32_hwnd(struct vo *vo); void vo_w32_run_on_thread(struct vo *vo, void (*cb)(void *ctx), void *ctx); +void vo_w32_set_transparency(struct vo *vo, bool enable); #endif /* MPLAYER_W32_COMMON_H */ diff --git a/video/out/wayland_common.c b/video/out/wayland_common.c index 589135f..4a86c21 100644 --- a/video/out/wayland_common.c +++ b/video/out/wayland_common.c @@ -56,6 +56,10 @@ #include "cursor-shape-v1.h" #endif +#if WAYLAND_VERSION_MAJOR > 1 || WAYLAND_VERSION_MINOR >= 21 +#define HAVE_WAYLAND_1_21 +#endif + #if WAYLAND_VERSION_MAJOR > 1 || WAYLAND_VERSION_MINOR >= 22 #define HAVE_WAYLAND_1_22 #endif @@ -133,7 +137,8 @@ static const struct mp_keymap keymap[] = { {XKB_KEY_XF86HomePage, MP_KEY_HOMEPAGE}, {XKB_KEY_XF86WWW, MP_KEY_WWW}, {XKB_KEY_XF86Mail, MP_KEY_MAIL}, {XKB_KEY_XF86Favorites, MP_KEY_FAVORITES}, {XKB_KEY_XF86Search, MP_KEY_SEARCH}, {XKB_KEY_XF86Sleep, MP_KEY_SLEEP}, - {XKB_KEY_XF86Back, MP_KEY_BACK}, {XKB_KEY_XF86Tools, MP_KEY_TOOLS}, + {XKB_KEY_XF86Back, MP_KEY_GO_BACK}, {XKB_KEY_XF86Forward, MP_KEY_GO_FORWARD}, + {XKB_KEY_XF86Tools, MP_KEY_TOOLS}, {XKB_KEY_XF86ZoomIn, MP_KEY_ZOOMIN}, {XKB_KEY_XF86ZoomOut, MP_KEY_ZOOMOUT}, {0, 0} @@ -182,53 +187,93 @@ struct vo_wayland_output { struct wl_list link; }; +struct vo_wayland_seat { + struct vo_wayland_state *wl; + struct wl_seat *seat; + uint32_t id; + struct wl_keyboard *keyboard; + struct wl_pointer *pointer; + struct wl_touch *touch; + struct wl_data_device *dnd_ddev; + /* TODO: unvoid this if required wayland protocols is bumped to 1.32+ */ + void *cursor_shape_device; + uint32_t pointer_enter_serial; + uint32_t pointer_button_serial; + struct xkb_keymap *xkb_keymap; + struct xkb_state *xkb_state; + uint32_t keyboard_code; + int mpkey; + int mpmod; + double axis_value_vertical; + int32_t axis_value120_vertical; + double axis_value_horizontal; + int32_t axis_value120_horizontal; + bool axis_value120_scroll; + bool has_keyboard_input; + struct wl_list link; +}; + +static bool single_output_spanned(struct vo_wayland_state *wl); + static int check_for_resize(struct vo_wayland_state *wl, int edge_pixels, enum xdg_toplevel_resize_edge *edge); -static int get_mods(struct vo_wayland_state *wl); +static int get_mods(struct vo_wayland_seat *seat); +static int greatest_common_divisor(int a, int b); static int lookupkey(int key); -static int set_cursor_visibility(struct vo_wayland_state *wl, bool on); +static int set_cursor_visibility(struct vo_wayland_seat *s, bool on); static int spawn_cursor(struct vo_wayland_state *wl); static void add_feedback(struct vo_wayland_feedback_pool *fback_pool, struct wp_presentation_feedback *fback); -static void get_shape_device(struct vo_wayland_state *wl); -static int greatest_common_divisor(int a, int b); +static void apply_keepaspect(struct vo_wayland_state *wl, int *width, int *height); +static void get_shape_device(struct vo_wayland_state *wl, struct vo_wayland_seat *s); static void guess_focus(struct vo_wayland_state *wl); -static void prepare_resize(struct vo_wayland_state *wl, int width, int height); +static void prepare_resize(struct vo_wayland_state *wl); static void remove_feedback(struct vo_wayland_feedback_pool *fback_pool, struct wp_presentation_feedback *fback); static void remove_output(struct vo_wayland_output *out); +static void remove_seat(struct vo_wayland_seat *seat); static void request_decoration_mode(struct vo_wayland_state *wl, uint32_t mode); static void rescale_geometry(struct vo_wayland_state *wl, double old_scale); static void set_geometry(struct vo_wayland_state *wl, bool resize); static void set_surface_scaling(struct vo_wayland_state *wl); -static void window_move(struct vo_wayland_state *wl, uint32_t serial); +static void update_output_scaling(struct vo_wayland_state *wl); +static void update_output_geometry(struct vo_wayland_state *wl, struct mp_rect old_geometry, + struct mp_rect old_output_geometry); /* Wayland listener boilerplate */ static void pointer_handle_enter(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t sx, wl_fixed_t sy) { - struct vo_wayland_state *wl = data; - - wl->pointer = pointer; - wl->pointer_id = serial; + struct vo_wayland_seat *s = data; + struct vo_wayland_state *wl = s->wl; - set_cursor_visibility(wl, wl->cursor_visible); + s->pointer_enter_serial = serial; + set_cursor_visibility(s, wl->cursor_visible); mp_input_put_key(wl->vo->input_ctx, MP_KEY_MOUSE_ENTER); + + wl->mouse_x = wl_fixed_to_int(sx) * wl->scaling; + wl->mouse_y = wl_fixed_to_int(sy) * wl->scaling; + + if (!wl->toplevel_configured) + mp_input_set_mouse_pos(wl->vo->input_ctx, wl->mouse_x, wl->mouse_y); + wl->toplevel_configured = false; } static void pointer_handle_leave(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface) { - struct vo_wayland_state *wl = data; + struct vo_wayland_seat *s = data; + struct vo_wayland_state *wl = s->wl; mp_input_put_key(wl->vo->input_ctx, MP_KEY_MOUSE_LEAVE); } static void pointer_handle_motion(void *data, struct wl_pointer *pointer, uint32_t time, wl_fixed_t sx, wl_fixed_t sy) { - struct vo_wayland_state *wl = data; + struct vo_wayland_seat *s = data; + struct vo_wayland_state *wl = s->wl; wl->mouse_x = wl_fixed_to_int(sx) * wl->scaling; wl->mouse_y = wl_fixed_to_int(sy) * wl->scaling; @@ -242,7 +287,8 @@ static void pointer_handle_button(void *data, struct wl_pointer *wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) { - struct vo_wayland_state *wl = data; + struct vo_wayland_seat *s = data; + struct vo_wayland_state *wl = s->wl; state = state == WL_POINTER_BUTTON_STATE_PRESSED ? MP_KEY_STATE_DOWN : MP_KEY_STATE_UP; @@ -272,44 +318,106 @@ static void pointer_handle_button(void *data, struct wl_pointer *wl_pointer, } if (button) - mp_input_put_key(wl->vo->input_ctx, button | state | wl->mpmod); + mp_input_put_key(wl->vo->input_ctx, button | state | s->mpmod); + enum xdg_toplevel_resize_edge edges; if (!mp_input_test_dragging(wl->vo->input_ctx, wl->mouse_x, wl->mouse_y) && - !wl->locked_size && (button == MP_MBTN_LEFT) && (state == MP_KEY_STATE_DOWN)) + !wl->locked_size && (button == MP_MBTN_LEFT) && (state == MP_KEY_STATE_DOWN) && + !wl->vo_opts->border && check_for_resize(wl, wl->opts->edge_pixels_pointer, &edges)) { - uint32_t edges; // Implement an edge resize zone if there are no decorations - if (!wl->vo_opts->border && check_for_resize(wl, wl->opts->edge_pixels_pointer, &edges)) { - xdg_toplevel_resize(wl->xdg_toplevel, wl->seat, serial, edges); - } else { - window_move(wl, serial); - } - // Explicitly send an UP event after the client finishes a move/resize + xdg_toplevel_resize(wl->xdg_toplevel, s->seat, serial, edges); + // Explicitly send an UP event after the client finishes a resize mp_input_put_key(wl->vo->input_ctx, button | MP_KEY_STATE_UP); + } else if (state == MP_KEY_STATE_DOWN) { + // Save the serial and seat for voctrl-initialized dragging requests. + s->pointer_button_serial = serial; + wl->last_button_seat = s; + } else { + wl->last_button_seat = NULL; } } static void pointer_handle_axis(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value) { - struct vo_wayland_state *wl = data; + struct vo_wayland_seat *s = data; + switch (axis) { + case WL_POINTER_AXIS_VERTICAL_SCROLL: + s->axis_value_vertical += wl_fixed_to_double(value); + break; + case WL_POINTER_AXIS_HORIZONTAL_SCROLL: + s->axis_value_horizontal += wl_fixed_to_double(value); + break; + } +} + +static void pointer_handle_frame(void *data, struct wl_pointer *wl_pointer) +{ + struct vo_wayland_seat *s = data; + struct vo_wayland_state *wl = s->wl; + double value_vertical, value_horizontal; + if (s->axis_value120_scroll) { + // Prefer axis_value120 if supported and the axis event is from mouse wheel. + value_vertical = s->axis_value120_vertical / 120.0; + value_horizontal = s->axis_value120_horizontal / 120.0; + } else { + // The axis value is specified in logical coordinates, but the exact value emitted + // by one mouse wheel click is unspecified. In practice, most compositors use either + // 10 (GNOME, Weston) or 15 (wlroots, same as libinput) as the value. + // Divide the value by 10 and clamp it between -1 and 1 so that mouse wheel clicks + // work as intended on all compositors while still allowing high resolution trackpads. + value_vertical = MPCLAMP(s->axis_value_vertical / 10.0, -1, 1); + value_horizontal = MPCLAMP(s->axis_value_horizontal / 10.0, -1, 1); + } + + if (value_vertical > 0) + mp_input_put_wheel(wl->vo->input_ctx, MP_WHEEL_DOWN | s->mpmod, +value_vertical); + if (value_vertical < 0) + mp_input_put_wheel(wl->vo->input_ctx, MP_WHEEL_UP | s->mpmod, -value_vertical); + if (value_horizontal > 0) + mp_input_put_wheel(wl->vo->input_ctx, MP_WHEEL_RIGHT | s->mpmod, +value_horizontal); + if (value_horizontal < 0) + mp_input_put_wheel(wl->vo->input_ctx, MP_WHEEL_LEFT | s->mpmod, -value_horizontal); + + s->axis_value120_scroll = false; + s->axis_value_vertical = 0; + s->axis_value_horizontal = 0; + s->axis_value120_vertical = 0; + s->axis_value120_horizontal = 0; +} + +static void pointer_handle_axis_source(void *data, struct wl_pointer *wl_pointer, + uint32_t axis_source) +{ +} + +static void pointer_handle_axis_stop(void *data, struct wl_pointer *wl_pointer, + uint32_t time, uint32_t axis) +{ +} + +static void pointer_handle_axis_discrete(void *data, struct wl_pointer *wl_pointer, + uint32_t axis, int32_t discrete) +{ +} - double val = wl_fixed_to_double(value) < 0 ? -1 : 1; +#ifdef HAVE_WAYLAND_1_21 +static void pointer_handle_axis_value120(void *data, struct wl_pointer *wl_pointer, + uint32_t axis, int32_t value120) +{ + struct vo_wayland_seat *s = data; + s->axis_value120_scroll = true; switch (axis) { case WL_POINTER_AXIS_VERTICAL_SCROLL: - if (value > 0) - mp_input_put_wheel(wl->vo->input_ctx, MP_WHEEL_DOWN | wl->mpmod, +val); - if (value < 0) - mp_input_put_wheel(wl->vo->input_ctx, MP_WHEEL_UP | wl->mpmod, -val); + s->axis_value120_vertical += value120; break; case WL_POINTER_AXIS_HORIZONTAL_SCROLL: - if (value > 0) - mp_input_put_wheel(wl->vo->input_ctx, MP_WHEEL_RIGHT | wl->mpmod, +val); - if (value < 0) - mp_input_put_wheel(wl->vo->input_ctx, MP_WHEEL_LEFT | wl->mpmod, -val); + s->axis_value120_horizontal += value120; break; } } +#endif static const struct wl_pointer_listener pointer_listener = { pointer_handle_enter, @@ -317,40 +425,55 @@ static const struct wl_pointer_listener pointer_listener = { pointer_handle_motion, pointer_handle_button, pointer_handle_axis, + pointer_handle_frame, + pointer_handle_axis_source, + pointer_handle_axis_stop, + pointer_handle_axis_discrete, +#ifdef HAVE_WAYLAND_1_21 + pointer_handle_axis_value120, +#endif }; static void touch_handle_down(void *data, struct wl_touch *wl_touch, uint32_t serial, uint32_t time, struct wl_surface *surface, int32_t id, wl_fixed_t x_w, wl_fixed_t y_w) { - struct vo_wayland_state *wl = data; + struct vo_wayland_seat *s = data; + struct vo_wayland_state *wl = s->wl; wl->mouse_x = wl_fixed_to_int(x_w) * wl->scaling; wl->mouse_y = wl_fixed_to_int(y_w) * wl->scaling; - enum xdg_toplevel_resize_edge edge; - if (!mp_input_test_dragging(wl->vo->input_ctx, wl->mouse_x, wl->mouse_y)) { - if (check_for_resize(wl, wl->opts->edge_pixels_touch, &edge)) { - xdg_toplevel_resize(wl->xdg_toplevel, wl->seat, serial, edge); - } else { - xdg_toplevel_move(wl->xdg_toplevel, wl->seat, serial); - } - } - mp_input_set_mouse_pos(wl->vo->input_ctx, wl->mouse_x, wl->mouse_y); mp_input_put_key(wl->vo->input_ctx, MP_MBTN_LEFT | MP_KEY_STATE_DOWN); + + enum xdg_toplevel_resize_edge edge; + if (!mp_input_test_dragging(wl->vo->input_ctx, wl->mouse_x, wl->mouse_y) && + !wl->locked_size && check_for_resize(wl, wl->opts->edge_pixels_touch, &edge)) + { + xdg_toplevel_resize(wl->xdg_toplevel, s->seat, serial, edge); + // Explicitly send an UP event after the client finishes a resize + mp_input_put_key(wl->vo->input_ctx, MP_MBTN_LEFT | MP_KEY_STATE_UP); + } else { + // Save the serial and seat for voctrl-initialized dragging requests. + s->pointer_button_serial = serial; + wl->last_button_seat = s; + } } static void touch_handle_up(void *data, struct wl_touch *wl_touch, uint32_t serial, uint32_t time, int32_t id) { - struct vo_wayland_state *wl = data; + struct vo_wayland_seat *s = data; + struct vo_wayland_state *wl = s->wl; mp_input_put_key(wl->vo->input_ctx, MP_MBTN_LEFT | MP_KEY_STATE_UP); + wl->last_button_seat = NULL; } static void touch_handle_motion(void *data, struct wl_touch *wl_touch, uint32_t time, int32_t id, wl_fixed_t x_w, wl_fixed_t y_w) { - struct vo_wayland_state *wl = data; + struct vo_wayland_seat *s = data; + struct vo_wayland_state *wl = s->wl; wl->mouse_x = wl_fixed_to_int(x_w) * wl->scaling; wl->mouse_y = wl_fixed_to_int(y_w) * wl->scaling; @@ -366,18 +489,31 @@ static void touch_handle_cancel(void *data, struct wl_touch *wl_touch) { } +static void touch_handle_shape(void *data, struct wl_touch *wl_touch, + int32_t id, wl_fixed_t major, wl_fixed_t minor) +{ +} + +static void touch_handle_orientation(void *data, struct wl_touch *wl_touch, + int32_t id, wl_fixed_t orientation) +{ +} + static const struct wl_touch_listener touch_listener = { touch_handle_down, touch_handle_up, touch_handle_motion, touch_handle_frame, touch_handle_cancel, + touch_handle_shape, + touch_handle_orientation, }; static void keyboard_handle_keymap(void *data, struct wl_keyboard *wl_keyboard, uint32_t format, int32_t fd, uint32_t size) { - struct vo_wayland_state *wl = data; + struct vo_wayland_seat *s = data; + struct vo_wayland_state *wl = s->wl; char *map_str; if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { @@ -391,23 +527,25 @@ static void keyboard_handle_keymap(void *data, struct wl_keyboard *wl_keyboard, return; } - wl->xkb_keymap = xkb_keymap_new_from_buffer(wl->xkb_context, map_str, - strnlen(map_str, size), - XKB_KEYMAP_FORMAT_TEXT_V1, 0); + if (!s->xkb_keymap) + s->xkb_keymap = xkb_keymap_new_from_buffer(wl->xkb_context, map_str, + strnlen(map_str, size), + XKB_KEYMAP_FORMAT_TEXT_V1, 0); munmap(map_str, size); close(fd); - if (!wl->xkb_keymap) { + if (!s->xkb_keymap) { MP_ERR(wl, "failed to compile keymap\n"); return; } - wl->xkb_state = xkb_state_new(wl->xkb_keymap); - if (!wl->xkb_state) { + if (!s->xkb_state) + s->xkb_state = xkb_state_new(s->xkb_keymap); + if (!s->xkb_state) { MP_ERR(wl, "failed to create XKB state\n"); - xkb_keymap_unref(wl->xkb_keymap); - wl->xkb_keymap = NULL; + xkb_keymap_unref(s->xkb_keymap); + s->xkb_keymap = NULL; return; } } @@ -416,19 +554,21 @@ static void keyboard_handle_enter(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys) { - struct vo_wayland_state *wl = data; - wl->has_keyboard_input = true; + struct vo_wayland_seat *s = data; + struct vo_wayland_state *wl = s->wl; + s->has_keyboard_input = true; guess_focus(wl); } static void keyboard_handle_leave(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, struct wl_surface *surface) { - struct vo_wayland_state *wl = data; - wl->has_keyboard_input = false; - wl->keyboard_code = 0; - wl->mpkey = 0; - wl->mpmod = 0; + struct vo_wayland_seat *s = data; + struct vo_wayland_state *wl = s->wl; + s->has_keyboard_input = false; + s->keyboard_code = 0; + s->mpkey = 0; + s->mpmod = 0; mp_input_put_key(wl->vo->input_ctx, MP_INPUT_RELEASE_ALL); guess_focus(wl); } @@ -437,30 +577,37 @@ static void keyboard_handle_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) { - struct vo_wayland_state *wl = data; + struct vo_wayland_seat *s = data; + struct vo_wayland_state *wl = s->wl; - wl->keyboard_code = key + 8; - xkb_keysym_t sym = xkb_state_key_get_one_sym(wl->xkb_state, wl->keyboard_code); + s->keyboard_code = key + 8; + xkb_keysym_t sym = xkb_state_key_get_one_sym(s->xkb_state, s->keyboard_code); int mpkey = lookupkey(sym); state = state == WL_KEYBOARD_KEY_STATE_PRESSED ? MP_KEY_STATE_DOWN : MP_KEY_STATE_UP; if (mpkey) { - mp_input_put_key(wl->vo->input_ctx, mpkey | state | wl->mpmod); + mp_input_put_key(wl->vo->input_ctx, mpkey | state | s->mpmod); } else { - char s[128]; - if (xkb_keysym_to_utf8(sym, s, sizeof(s)) > 0) { - mp_input_put_key_utf8(wl->vo->input_ctx, state | wl->mpmod, bstr0(s)); + char str[128]; + if (xkb_keysym_to_utf8(sym, str, sizeof(str)) > 0) { + mp_input_put_key_utf8(wl->vo->input_ctx, state | s->mpmod, bstr0(str)); } else { // Assume a modifier was pressed and handle it in the mod event instead. + // If a modifier is released before a regular key, also release that + // key to not activate it again by accident. + if (state == MP_KEY_STATE_UP) { + s->mpkey = 0; + mp_input_put_key(wl->vo->input_ctx, MP_INPUT_RELEASE_ALL); + } return; } } if (state == MP_KEY_STATE_DOWN) - wl->mpkey = mpkey; + s->mpkey = mpkey; if (mpkey && state == MP_KEY_STATE_UP) - wl->mpkey = 0; + s->mpkey = 0; } static void keyboard_handle_modifiers(void *data, struct wl_keyboard *wl_keyboard, @@ -468,21 +615,23 @@ static void keyboard_handle_modifiers(void *data, struct wl_keyboard *wl_keyboar uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { - struct vo_wayland_state *wl = data; + struct vo_wayland_seat *s = data; + struct vo_wayland_state *wl = s->wl; - if (wl->xkb_state) { - xkb_state_update_mask(wl->xkb_state, mods_depressed, mods_latched, + if (s->xkb_state) { + xkb_state_update_mask(s->xkb_state, mods_depressed, mods_latched, mods_locked, 0, 0, group); - wl->mpmod = get_mods(wl); - if (wl->mpkey) - mp_input_put_key(wl->vo->input_ctx, wl->mpkey | MP_KEY_STATE_DOWN | wl->mpmod); + s->mpmod = get_mods(s); + if (s->mpkey) + mp_input_put_key(wl->vo->input_ctx, s->mpkey | MP_KEY_STATE_DOWN | s->mpmod); } } static void keyboard_handle_repeat_info(void *data, struct wl_keyboard *wl_keyboard, int32_t rate, int32_t delay) { - struct vo_wayland_state *wl = data; + struct vo_wayland_seat *s = data; + struct vo_wayland_state *wl = s->wl; if (wl->vo_opts->native_keyrepeat) mp_input_set_repeat_info(wl->vo->input_ctx, rate, delay); } @@ -499,43 +648,50 @@ static const struct wl_keyboard_listener keyboard_listener = { static void seat_handle_caps(void *data, struct wl_seat *seat, enum wl_seat_capability caps) { - struct vo_wayland_state *wl = data; + struct vo_wayland_seat *s = data; - if ((caps & WL_SEAT_CAPABILITY_POINTER) && !wl->pointer) { - wl->pointer = wl_seat_get_pointer(seat); - get_shape_device(wl); - wl_pointer_add_listener(wl->pointer, &pointer_listener, wl); - } else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && wl->pointer) { - wl_pointer_destroy(wl->pointer); - wl->pointer = NULL; + if ((caps & WL_SEAT_CAPABILITY_POINTER) && !s->pointer) { + s->pointer = wl_seat_get_pointer(seat); + get_shape_device(s->wl, s); + wl_pointer_add_listener(s->pointer, &pointer_listener, s); + } else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && s->pointer) { + wl_pointer_destroy(s->pointer); + s->pointer = NULL; } - if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !wl->keyboard) { - wl->keyboard = wl_seat_get_keyboard(seat); - wl_keyboard_add_listener(wl->keyboard, &keyboard_listener, wl); - } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && wl->keyboard) { - wl_keyboard_destroy(wl->keyboard); - wl->keyboard = NULL; + if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !s->keyboard) { + s->keyboard = wl_seat_get_keyboard(seat); + wl_keyboard_add_listener(s->keyboard, &keyboard_listener, s); + } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && s->keyboard) { + wl_keyboard_destroy(s->keyboard); + s->keyboard = NULL; } - if ((caps & WL_SEAT_CAPABILITY_TOUCH) && !wl->touch) { - wl->touch = wl_seat_get_touch(seat); - wl_touch_set_user_data(wl->touch, wl); - wl_touch_add_listener(wl->touch, &touch_listener, wl); - } else if (!(caps & WL_SEAT_CAPABILITY_TOUCH) && wl->touch) { - wl_touch_destroy(wl->touch); - wl->touch = NULL; + if ((caps & WL_SEAT_CAPABILITY_TOUCH) && !s->touch) { + s->touch = wl_seat_get_touch(seat); + wl_touch_set_user_data(s->touch, s); + wl_touch_add_listener(s->touch, &touch_listener, s); + } else if (!(caps & WL_SEAT_CAPABILITY_TOUCH) && s->touch) { + wl_touch_destroy(s->touch); + s->touch = NULL; } } +static void seat_handle_name(void *data, struct wl_seat *seat, + const char *name) +{ +} + static const struct wl_seat_listener seat_listener = { seat_handle_caps, + seat_handle_name, }; static void data_offer_handle_offer(void *data, struct wl_data_offer *offer, const char *mime_type) { - struct vo_wayland_state *wl = data; + struct vo_wayland_seat *s = data; + struct vo_wayland_state *wl = s->wl; int score = mp_event_get_mime_type_score(wl->vo->input_ctx, mime_type); if (score > wl->dnd_mime_score && wl->vo_opts->drag_and_drop != -2) { wl->dnd_mime_score = score; @@ -552,7 +708,8 @@ static void data_offer_source_actions(void *data, struct wl_data_offer *offer, u static void data_offer_action(void *data, struct wl_data_offer *wl_data_offer, uint32_t dnd_action) { - struct vo_wayland_state *wl = data; + struct vo_wayland_seat *s = data; + struct vo_wayland_state *wl = s->wl; if (dnd_action && wl->vo_opts->drag_and_drop != -2) { if (wl->vo_opts->drag_and_drop >= 0) { wl->dnd_action = wl->vo_opts->drag_and_drop; @@ -560,8 +717,14 @@ static void data_offer_action(void *data, struct wl_data_offer *wl_data_offer, u wl->dnd_action = dnd_action & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY ? DND_REPLACE : DND_APPEND; } - MP_VERBOSE(wl, "DND action is %s\n", - wl->dnd_action == DND_REPLACE ? "DND_REPLACE" : "DND_APPEND"); + + static const char * const dnd_action_names[] = { + [DND_REPLACE] = "DND_REPLACE", + [DND_APPEND] = "DND_APPEND", + [DND_INSERT_NEXT] = "DND_INSERT_NEXT", + }; + + MP_VERBOSE(wl, "DND action is %s\n", dnd_action_names[wl->dnd_action]); } } @@ -574,12 +737,13 @@ static const struct wl_data_offer_listener data_offer_listener = { static void data_device_handle_data_offer(void *data, struct wl_data_device *wl_ddev, struct wl_data_offer *id) { - struct vo_wayland_state *wl = data; + struct vo_wayland_seat *s = data; + struct vo_wayland_state *wl = s->wl; if (wl->dnd_offer) wl_data_offer_destroy(wl->dnd_offer); wl->dnd_offer = id; - wl_data_offer_add_listener(id, &data_offer_listener, wl); + wl_data_offer_add_listener(id, &data_offer_listener, s); } static void data_device_handle_enter(void *data, struct wl_data_device *wl_ddev, @@ -587,7 +751,8 @@ static void data_device_handle_enter(void *data, struct wl_data_device *wl_ddev, wl_fixed_t x, wl_fixed_t y, struct wl_data_offer *id) { - struct vo_wayland_state *wl = data; + struct vo_wayland_seat *s = data; + struct vo_wayland_state *wl = s->wl; if (wl->dnd_offer != id) { MP_FATAL(wl, "DND offer ID mismatch!\n"); return; @@ -605,7 +770,8 @@ static void data_device_handle_enter(void *data, struct wl_data_device *wl_ddev, static void data_device_handle_leave(void *data, struct wl_data_device *wl_ddev) { - struct vo_wayland_state *wl = data; + struct vo_wayland_seat *s = data; + struct vo_wayland_state *wl = s->wl; if (wl->dnd_offer) { if (wl->dnd_fd != -1) @@ -625,13 +791,15 @@ static void data_device_handle_leave(void *data, struct wl_data_device *wl_ddev) static void data_device_handle_motion(void *data, struct wl_data_device *wl_ddev, uint32_t time, wl_fixed_t x, wl_fixed_t y) { - struct vo_wayland_state *wl = data; + struct vo_wayland_seat *s = data; + struct vo_wayland_state *wl = s->wl; wl_data_offer_accept(wl->dnd_offer, time, wl->dnd_mime_type); } static void data_device_handle_drop(void *data, struct wl_data_device *wl_ddev) { - struct vo_wayland_state *wl = data; + struct vo_wayland_seat *s = data; + struct vo_wayland_state *wl = s->wl; int pipefd[2]; @@ -652,7 +820,8 @@ static void data_device_handle_drop(void *data, struct wl_data_device *wl_ddev) static void data_device_handle_selection(void *data, struct wl_data_device *wl_ddev, struct wl_data_offer *id) { - struct vo_wayland_state *wl = data; + struct vo_wayland_seat *s = data; + struct vo_wayland_state *wl = s->wl; if (wl->dnd_offer) { wl_data_offer_destroy(wl->dnd_offer); @@ -723,10 +892,8 @@ static void output_handle_done(void *data, struct wl_output *wl_output) * geometry and scaling should be recalculated. */ if (wl->current_output && wl->current_output->output == wl_output) { set_surface_scaling(wl); - spawn_cursor(wl); set_geometry(wl, false); - prepare_resize(wl, 0, 0); - wl->pending_vo_events |= VO_EVENT_DPI; + prepare_resize(wl); } wl->pending_vo_events |= VO_EVENT_WIN_STATE; @@ -775,36 +942,23 @@ static void surface_handle_enter(void *data, struct wl_surface *wl_surface, struct mp_rect old_geometry = wl->geometry; wl->current_output = NULL; + int outputs = 0; struct vo_wayland_output *o; wl_list_for_each(o, &wl->output_list, link) { if (o->output == output) { wl->current_output = o; - break; + wl->current_output->has_surface = true; } + if (o->has_surface) + ++outputs; } - wl->current_output->has_surface = true; - bool force_resize = false; - - if (!wl->fractional_scale_manager && wl_surface_get_version(wl_surface) < 6 && - wl->scaling != wl->current_output->scale) - { - set_surface_scaling(wl); - spawn_cursor(wl); - force_resize = true; - wl->pending_vo_events |= VO_EVENT_DPI; - } - - if (!mp_rect_equals(&old_output_geometry, &wl->current_output->geometry)) { - set_geometry(wl, false); - force_resize = true; - } - - if (!mp_rect_equals(&old_geometry, &wl->geometry) || force_resize) - prepare_resize(wl, 0, 0); + if (outputs == 1) + update_output_geometry(wl, old_geometry, old_output_geometry); MP_VERBOSE(wl, "Surface entered output %s %s (0x%x), scale = %f, refresh rate = %f Hz\n", - o->make, o->model, o->id, wl->scaling, o->refresh_rate); + wl->current_output->make, wl->current_output->model, + wl->current_output->id, wl->scaling, wl->current_output->refresh_rate); wl->pending_vo_events |= VO_EVENT_WIN_STATE; } @@ -813,15 +967,27 @@ static void surface_handle_leave(void *data, struct wl_surface *wl_surface, struct wl_output *output) { struct vo_wayland_state *wl = data; + if (!wl->current_output) + return; + struct mp_rect old_output_geometry = wl->current_output->geometry; + struct mp_rect old_geometry = wl->geometry; + + int outputs = 0; struct vo_wayland_output *o; wl_list_for_each(o, &wl->output_list, link) { - if (o->output == output) { + if (o->output == output) o->has_surface = false; - wl->pending_vo_events |= VO_EVENT_WIN_STATE; - return; - } + if (o->has_surface) + ++outputs; + if (o->output != output && o->has_surface) + wl->current_output = o; } + + if (outputs == 1) + update_output_geometry(wl, old_geometry, old_output_geometry); + + wl->pending_vo_events |= VO_EVENT_WIN_STATE; } #ifdef HAVE_WAYLAND_1_22 @@ -830,21 +996,20 @@ static void surface_handle_preferred_buffer_scale(void *data, int32_t scale) { struct vo_wayland_state *wl = data; - double old_scale = wl->scaling; - if (wl->fractional_scale_manager) + if (wl->fractional_scale_manager || wl->scaling == scale) return; - // dmabuf_wayland is always wl->scaling = 1 - wl->scaling = !wl->using_dmabuf_wayland ? scale : 1; + wl->pending_scaling = scale; + wl->scale_configured = true; MP_VERBOSE(wl, "Obtained preferred scale, %f, from the compositor.\n", wl->scaling); wl->pending_vo_events |= VO_EVENT_DPI; - if (wl->current_output) { - rescale_geometry(wl, old_scale); - set_geometry(wl, false); - prepare_resize(wl, 0, 0); - } + wl->need_rescale = true; + + // Update scaling now. + if (single_output_spanned(wl)) + update_output_scaling(wl); } static void surface_handle_preferred_buffer_transform(void *data, @@ -889,12 +1054,12 @@ static void handle_toplevel_config(void *data, struct xdg_toplevel *toplevel, struct mp_vo_opts *vo_opts = wl->vo_opts; struct mp_rect old_geometry = wl->geometry; - int old_toplevel_width = wl->toplevel_width; - int old_toplevel_height = wl->toplevel_height; - wl->toplevel_width = width; - wl->toplevel_height = height; + if (width < 0 || height < 0) { + MP_WARN(wl, "Compositor sent negative width/height values. Treating them as zero.\n"); + width = height = 0; + } - if (!wl->configured) { + if (!wl->geometry_configured) { /* Save initial window size if the compositor gives us a hint here. */ bool autofit_or_geometry = vo_opts->geometry.wh_valid || vo_opts->autofit.wh_valid || vo_opts->autofit_larger.wh_valid || vo_opts->autofit_smaller.wh_valid; @@ -949,20 +1114,24 @@ static void handle_toplevel_config(void *data, struct xdg_toplevel *toplevel, wl->hidden = is_suspended; if (vo_opts->fullscreen != is_fullscreen) { - wl->state_change = true; + wl->state_change = wl->reconfigured; vo_opts->fullscreen = is_fullscreen; m_config_cache_write_opt(wl->vo_opts_cache, &vo_opts->fullscreen); } if (vo_opts->window_maximized != is_maximized) { - wl->state_change = true; + wl->state_change = wl->reconfigured; vo_opts->window_maximized = is_maximized; m_config_cache_write_opt(wl->vo_opts_cache, &vo_opts->window_maximized); } + if (!is_tiled && wl->tiled) + wl->state_change = wl->reconfigured; + wl->tiled = is_tiled; wl->locked_size = is_fullscreen || is_maximized || is_tiled; + wl->reconfigured = false; if (wl->requested_decoration) request_decoration_mode(wl, wl->requested_decoration); @@ -993,26 +1162,17 @@ static void handle_toplevel_config(void *data, struct xdg_toplevel *toplevel, goto resize; } - if (old_toplevel_width == wl->toplevel_width && - old_toplevel_height == wl->toplevel_height) - return; - if (!wl->locked_size) { - if (vo_opts->keepaspect) { - double scale_factor = (double)width / wl->reduced_width; - width = ceil(wl->reduced_width * scale_factor); - if (vo_opts->keepaspect_window) - height = ceil(wl->reduced_height * scale_factor); - } + apply_keepaspect(wl, &width, &height); wl->window_size.x0 = 0; wl->window_size.y0 = 0; - wl->window_size.x1 = round(width * wl->scaling); - wl->window_size.y1 = round(height * wl->scaling); + wl->window_size.x1 = lround(width * wl->scaling); + wl->window_size.y1 = lround(height * wl->scaling); } wl->geometry.x0 = 0; wl->geometry.y0 = 0; - wl->geometry.x1 = round(width * wl->scaling); - wl->geometry.y1 = round(height * wl->scaling); + wl->geometry.x1 = lround(width * wl->scaling); + wl->geometry.y1 = lround(height * wl->scaling); if (mp_rect_equals(&old_geometry, &wl->geometry)) return; @@ -1022,7 +1182,7 @@ resize: mp_rect_w(old_geometry), mp_rect_h(old_geometry), mp_rect_w(wl->geometry), mp_rect_h(wl->geometry)); - prepare_resize(wl, width, height); + prepare_resize(wl); wl->toplevel_configured = true; } @@ -1062,18 +1222,19 @@ static void preferred_scale(void *data, uint32_t scale) { struct vo_wayland_state *wl = data; - double old_scale = wl->scaling; + double new_scale = (double)scale / 120; + if (wl->scaling == new_scale) + return; - // dmabuf_wayland is always wl->scaling = 1 - wl->scaling = !wl->using_dmabuf_wayland ? (double)scale / 120 : 1; + wl->pending_scaling = new_scale; + wl->scale_configured = true; MP_VERBOSE(wl, "Obtained preferred scale, %f, from the compositor.\n", - wl->scaling); - wl->pending_vo_events |= VO_EVENT_DPI; - if (wl->current_output) { - rescale_geometry(wl, old_scale); - set_geometry(wl, false); - prepare_resize(wl, 0, 0); - } + wl->pending_scaling); + wl->need_rescale = true; + + // Update scaling now. + if (single_output_spanned(wl)) + update_output_scaling(wl); } static const struct wp_fractional_scale_v1_listener fractional_scale_listener = { @@ -1296,9 +1457,8 @@ static void registry_handle_add(void *data, struct wl_registry *reg, uint32_t id wl_surface_add_listener(wl->surface, &surface_listener, wl); } - if (!strcmp(interface, wl_subcompositor_interface.name) && (ver >= 1) && found++) { + if (!strcmp(interface, wl_subcompositor_interface.name) && (ver >= 1) && found++) wl->subcompositor = wl_registry_bind(reg, id, &wl_subcompositor_interface, 1); - } if (!strcmp (interface, zwp_linux_dmabuf_v1_interface.name) && (ver >= 4) && found++) { wl->dmabuf = wl_registry_bind(reg, id, &zwp_linux_dmabuf_v1_interface, 4); @@ -1306,13 +1466,11 @@ static void registry_handle_add(void *data, struct wl_registry *reg, uint32_t id zwp_linux_dmabuf_feedback_v1_add_listener(wl->dmabuf_feedback, &dmabuf_feedback_listener, wl); } - if (!strcmp (interface, wp_viewporter_interface.name) && (ver >= 1) && found++) { - wl->viewporter = wl_registry_bind (reg, id, &wp_viewporter_interface, 1); - } + if (!strcmp (interface, wp_viewporter_interface.name) && (ver >= 1) && found++) + wl->viewporter = wl_registry_bind (reg, id, &wp_viewporter_interface, 1); - if (!strcmp(interface, wl_data_device_manager_interface.name) && (ver >= 3) && found++) { + if (!strcmp(interface, wl_data_device_manager_interface.name) && (ver >= 3) && found++) wl->dnd_devman = wl_registry_bind(reg, id, &wl_data_device_manager_interface, 3); - } if (!strcmp(interface, wl_output_interface.name) && (ver >= 2) && found++) { struct vo_wayland_output *output = talloc_zero(wl, struct vo_wayland_output); @@ -1329,34 +1487,41 @@ static void registry_handle_add(void *data, struct wl_registry *reg, uint32_t id } if (!strcmp(interface, wl_seat_interface.name) && found++) { - wl->seat = wl_registry_bind(reg, id, &wl_seat_interface, 1); - wl_seat_add_listener(wl->seat, &seat_listener, wl); + if (ver < 5) + MP_WARN(wl, "Scrolling won't work because the compositor doesn't " + "support version 5 of wl_seat protocol!\n"); +#ifdef HAVE_WAYLAND_1_21 + ver = MPMIN(ver, 8); /* Cap at 8 in case new events are added later. */ +#else + ver = MPMIN(ver, 7); +#endif + struct vo_wayland_seat *seat = talloc_zero(wl, struct vo_wayland_seat); + seat->wl = wl; + seat->id = id; + seat->seat = wl_registry_bind(reg, id, &wl_seat_interface, ver); + wl_seat_add_listener(seat->seat, &seat_listener, seat); + wl_list_insert(&wl->seat_list, &seat->link); } - if (!strcmp(interface, wl_shm_interface.name) && found++) { + if (!strcmp(interface, wl_shm_interface.name) && found++) wl->shm = wl_registry_bind(reg, id, &wl_shm_interface, 1); - } #if HAVE_WAYLAND_PROTOCOLS_1_27 - if (!strcmp(interface, wp_content_type_manager_v1_interface.name) && found++) { + if (!strcmp(interface, wp_content_type_manager_v1_interface.name) && found++) wl->content_type_manager = wl_registry_bind(reg, id, &wp_content_type_manager_v1_interface, 1); - } - if (!strcmp(interface, wp_single_pixel_buffer_manager_v1_interface.name) && found++) { + if (!strcmp(interface, wp_single_pixel_buffer_manager_v1_interface.name) && found++) wl->single_pixel_manager = wl_registry_bind(reg, id, &wp_single_pixel_buffer_manager_v1_interface, 1); - } #endif #if HAVE_WAYLAND_PROTOCOLS_1_31 - if (!strcmp(interface, wp_fractional_scale_manager_v1_interface.name) && found++) { + if (!strcmp(interface, wp_fractional_scale_manager_v1_interface.name) && found++) wl->fractional_scale_manager = wl_registry_bind(reg, id, &wp_fractional_scale_manager_v1_interface, 1); - } #endif #if HAVE_WAYLAND_PROTOCOLS_1_32 - if (!strcmp(interface, wp_cursor_shape_manager_v1_interface.name) && found++) { + if (!strcmp(interface, wp_cursor_shape_manager_v1_interface.name) && found++) wl->cursor_shape_manager = wl_registry_bind(reg, id, &wp_cursor_shape_manager_v1_interface, 1); - } #endif if (!strcmp(interface, wp_presentation_interface.name) && found++) { @@ -1370,13 +1535,11 @@ static void registry_handle_add(void *data, struct wl_registry *reg, uint32_t id xdg_wm_base_add_listener(wl->wm_base, &xdg_wm_base_listener, wl); } - if (!strcmp(interface, zxdg_decoration_manager_v1_interface.name) && found++) { + if (!strcmp(interface, zxdg_decoration_manager_v1_interface.name) && found++) wl->xdg_decoration_manager = wl_registry_bind(reg, id, &zxdg_decoration_manager_v1_interface, 1); - } - if (!strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name) && found++) { + if (!strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name) && found++) wl->idle_inhibit_manager = wl_registry_bind(reg, id, &zwp_idle_inhibit_manager_v1_interface, 1); - } if (found > 1) MP_VERBOSE(wl, "Registered for protocol %s\n", interface); @@ -1385,13 +1548,21 @@ static void registry_handle_add(void *data, struct wl_registry *reg, uint32_t id static void registry_handle_remove(void *data, struct wl_registry *reg, uint32_t id) { struct vo_wayland_state *wl = data; - struct vo_wayland_output *output, *tmp; - wl_list_for_each_safe(output, tmp, &wl->output_list, link) { + struct vo_wayland_output *output, *output_tmp; + wl_list_for_each_safe(output, output_tmp, &wl->output_list, link) { if (output->id == id) { remove_output(output); return; } } + + struct vo_wayland_seat *seat, *seat_tmp; + wl_list_for_each_safe(seat, seat_tmp, &wl->seat_list, link) { + if (seat->id == id) { + remove_seat(seat); + return; + } + } } static const struct wl_registry_listener registry_listener = { @@ -1400,6 +1571,26 @@ static const struct wl_registry_listener registry_listener = { }; /* Static functions */ +static void apply_keepaspect(struct vo_wayland_state *wl, int *width, int *height) +{ + if (!wl->vo_opts->keepaspect) + return; + + double scale_factor = (double)*width / wl->reduced_width; + *width = ceil(wl->reduced_width * scale_factor); + if (wl->vo_opts->keepaspect_window) + *height = ceil(wl->reduced_height * scale_factor); +} + +static void free_dnd_data(struct vo_wayland_state *wl) +{ + // caller should close wl->dnd_fd if appropriate + + wl->dnd_action = -1; + TA_FREEP(&wl->dnd_mime_type); + wl->dnd_mime_score = 0; +} + static void check_dnd_fd(struct vo_wayland_state *wl) { if (wl->dnd_fd == -1) @@ -1410,40 +1601,44 @@ static void check_dnd_fd(struct vo_wayland_state *wl) return; if (fdp.revents & POLLIN) { - ptrdiff_t offset = 0; - size_t data_read = 0; - const size_t chunk_size = 1; - uint8_t *buffer = ta_zalloc_size(wl, chunk_size); - if (!buffer) - goto end; - - while ((data_read = read(wl->dnd_fd, buffer + offset, chunk_size)) > 0) { - offset += data_read; - buffer = ta_realloc_size(wl, buffer, offset + chunk_size); - memset(buffer + offset, 0, chunk_size); - if (!buffer) - goto end; + ssize_t data_read = 0; + const size_t chunk_size = 256; + bstr file_list = { + .start = talloc_zero_size(NULL, chunk_size), + }; + + while (1) { + data_read = read(wl->dnd_fd, file_list.start + file_list.len, chunk_size); + if (data_read == -1 && errno == EINTR) + continue; + else if (data_read <= 0) + break; + file_list.len += data_read; + file_list.start = talloc_realloc_size(NULL, file_list.start, file_list.len + chunk_size); + memset(file_list.start + file_list.len, 0, chunk_size); } - MP_VERBOSE(wl, "Read %td bytes from the DND fd\n", offset); + if (data_read == -1) { + MP_VERBOSE(wl, "DND aborted (read error)\n"); + } else { + MP_VERBOSE(wl, "Read %zu bytes from the DND fd\n", file_list.len); - struct bstr file_list = bstr0(buffer); - mp_event_drop_mime_data(wl->vo->input_ctx, wl->dnd_mime_type, - file_list, wl->dnd_action); - talloc_free(buffer); -end: - if (wl->dnd_mime_type) - talloc_free(wl->dnd_mime_type); + if (wl->dnd_offer) + wl_data_offer_finish(wl->dnd_offer); - if (wl->dnd_action >= 0 && wl->dnd_offer) - wl_data_offer_finish(wl->dnd_offer); + assert(wl->dnd_action >= 0); + mp_event_drop_mime_data(wl->vo->input_ctx, wl->dnd_mime_type, + file_list, wl->dnd_action); + } - wl->dnd_action = -1; - wl->dnd_mime_type = NULL; - wl->dnd_mime_score = 0; + talloc_free(file_list.start); + free_dnd_data(wl); } if (fdp.revents & (POLLIN | POLLERR | POLLHUP)) { + if (wl->dnd_action >= 0) + MP_VERBOSE(wl, "DND aborted (hang up or error)\n"); + free_dnd_data(wl); close(wl->dnd_fd); wl->dnd_fd = -1; } @@ -1499,13 +1694,12 @@ static bool create_input(struct vo_wayland_state *wl) static int create_viewports(struct vo_wayland_state *wl) { - if (wl->viewporter) { - wl->viewport = wp_viewporter_get_viewport(wl->viewporter, wl->surface); - wl->osd_viewport = wp_viewporter_get_viewport(wl->viewporter, wl->osd_surface); - wl->video_viewport = wp_viewporter_get_viewport(wl->viewporter, wl->video_surface); - } + wl->viewport = wp_viewporter_get_viewport(wl->viewporter, wl->surface); + wl->cursor_viewport = wp_viewporter_get_viewport(wl->viewporter, wl->cursor_surface); + wl->osd_viewport = wp_viewporter_get_viewport(wl->viewporter, wl->osd_surface); + wl->video_viewport = wp_viewporter_get_viewport(wl->viewporter, wl->video_surface); - if (wl->viewporter && (!wl->viewport || !wl->osd_viewport || !wl->video_viewport)) { + if (!wl->viewport || !wl->osd_viewport || !wl->video_viewport) { MP_ERR(wl, "failed to create viewport interfaces!\n"); return 1; } @@ -1544,8 +1738,6 @@ static void add_feedback(struct vo_wayland_feedback_pool *fback_pool, static void do_minimize(struct vo_wayland_state *wl) { - if (!wl->xdg_toplevel) - return; if (wl->vo_opts->window_minimized) xdg_toplevel_set_minimized(wl->xdg_toplevel); } @@ -1566,7 +1758,7 @@ static char **get_displays_spanned(struct vo_wayland_state *wl) return names; } -static int get_mods(struct vo_wayland_state *wl) +static int get_mods(struct vo_wayland_seat *s) { static char* const mod_names[] = { XKB_MOD_NAME_SHIFT, @@ -1585,21 +1777,21 @@ static int get_mods(struct vo_wayland_state *wl) int modifiers = 0; for (int n = 0; n < MP_ARRAY_SIZE(mods); n++) { - xkb_mod_index_t index = xkb_keymap_mod_get_index(wl->xkb_keymap, mod_names[n]); - if (!xkb_state_mod_index_is_consumed(wl->xkb_state, wl->keyboard_code, index) - && xkb_state_mod_index_is_active(wl->xkb_state, index, - XKB_STATE_MODS_DEPRESSED)) + xkb_mod_index_t index = xkb_keymap_mod_get_index(s->xkb_keymap, mod_names[n]); + if (index != XKB_MOD_INVALID + && xkb_state_mod_index_is_active(s->xkb_state, index, + XKB_STATE_MODS_EFFECTIVE)) modifiers |= mods[n]; } return modifiers; } -static void get_shape_device(struct vo_wayland_state *wl) +static void get_shape_device(struct vo_wayland_state *wl, struct vo_wayland_seat *s) { #if HAVE_WAYLAND_PROTOCOLS_1_32 - if (!wl->cursor_shape_device && wl->cursor_shape_manager) { - wl->cursor_shape_device = wp_cursor_shape_manager_v1_get_pointer(wl->cursor_shape_manager, - wl->pointer); + if (!s->cursor_shape_device && wl->cursor_shape_manager) { + s->cursor_shape_device = wp_cursor_shape_manager_v1_get_pointer(wl->cursor_shape_manager, + s->pointer); } #endif } @@ -1616,8 +1808,17 @@ static void guess_focus(struct vo_wayland_state *wl) { // We can't actually know if the window is focused or not in wayland, // so just guess it with some common sense. Obviously won't work if - // the user has no keyboard. - if ((!wl->focused && wl->activated && wl->has_keyboard_input) || + // the user has no keyboard. We flag has_keyboard_input if + // at least one seat has it. + bool has_keyboard_input = false; + struct vo_wayland_seat *seat; + wl_list_for_each(seat, &wl->seat_list, link) { + if (seat->has_keyboard_input) { + has_keyboard_input = true; + } + } + + if ((!wl->focused && wl->activated && has_keyboard_input) || (wl->focused && !wl->activated)) { wl->focused = !wl->focused; @@ -1672,12 +1873,10 @@ static int lookupkey(int key) return mpkey; } -static void prepare_resize(struct vo_wayland_state *wl, int width, int height) +static void prepare_resize(struct vo_wayland_state *wl) { - if (!width) - width = mp_rect_w(wl->geometry) / wl->scaling; - if (!height) - height = mp_rect_h(wl->geometry) / wl->scaling; + int32_t width = mp_rect_w(wl->geometry) / wl->scaling; + int32_t height = mp_rect_h(wl->geometry) / wl->scaling; xdg_surface_set_window_geometry(wl->xdg_surface, 0, 0, width, height); wl->pending_vo_events |= VO_EVENT_RESIZE; } @@ -1690,6 +1889,9 @@ static void request_decoration_mode(struct vo_wayland_state *wl, uint32_t mode) static void rescale_geometry(struct vo_wayland_state *wl, double old_scale) { + if (!wl->vo_opts->hidpi_window_scale && !wl->locked_size) + return; + double factor = old_scale / wl->scaling; wl->window_size.x1 /= factor; wl->window_size.y1 /= factor; @@ -1734,6 +1936,37 @@ static void remove_output(struct vo_wayland_output *out) return; } +static void remove_seat(struct vo_wayland_seat *seat) +{ + if (!seat) + return; + + MP_VERBOSE(seat->wl, "Deregistering seat 0x%x\n", seat->id); + wl_list_remove(&seat->link); + if (seat == seat->wl->last_button_seat) + seat->wl->last_button_seat = NULL; + if (seat->keyboard) + wl_keyboard_destroy(seat->keyboard); + if (seat->pointer) + wl_pointer_destroy(seat->pointer); + if (seat->touch) + wl_touch_destroy(seat->touch); + if (seat->dnd_ddev) + wl_data_device_destroy(seat->dnd_ddev); +#if HAVE_WAYLAND_PROTOCOLS_1_32 + if (seat->cursor_shape_device) + wp_cursor_shape_device_v1_destroy(seat->cursor_shape_device); +#endif + if (seat->xkb_keymap) + xkb_keymap_unref(seat->xkb_keymap); + if (seat->xkb_state) + xkb_state_unref(seat->xkb_state); + + wl_seat_destroy(seat->seat); + talloc_free(seat); + return; +} + static void set_content_type(struct vo_wayland_state *wl) { if (!wl->content_type_manager) @@ -1748,20 +1981,23 @@ static void set_content_type(struct vo_wayland_state *wl) #endif } -static void set_cursor_shape(struct vo_wayland_state *wl) +static void set_cursor_shape(struct vo_wayland_seat *s) { #if HAVE_WAYLAND_PROTOCOLS_1_32 - wp_cursor_shape_device_v1_set_shape(wl->cursor_shape_device, wl->pointer_id, + wp_cursor_shape_device_v1_set_shape(s->cursor_shape_device, s->pointer_enter_serial, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT); #endif } -static int set_cursor_visibility(struct vo_wayland_state *wl, bool on) +static int set_cursor_visibility(struct vo_wayland_seat *s, bool on) { + if (!s) + return VO_FALSE; + struct vo_wayland_state *wl = s->wl; wl->cursor_visible = on; if (on) { - if (wl->cursor_shape_device) { - set_cursor_shape(wl); + if (s->cursor_shape_device) { + set_cursor_shape(s); } else { if (spawn_cursor(wl)) return VO_FALSE; @@ -1770,19 +2006,41 @@ static int set_cursor_visibility(struct vo_wayland_state *wl, bool on) if (!buffer) return VO_FALSE; int scale = MPMAX(wl->scaling, 1); - wl_pointer_set_cursor(wl->pointer, wl->pointer_id, wl->cursor_surface, + wl_pointer_set_cursor(s->pointer, s->pointer_enter_serial, wl->cursor_surface, img->hotspot_x / scale, img->hotspot_y / scale); - wl_surface_set_buffer_scale(wl->cursor_surface, scale); + wp_viewport_set_destination(wl->cursor_viewport, lround(img->width / scale), + lround(img->height / scale)); wl_surface_attach(wl->cursor_surface, buffer, 0, 0); wl_surface_damage_buffer(wl->cursor_surface, 0, 0, img->width, img->height); } wl_surface_commit(wl->cursor_surface); } else { - wl_pointer_set_cursor(wl->pointer, wl->pointer_id, NULL, 0, 0); + wl_pointer_set_cursor(s->pointer, s->pointer_enter_serial, NULL, 0, 0); } return VO_TRUE; } +static int set_cursor_visibility_all_seats(struct vo_wayland_state *wl, bool on) +{ + bool unavailable = true; + bool failed = false; + struct vo_wayland_seat *seat; + wl_list_for_each(seat, &wl->seat_list, link) { + if (seat->pointer) { + unavailable = false; + if (set_cursor_visibility(seat, on) == VO_FALSE) + failed = true; + } + } + + if (unavailable) + return VO_NOTAVAIL; + if (failed) + return VO_FALSE; + + return VO_TRUE; +} + static void set_geometry(struct vo_wayland_state *wl, bool resize) { struct vo *vo = wl->vo; @@ -1805,7 +2063,7 @@ static void set_geometry(struct vo_wayland_state *wl, bool resize) if (resize) { if (!wl->locked_size) wl->geometry = wl->window_size; - prepare_resize(wl, 0, 0); + prepare_resize(wl); } } @@ -1840,15 +2098,16 @@ static int set_screensaver_inhibitor(struct vo_wayland_state *wl, int state) static void set_surface_scaling(struct vo_wayland_state *wl) { - if (wl->fractional_scale_manager) + if (wl->scale_configured && (wl->fractional_scale_manager || + wl_surface_get_version(wl->surface) >= 6)) + { return; + } - // dmabuf_wayland is always wl->scaling = 1 double old_scale = wl->scaling; - wl->scaling = !wl->using_dmabuf_wayland ? wl->current_output->scale : 1; - + wl->scaling = wl->current_output->scale; rescale_geometry(wl, old_scale); - wl_surface_set_buffer_scale(wl->surface, wl->scaling); + wl->pending_vo_events |= VO_EVENT_DPI; } static void set_window_bounds(struct vo_wayland_state *wl) @@ -1862,22 +2121,34 @@ static void set_window_bounds(struct vo_wayland_state *wl) return; } + apply_keepaspect(wl, &wl->bounded_width, &wl->bounded_height); + if (wl->bounded_width && wl->bounded_width < wl->window_size.x1) wl->window_size.x1 = wl->bounded_width; if (wl->bounded_height && wl->bounded_height < wl->window_size.y1) wl->window_size.y1 = wl->bounded_height; } +static bool single_output_spanned(struct vo_wayland_state *wl) +{ + int outputs = 0; + struct vo_wayland_output *output; + wl_list_for_each(output, &wl->output_list, link) { + if (output->has_surface) + ++outputs; + if (outputs > 1) + return false; + } + return wl->current_output && outputs == 1; +} + static int spawn_cursor(struct vo_wayland_state *wl) { - /* Don't use this if we have cursor-shape. */ - if (wl->cursor_shape_device) - return 0; - /* Reuse if size is identical */ - if (!wl->pointer || wl->allocated_cursor_scale == wl->scaling) + if (wl->allocated_cursor_scale == wl->scaling) { return 0; - else if (wl->cursor_theme) + } else if (wl->cursor_theme) { wl_cursor_theme_destroy(wl->cursor_theme); + } const char *xcursor_theme = getenv("XCURSOR_THEME"); const char *size_str = getenv("XCURSOR_SIZE"); @@ -1896,9 +2167,12 @@ static int spawn_cursor(struct vo_wayland_state *wl) return 1; } - wl->default_cursor = wl_cursor_theme_get_cursor(wl->cursor_theme, "left_ptr"); + wl->default_cursor = wl_cursor_theme_get_cursor(wl->cursor_theme, "default"); + if (!wl->default_cursor) + wl->default_cursor = wl_cursor_theme_get_cursor(wl->cursor_theme, "left_ptr"); + if (!wl->default_cursor) { - MP_ERR(wl, "Unable to load cursor theme!\n"); + MP_ERR(wl, "Unable to get default and left_ptr XCursor from theme!\n"); return 1; } @@ -1909,9 +2183,6 @@ static int spawn_cursor(struct vo_wayland_state *wl) static void toggle_fullscreen(struct vo_wayland_state *wl) { - if (!wl->xdg_toplevel) - return; - wl->state_change = true; bool specific_screen = wl->vo_opts->fsscreen_id >= 0 || wl->vo_opts->fsscreen_name; if (wl->vo_opts->fullscreen && !specific_screen) { xdg_toplevel_set_fullscreen(wl->xdg_toplevel, NULL); @@ -1919,33 +2190,66 @@ static void toggle_fullscreen(struct vo_wayland_state *wl) struct vo_wayland_output *output = find_output(wl); xdg_toplevel_set_fullscreen(wl->xdg_toplevel, output->output); } else { + wl->state_change = wl->reconfigured; xdg_toplevel_unset_fullscreen(wl->xdg_toplevel); } } static void toggle_maximized(struct vo_wayland_state *wl) { - if (!wl->xdg_toplevel) - return; - wl->state_change = true; if (wl->vo_opts->window_maximized) { xdg_toplevel_set_maximized(wl->xdg_toplevel); } else { + wl->state_change = wl->reconfigured; xdg_toplevel_unset_maximized(wl->xdg_toplevel); } } static void update_app_id(struct vo_wayland_state *wl) { - if (!wl->xdg_toplevel) - return; xdg_toplevel_set_app_id(wl->xdg_toplevel, wl->vo_opts->appid); } +static void update_output_scaling(struct vo_wayland_state *wl) +{ + double old_scale = wl->scaling; + wl->scaling = wl->pending_scaling; + rescale_geometry(wl, old_scale); + set_geometry(wl, false); + prepare_resize(wl); + wl->need_rescale = false; + wl->pending_vo_events |= VO_EVENT_DPI; +} + +static void update_output_geometry(struct vo_wayland_state *wl, struct mp_rect old_geometry, + struct mp_rect old_output_geometry) +{ + if (wl->need_rescale) { + update_output_scaling(wl); + return; + } + + bool force_resize = false; + bool use_output_scale = wl_surface_get_version(wl->surface) < 6 && + !wl->fractional_scale_manager && + wl->scaling != wl->current_output->scale; + + if (use_output_scale) { + set_surface_scaling(wl); + force_resize = true; + } + + if (!mp_rect_equals(&old_output_geometry, &wl->current_output->geometry)) { + set_geometry(wl, false); + force_resize = true; + } + + if (!mp_rect_equals(&old_geometry, &wl->geometry) || force_resize) + prepare_resize(wl); +} + static int update_window_title(struct vo_wayland_state *wl, const char *title) { - if (!wl->xdg_toplevel) - return VO_NOTAVAIL; /* The xdg-shell protocol requires that the title is UTF-8. */ void *tmp = talloc_new(NULL); struct bstr b_title = bstr_sanitize_utf8_latin1(tmp, bstr0(title)); @@ -1954,12 +2258,6 @@ static int update_window_title(struct vo_wayland_state *wl, const char *title) return VO_TRUE; } -static void window_move(struct vo_wayland_state *wl, uint32_t serial) -{ - if (wl->xdg_toplevel) - xdg_toplevel_move(wl->xdg_toplevel, wl->seat, serial); -} - static void wayland_dispatch_events(struct vo_wayland_state *wl, int nfds, int64_t timeout_ns) { if (wl->display_fd == -1) @@ -1994,6 +2292,18 @@ static void wayland_dispatch_events(struct vo_wayland_state *wl, int nfds, int64 wl_display_dispatch_pending(wl->display); } +static void begin_dragging(struct vo_wayland_state *wl) +{ + struct vo_wayland_seat *s = wl->last_button_seat; + if (!mp_input_test_dragging(wl->vo->input_ctx, wl->mouse_x, wl->mouse_y) && + !wl->locked_size && s) + { + xdg_toplevel_move(wl->xdg_toplevel, s->seat, s->pointer_button_serial); + wl->last_button_seat = NULL; + mp_input_put_key(wl->vo->input_ctx, MP_INPUT_RELEASE_ALL); + } +} + /* Non-static */ int vo_wayland_allocate_memfd(struct vo *vo, size_t size) { @@ -2076,8 +2386,6 @@ int vo_wayland_control(struct vo *vo, int *events, int request, void *arg) set_input_region(wl, opts->cursor_passthrough); if (opt == &opts->fullscreen) toggle_fullscreen(wl); - if (opt == &opts->hidpi_window_scale) - set_geometry(wl, true); if (opt == &opts->window_maximized) toggle_maximized(wl); if (opt == &opts->window_minimized) @@ -2085,6 +2393,7 @@ int vo_wayland_control(struct vo *vo, int *events, int request, void *arg) if (opt == &opts->geometry || opt == &opts->autofit || opt == &opts->autofit_smaller || opt == &opts->autofit_larger) { + wl->state_change = true; set_geometry(wl, true); } } @@ -2123,6 +2432,7 @@ int vo_wayland_control(struct vo *vo, int *events, int request, void *arg) wl->window_size.x1 = s[0]; wl->window_size.y1 = s[1]; if (!wl->vo_opts->fullscreen && !wl->tiled) { + wl->state_change = true; if (wl->vo_opts->window_maximized) { xdg_toplevel_unset_maximized(wl->xdg_toplevel); wl_display_dispatch_pending(wl->display); @@ -2131,7 +2441,7 @@ int vo_wayland_control(struct vo *vo, int *events, int request, void *arg) return VO_TRUE; } wl->geometry = wl->window_size; - prepare_resize(wl, 0, 0); + prepare_resize(wl); } return VO_TRUE; } @@ -2166,12 +2476,13 @@ int vo_wayland_control(struct vo *vo, int *events, int request, void *arg) *(double *)arg = wl->scaling; return VO_TRUE; } + case VOCTRL_BEGIN_DRAGGING: + begin_dragging(wl); + return VO_TRUE; case VOCTRL_UPDATE_WINDOW_TITLE: return update_window_title(wl, (const char *)arg); case VOCTRL_SET_CURSOR_VISIBILITY: - if (!wl->pointer) - return VO_NOTAVAIL; - return set_cursor_visibility(wl, *(bool *)arg); + return set_cursor_visibility_all_seats(wl, *(bool *)arg); case VOCTRL_KILL_SCREENSAVER: return set_screensaver_inhibitor(wl, true); case VOCTRL_RESTORE_SCREENSAVER: @@ -2181,16 +2492,18 @@ int vo_wayland_control(struct vo *vo, int *events, int request, void *arg) return VO_NOTIMPL; } -void vo_wayland_handle_fractional_scale(struct vo_wayland_state *wl) +void vo_wayland_handle_scale(struct vo_wayland_state *wl) { - if (wl->fractional_scale_manager && wl->viewport) - wp_viewport_set_destination(wl->viewport, - round(mp_rect_w(wl->geometry) / wl->scaling), - round(mp_rect_h(wl->geometry) / wl->scaling)); + wp_viewport_set_destination(wl->viewport, + lround(mp_rect_w(wl->geometry) / wl->scaling), + lround(mp_rect_h(wl->geometry) / wl->scaling)); } bool vo_wayland_init(struct vo *vo) { + if (!getenv("WAYLAND_DISPLAY")) + goto err; + vo->wl = talloc_zero(NULL, struct vo_wayland_state); struct vo_wayland_state *wl = vo->wl; @@ -2209,9 +2522,10 @@ bool vo_wayland_init(struct vo *vo) .vo_opts_cache = m_config_cache_alloc(wl, vo->global, &vo_sub_opts), }; wl->vo_opts = wl->vo_opts_cache->opts; - wl->using_dmabuf_wayland = !strcmp(wl->vo->driver->name, "dmabuf-wayland"); + bool using_dmabuf_wayland = !strcmp(wl->vo->driver->name, "dmabuf-wayland"); wl_list_init(&wl->output_list); + wl_list_init(&wl->seat_list); if (!wl->display) goto err; @@ -2243,6 +2557,12 @@ bool vo_wayland_init(struct vo *vo) goto err; } + if (!wl->viewporter) { + MP_FATAL(wl, "Compositor doesn't support the required %s protocol!\n", + wp_viewporter_interface.name); + goto err; + } + /* Can't be initialized during registry due to multi-protocol dependence */ if (create_viewports(wl)) goto err; @@ -2279,10 +2599,13 @@ bool vo_wayland_init(struct vo *vo) } #endif - if (wl->dnd_devman && wl->seat) { - wl->dnd_ddev = wl_data_device_manager_get_data_device(wl->dnd_devman, wl->seat); - wl_data_device_add_listener(wl->dnd_ddev, &data_device_listener, wl); - } else if (!wl->dnd_devman) { + if (wl->dnd_devman) { + struct vo_wayland_seat *seat; + wl_list_for_each(seat, &wl->seat_list, link) { + seat->dnd_ddev = wl_data_device_manager_get_data_device(wl->dnd_devman, seat->seat); + wl_data_device_add_listener(seat->dnd_ddev, &data_device_listener, seat); + } + } else { MP_VERBOSE(wl, "Compositor doesn't support the %s (ver. 3) protocol!\n", wl_data_device_manager_interface.name); } @@ -2325,7 +2648,7 @@ bool vo_wayland_init(struct vo *vo) update_app_id(wl); mp_make_wakeup_pipe(wl->wakeup_pipe); - wl->callback_surface = wl->using_dmabuf_wayland ? wl->video_surface : wl->surface; + wl->callback_surface = using_dmabuf_wayland ? wl->video_surface : wl->surface; wl->frame_callback = wl_surface_frame(wl->callback_surface); wl_callback_add_listener(wl->frame_callback, &frame_listener, wl); wl_surface_commit(wl->surface); @@ -2352,33 +2675,38 @@ bool vo_wayland_reconfig(struct vo *vo) if (!wl->current_output) return false; set_surface_scaling(wl); + wl->scale_configured = true; wl->pending_vo_events |= VO_EVENT_DPI; } - if (wl->vo_opts->auto_window_resize || !wl->configured) + if (wl->vo_opts->auto_window_resize || !wl->geometry_configured) set_geometry(wl, false); + if (wl->geometry_configured && wl->vo_opts->auto_window_resize) + wl->reconfigured = true; + if (wl->opts->configure_bounds) set_window_bounds(wl); - if (!wl->configured || !wl->locked_size) { - wl->geometry = wl->window_size; - wl->configured = true; - } - if (wl->vo_opts->cursor_passthrough) set_input_region(wl, true); - if (wl->vo_opts->fullscreen) - toggle_fullscreen(wl); + if (!wl->geometry_configured || !wl->locked_size) + wl->geometry = wl->window_size; - if (wl->vo_opts->window_maximized) - toggle_maximized(wl); + if (!wl->geometry_configured) { + if (wl->vo_opts->fullscreen) + toggle_fullscreen(wl); - if (wl->vo_opts->window_minimized) - do_minimize(wl); + if (wl->vo_opts->window_maximized) + toggle_maximized(wl); - prepare_resize(wl, 0, 0); + if (wl->vo_opts->window_minimized) + do_minimize(wl); + wl->geometry_configured = true; + } + + prepare_resize(wl); return true; } @@ -2403,6 +2731,10 @@ void vo_wayland_uninit(struct vo *vo) if (!wl) return; + if (wl->dnd_fd != -1) + close(wl->dnd_fd); + free_dnd_data(wl); + mp_input_put_key(wl->vo->input_ctx, MP_INPUT_RELEASE_ALL); if (wl->compositor) @@ -2412,9 +2744,6 @@ void vo_wayland_uninit(struct vo *vo) wl_subcompositor_destroy(wl->subcompositor); #if HAVE_WAYLAND_PROTOCOLS_1_32 - if (wl->cursor_shape_device) - wp_cursor_shape_device_v1_destroy(wl->cursor_shape_device); - if (wl->cursor_shape_manager) wp_cursor_shape_manager_v1_destroy(wl->cursor_shape_manager); #endif @@ -2433,9 +2762,6 @@ void vo_wayland_uninit(struct vo *vo) wp_content_type_manager_v1_destroy(wl->content_type_manager); #endif - if (wl->dnd_ddev) - wl_data_device_destroy(wl->dnd_ddev); - if (wl->dnd_devman) wl_data_device_manager_destroy(wl->dnd_devman); @@ -2462,12 +2788,6 @@ void vo_wayland_uninit(struct vo *vo) if (wl->idle_inhibit_manager) zwp_idle_inhibit_manager_v1_destroy(wl->idle_inhibit_manager); - if (wl->keyboard) - wl_keyboard_destroy(wl->keyboard); - - if (wl->pointer) - wl_pointer_destroy(wl->pointer); - if (wl->presentation) wp_presentation_destroy(wl->presentation); @@ -2480,6 +2800,9 @@ void vo_wayland_uninit(struct vo *vo) if (wl->viewport) wp_viewport_destroy(wl->viewport); + if (wl->cursor_viewport) + wp_viewport_destroy(wl->cursor_viewport); + if (wl->osd_viewport) wp_viewport_destroy(wl->osd_viewport); @@ -2492,9 +2815,6 @@ void vo_wayland_uninit(struct vo *vo) if (wl->dmabuf_feedback) zwp_linux_dmabuf_feedback_v1_destroy(wl->dmabuf_feedback); - if (wl->seat) - wl_seat_destroy(wl->seat); - if (wl->shm) wl_shm_destroy(wl->shm); @@ -2536,16 +2856,14 @@ void vo_wayland_uninit(struct vo *vo) if (wl->xkb_context) xkb_context_unref(wl->xkb_context); - if (wl->xkb_keymap) - xkb_keymap_unref(wl->xkb_keymap); - - if (wl->xkb_state) - xkb_state_unref(wl->xkb_state); - - struct vo_wayland_output *output, *tmp; - wl_list_for_each_safe(output, tmp, &wl->output_list, link) + struct vo_wayland_output *output, *output_tmp; + wl_list_for_each_safe(output, output_tmp, &wl->output_list, link) remove_output(output); + struct vo_wayland_seat *seat, *seat_tmp; + wl_list_for_each_safe(seat, seat_tmp, &wl->seat_list, link) + remove_seat(seat); + if (wl->display) wl_display_disconnect(wl->display); diff --git a/video/out/wayland_common.h b/video/out/wayland_common.h index adbcca6..7a2f319 100644 --- a/video/out/wayland_common.h +++ b/video/out/wayland_common.h @@ -22,6 +22,8 @@ #include "input/event.h" #include "vo.h" +struct vo_wayland_seat; + typedef struct { uint32_t format; uint32_t padding; @@ -64,18 +66,18 @@ struct vo_wayland_state { int bounded_width; int reduced_height; int reduced_width; - int toplevel_width; - int toplevel_height; /* State */ bool activated; - bool configured; bool focused; bool frame_wait; - bool has_keyboard_input; + bool geometry_configured; bool hidden; bool initial_size_hint; bool locked_size; + bool need_rescale; + bool reconfigured; + bool scale_configured; bool state_change; bool tiled; bool toplevel_configured; @@ -83,6 +85,7 @@ struct vo_wayland_state { int mouse_x; int mouse_y; int pending_vo_events; + double pending_scaling; double scaling; int timeout_count; int wakeup_pipe[2]; @@ -96,7 +99,6 @@ struct vo_wayland_state { /* cursor-shape */ /* TODO: unvoid these if required wayland protocols is bumped to 1.32+ */ void *cursor_shape_manager; - void *cursor_shape_device; /* fractional-scale */ /* TODO: unvoid these if required wayland protocols is bumped to 1.31+ */ @@ -112,7 +114,6 @@ struct vo_wayland_state { struct zwp_linux_dmabuf_feedback_v1 *dmabuf_feedback; wayland_format *format_map; uint32_t format_size; - bool using_dmabuf_wayland; /* presentation-time */ struct wp_presentation *presentation; @@ -138,26 +139,18 @@ struct vo_wayland_state { /* viewporter */ struct wp_viewporter *viewporter; struct wp_viewport *viewport; + struct wp_viewport *cursor_viewport; struct wp_viewport *osd_viewport; struct wp_viewport *video_viewport; /* Input */ - struct wl_keyboard *keyboard; - struct wl_pointer *pointer; - struct wl_seat *seat; - struct wl_touch *touch; + struct wl_list seat_list; struct xkb_context *xkb_context; - struct xkb_keymap *xkb_keymap; - struct xkb_state *xkb_state; - uint32_t keyboard_code; - int mpkey; - int mpmod; /* DND */ - struct wl_data_device *dnd_ddev; struct wl_data_device_manager *dnd_devman; struct wl_data_offer *dnd_offer; - enum mp_dnd_action dnd_action; + int dnd_action; // actually enum mp_dnd_action char *dnd_mime_type; int dnd_fd; int dnd_mime_score; @@ -168,7 +161,7 @@ struct vo_wayland_state { struct wl_surface *cursor_surface; bool cursor_visible; int allocated_cursor_scale; - uint32_t pointer_id; + struct vo_wayland_seat *last_button_seat; }; bool vo_wayland_check_visible(struct vo *vo); @@ -178,7 +171,7 @@ bool vo_wayland_reconfig(struct vo *vo); int vo_wayland_allocate_memfd(struct vo *vo, size_t size); int vo_wayland_control(struct vo *vo, int *events, int request, void *arg); -void vo_wayland_handle_fractional_scale(struct vo_wayland_state *wl); +void vo_wayland_handle_scale(struct vo_wayland_state *wl); void vo_wayland_set_opaque_region(struct vo_wayland_state *wl, bool alpha); void vo_wayland_sync_swap(struct vo_wayland_state *wl); void vo_wayland_uninit(struct vo *vo); diff --git a/video/out/win32/droptarget.c b/video/out/win32/droptarget.c index 8a33522..fdf9c7a 100644 --- a/video/out/win32/droptarget.c +++ b/video/out/win32/droptarget.c @@ -18,6 +18,7 @@ #include <windows.h> #include <ole2.h> +#include <shellapi.h> #include <shobjidl.h> #include "common/msg.h" @@ -156,8 +157,10 @@ static STDMETHODIMP DropTarget_Drop(IDropTarget *self, IDataObject *pDataObj, wchar_t *buf = talloc_array(NULL, wchar_t, len + 1); if (DragQueryFileW(drop, i, buf, len + 1) == len) { - char *fname = mp_to_utf8(files, buf); + wchar_t *target = mp_w32_get_shell_link_target(buf); + char *fname = mp_to_utf8(files, target ? target : buf); files[recvd_files++] = fname; + talloc_free(target); MP_VERBOSE(t, "received dropped file: %s\n", fname); } else { diff --git a/video/out/win32/menu.c b/video/out/win32/menu.c new file mode 100644 index 0000000..25681e8 --- /dev/null +++ b/video/out/win32/menu.c @@ -0,0 +1,231 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <windows.h> +#include <string.h> + +#include "libmpv/client.h" +#include "osdep/io.h" +#include "mpv_talloc.h" + +#include "menu.h" + +struct menu_ctx { + HMENU menu; + void *ta_data; // talloc context for MENUITEMINFOW.dwItemData +}; + +// append menu item to HMENU +static int append_menu(HMENU hmenu, UINT fMask, UINT fType, UINT fState, + wchar_t *title, HMENU submenu, void *data) +{ + static UINT id = WM_USER + 100; + MENUITEMINFOW mii = {0}; + + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_ID | fMask; + mii.wID = id++; + + if (fMask & MIIM_FTYPE) + mii.fType = fType; + if (fMask & MIIM_STATE) + mii.fState = fState; + if (fMask & MIIM_STRING) { + mii.dwTypeData = title; + mii.cch = wcslen(title); + } + if (fMask & MIIM_SUBMENU) + mii.hSubMenu = submenu; + if (fMask & MIIM_DATA) + mii.dwItemData = (ULONG_PTR)data; + + return InsertMenuItemW(hmenu, -1, TRUE, &mii) ? mii.wID : -1; +} + +// build fState for menu item creation +static int build_state(mpv_node *node) +{ + int fState = 0; + for (int i = 0; i < node->u.list->num; i++) { + mpv_node *item = &node->u.list->values[i]; + if (item->format != MPV_FORMAT_STRING) + continue; + + if (strcmp(item->u.string, "hidden") == 0) { + return -1; + } else if (strcmp(item->u.string, "checked") == 0) { + fState |= MFS_CHECKED; + } else if (strcmp(item->u.string, "disabled") == 0) { + fState |= MFS_DISABLED; + } + } + return fState; +} + +// build dwTypeData for menu item creation +static wchar_t *build_title(void *talloc_ctx, char *title, char *shortcut) +{ + if (shortcut && shortcut[0]) { + char *buf = talloc_asprintf(NULL, "%s\t%s", title, shortcut); + wchar_t *wbuf = mp_from_utf8(talloc_ctx, buf); + talloc_free(buf); + return wbuf; + } + return mp_from_utf8(talloc_ctx, title); +} + +// build HMENU from mpv node +// +// node structure: +// +// MPV_FORMAT_NODE_ARRAY +// MPV_FORMAT_NODE_MAP (menu item) +// "type" MPV_FORMAT_STRING +// "title" MPV_FORMAT_STRING +// "cmd" MPV_FORMAT_STRING +// "shortcut" MPV_FORMAT_STRING +// "state" MPV_FORMAT_NODE_ARRAY[MPV_FORMAT_STRING] +// "submenu" MPV_FORMAT_NODE_ARRAY[menu item] +static void build_menu(void *talloc_ctx, HMENU hmenu, struct mpv_node *node) +{ + if (node->format != MPV_FORMAT_NODE_ARRAY) + return; + + for (int i = 0; i < node->u.list->num; i++) { + mpv_node *item = &node->u.list->values[i]; + if (item->format != MPV_FORMAT_NODE_MAP) + continue; + + mpv_node_list *list = item->u.list; + + char *type = ""; + char *title = NULL; + char *cmd = NULL; + char *shortcut = NULL; + int fState = 0; + HMENU submenu = NULL; + + for (int j = 0; j < list->num; j++) { + char *key = list->keys[j]; + mpv_node *value = &list->values[j]; + + switch (value->format) { + case MPV_FORMAT_STRING: + if (strcmp(key, "title") == 0) { + title = value->u.string; + } else if (strcmp(key, "cmd") == 0) { + cmd = value->u.string; + } else if (strcmp(key, "type") == 0) { + type = value->u.string; + } else if (strcmp(key, "shortcut") == 0) { + shortcut = value->u.string; + } + break; + case MPV_FORMAT_NODE_ARRAY: + if (strcmp(key, "state") == 0) { + fState = build_state(value); + } else if (strcmp(key, "submenu") == 0) { + submenu = CreatePopupMenu(); + build_menu(talloc_ctx, submenu, value); + } + break; + default: + break; + } + } + + if (fState == -1) // hidden + continue; + + if (strcmp(type, "separator") == 0) { + append_menu(hmenu, MIIM_FTYPE, MFT_SEPARATOR, 0, NULL, NULL, NULL); + } else { + if (title == NULL || title[0] == '\0') + continue; + + UINT fMask = MIIM_STRING | MIIM_STATE; + bool grayed = false; + if (strcmp(type, "submenu") == 0) { + if (submenu == NULL) + submenu = CreatePopupMenu(); + fMask |= MIIM_SUBMENU; + grayed = GetMenuItemCount(submenu) == 0; + } else { + fMask |= MIIM_DATA; + grayed = cmd == NULL || cmd[0] == '\0' || cmd[0] == '#' || + strcmp(cmd, "ignore") == 0; + } + int id = append_menu(hmenu, fMask, 0, (UINT)fState, + build_title(talloc_ctx, title, shortcut), + submenu, talloc_strdup(talloc_ctx, cmd)); + if (id > 0 && grayed) + EnableMenuItem(hmenu, id, MF_BYCOMMAND | MF_GRAYED); + } + } +} + +struct menu_ctx *mp_win32_menu_init(void) +{ + struct menu_ctx *ctx = talloc_ptrtype(NULL, ctx); + ctx->menu = CreatePopupMenu(); + ctx->ta_data = talloc_new(ctx); + return ctx; +} + +void mp_win32_menu_uninit(struct menu_ctx *ctx) +{ + DestroyMenu(ctx->menu); + talloc_free(ctx); +} + +void mp_win32_menu_show(struct menu_ctx *ctx, HWND hwnd) +{ + POINT pt; + RECT rc; + + if (!GetCursorPos(&pt)) + return; + + GetClientRect(hwnd, &rc); + ScreenToClient(hwnd, &pt); + + if (!PtInRect(&rc, pt)) + return; + + ClientToScreen(hwnd, &pt); + TrackPopupMenuEx(ctx->menu, TPM_LEFTALIGN | TPM_LEFTBUTTON, pt.x, pt.y, + hwnd, NULL); +} + +void mp_win32_menu_update(struct menu_ctx *ctx, struct mpv_node *data) +{ + while (GetMenuItemCount(ctx->menu) > 0) + RemoveMenu(ctx->menu, 0, MF_BYPOSITION); + talloc_free_children(ctx->ta_data); + + build_menu(ctx->ta_data, ctx->menu, data); +} + +const char* mp_win32_menu_get_cmd(struct menu_ctx *ctx, UINT id) +{ + MENUITEMINFOW mii = {0}; + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_DATA; + + GetMenuItemInfoW(ctx->menu, id, FALSE, &mii); + return (const char *)mii.dwItemData; +} diff --git a/osdep/macosx_menubar.h b/video/out/win32/menu.h index 509083d..8b1fe72 100644 --- a/osdep/macosx_menubar.h +++ b/video/out/win32/menu.h @@ -15,16 +15,18 @@ * License along with mpv. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef MPV_MACOSX_MENU -#define MPV_MACOSX_MENU +#ifndef MP_WIN32_MENU_H +#define MP_WIN32_MENU_H -// Menu Keys identifying menu items -typedef enum { - MPM_H_SIZE, - MPM_N_SIZE, - MPM_D_SIZE, - MPM_MINIMIZE, - MPM_ZOOM, -} MPMenuKey; +#include <windows.h> -#endif /* MPV_MACOSX_MENU */ +struct mpv_node; +struct menu_ctx; + +struct menu_ctx *mp_win32_menu_init(void); +void mp_win32_menu_uninit(struct menu_ctx *ctx); +void mp_win32_menu_show(struct menu_ctx *ctx, HWND hwnd); +void mp_win32_menu_update(struct menu_ctx *ctx, struct mpv_node *data); +const char* mp_win32_menu_get_cmd(struct menu_ctx *ctx, UINT id); + +#endif diff --git a/video/out/x11_common.c b/video/out/x11_common.c index b4605bf..fa2f2ba 100644 --- a/video/out/x11_common.c +++ b/video/out/x11_common.c @@ -29,6 +29,7 @@ #include <X11/Xlib.h> #include <X11/Xutil.h> #include <X11/Xatom.h> +#include <X11/Xresource.h> #include <X11/keysym.h> #include <X11/XKBlib.h> #include <X11/XF86keysym.h> @@ -98,7 +99,9 @@ #define MWM_FUNC_MAXIMIZE (1L << 4) #define MWM_FUNC_CLOSE (1L << 5) -#define MWM_DECOR_ALL (1L << 0) +// Equals to all MWM_DECOR_* OR'd together. +#define MWM_DECOR_ALL 126 +#define MWM_DECOR_TITLE (1L << 3) typedef struct { @@ -603,6 +606,70 @@ static void vo_x11_get_bounding_monitors(struct vo_x11_state *x11, long b[4]) } } +// Get the dpi scale of the x11 screen. Almost no GUI programs use this value +// nowadays, and it has inconsistent behavior for different drivers. +// (it returns the physical display DPI for proprietary NVIDIA driver only, +// but essentially a user-set prefrence value everywhere else) +static void vo_x11_get_x11_screen_dpi_scale(struct vo_x11_state *x11) +{ + int w_mm = DisplayWidthMM(x11->display, x11->screen); + int h_mm = DisplayHeightMM(x11->display, x11->screen); + double dpi_x = x11->ws_width * 25.4 / w_mm; + double dpi_y = x11->ws_height * 25.4 / h_mm; + double base_dpi = 96; + if (isfinite(dpi_x) && isfinite(dpi_y)) { + int s_x = lrint(MPCLAMP(2 * dpi_x / base_dpi, 0, 20)); + int s_y = lrint(MPCLAMP(2 * dpi_y / base_dpi, 0, 20)); + if (s_x == s_y && s_x > 2 && s_x < 20) { + x11->dpi_scale = s_x / 2.0; + MP_VERBOSE(x11, "Using X11 screen DPI scale: %g", x11->dpi_scale); + } + } +} + +// Get the dpi scale from the Xft.dpi resource. In practice, this value is much more +// commonly used by GUI programs for scaling compared to the x11 screen dpi. +// This is always a preference value so it's also more consistent. +static bool vo_x11_get_xft_dpi_scale(struct vo_x11_state *x11) +{ + XrmInitialize(); + char *resman = XResourceManagerString(x11->display); + if (!resman) + return false; + + XrmDatabase db = XrmGetStringDatabase(resman); + if (!db) + return false; + + XrmValue ret; + char *type; + double base_dpi = 96; + bool success = false; + if (XrmGetResource(db, "Xft.dpi", "String", &type, &ret) == True && + ret.addr && !strcmp("String", type)) + { + char *end; + long value = strtol(ret.addr, &end, 10); + if (*ret.addr && *end == '\0') { + int s = lrint(MPCLAMP(2 * value / base_dpi, 0, 20)); + if (s > 2 && s < 20) { + x11->dpi_scale = s / 2.0; + MP_VERBOSE(x11, "Using Xft.dpi scale: %g", x11->dpi_scale); + success = true; + } + } + } + XrmDestroyDatabase(db); + return success; +} + +static void vo_x11_get_dpi_scale(struct vo_x11_state *x11) +{ + if (!vo_x11_get_xft_dpi_scale(x11)) + vo_x11_get_x11_screen_dpi_scale(x11); + x11->pending_vo_events |= VO_EVENT_DPI; +} + bool vo_x11_init(struct vo *vo) { char *dispName; @@ -667,21 +734,7 @@ bool vo_x11_init(struct vo *vo) x11->ws_width, x11->ws_height, dispName, x11->display_is_local ? "local" : "remote"); - int w_mm = DisplayWidthMM(x11->display, x11->screen); - int h_mm = DisplayHeightMM(x11->display, x11->screen); - double dpi_x = x11->ws_width * 25.4 / w_mm; - double dpi_y = x11->ws_height * 25.4 / h_mm; - double base_dpi = 96; - if (isfinite(dpi_x) && isfinite(dpi_y) && x11->opts->hidpi_window_scale) { - int s_x = lrint(MPCLAMP(dpi_x / base_dpi, 0, 10)); - int s_y = lrint(MPCLAMP(dpi_y / base_dpi, 0, 10)); - if (s_x == s_y && s_x > 1 && s_x < 10) { - x11->dpi_scale = s_x; - MP_VERBOSE(x11, "Assuming DPI scale %d for prescaling. This can " - "be disabled with --hidpi-window-scale=no.\n", - x11->dpi_scale); - } - } + vo_x11_get_dpi_scale(x11); x11->wm_type = vo_wm_detect(vo); @@ -754,7 +807,8 @@ static const struct mp_keymap keymap[] = { {XF86XK_HomePage, MP_KEY_HOMEPAGE}, {XF86XK_WWW, MP_KEY_WWW}, {XF86XK_Mail, MP_KEY_MAIL}, {XF86XK_Favorites, MP_KEY_FAVORITES}, {XF86XK_Search, MP_KEY_SEARCH}, {XF86XK_Sleep, MP_KEY_SLEEP}, - {XF86XK_Back, MP_KEY_BACK}, {XF86XK_Tools, MP_KEY_TOOLS}, + {XF86XK_Back, MP_KEY_GO_BACK}, {XF86XK_Forward, MP_KEY_GO_FORWARD}, + {XF86XK_Tools, MP_KEY_TOOLS}, {XF86XK_ZoomIn, MP_KEY_ZOOMIN}, {XF86XK_ZoomOut, MP_KEY_ZOOMOUT}, {0, 0} @@ -783,7 +837,7 @@ static int vo_x11_lookupkey(int key) return mpkey; } -static void vo_x11_decoration(struct vo *vo, bool d) +static void vo_x11_decoration(struct vo *vo, bool decorations, bool title_bar) { struct vo_x11_state *x11 = vo->x11; @@ -794,8 +848,9 @@ static void vo_x11_decoration(struct vo *vo, bool d) MotifWmHints mhints = {0}; bool got = x11_get_property_copy(x11, x11->window, motif_hints, motif_hints, 32, &mhints, sizeof(mhints)); - // hints weren't set, and decorations requested -> assume WM displays them - if (!got && d) + // If hints weren't set, and decorations and title bar requested, + // assume WM displays them. + if (!got && decorations && title_bar) return; if (!got) { mhints.flags = MWM_HINTS_FUNCTIONS; @@ -803,7 +858,8 @@ static void vo_x11_decoration(struct vo *vo, bool d) MWM_FUNC_MAXIMIZE | MWM_FUNC_RESIZE; } mhints.flags |= MWM_HINTS_DECORATIONS; - mhints.decorations = d ? MWM_DECOR_ALL : 0; + mhints.decorations = decorations ? MWM_DECOR_ALL : 0; + mhints.decorations &= ~(!title_bar ? MWM_DECOR_TITLE : 0); XChangeProperty(x11->display, x11->window, motif_hints, motif_hints, 32, PropModeReplace, (unsigned char *) &mhints, 5); } @@ -1105,11 +1161,6 @@ static void vo_x11_check_net_wm_state_change(struct vo *vo) XFree(elems); } - if (opts->window_maximized && !is_maximized && x11->geometry_change) { - x11->geometry_change = false; - vo_x11_config_vo_window(vo); - } - opts->window_minimized = is_minimized; x11->hidden = is_minimized; m_config_cache_write_opt(x11->opts_cache, &opts->window_minimized); @@ -1163,7 +1214,28 @@ static void release_all_keys(struct vo *vo) if (x11->no_autorepeat) mp_input_put_key(x11->input_ctx, MP_INPUT_RELEASE_ALL); - x11->win_drag_button1_down = false; +} + +static void vo_x11_begin_dragging(struct vo *vo) +{ + struct vo_x11_state *x11 = vo->x11; + XEvent Event = x11->last_button_event; + if (Event.type == ButtonPress && !x11->fs && + !mp_input_test_dragging(x11->input_ctx, Event.xmotion.x, + Event.xmotion.y)) + { + mp_input_put_key(x11->input_ctx, MP_INPUT_RELEASE_ALL); + XUngrabPointer(x11->display, CurrentTime); + + long params[5] = { + Event.xmotion.x_root, Event.xmotion.y_root, + 8, // _NET_WM_MOVERESIZE_MOVE + Event.xbutton.button, + 1, // source indication: normal + }; + x11_send_ewmh_msg(x11, "_NET_WM_MOVERESIZE", params); + x11->last_button_event = (XEvent){0}; + } } void vo_x11_check_events(struct vo *vo) @@ -1176,6 +1248,8 @@ void vo_x11_check_events(struct vo *vo) while (XPending(display)) { XNextEvent(display, &Event); + if (XFilterEvent(&Event, x11->window)) + continue; MP_TRACE(x11, "XEvent: %d\n", Event.type); switch (Event.type) { case Expose: @@ -1232,30 +1306,12 @@ void vo_x11_check_events(struct vo *vo) release_all_keys(vo); break; case MotionNotify: - if (x11->win_drag_button1_down && !x11->fs && - !mp_input_test_dragging(x11->input_ctx, Event.xmotion.x, - Event.xmotion.y)) - { - mp_input_put_key(x11->input_ctx, MP_INPUT_RELEASE_ALL); - XUngrabPointer(x11->display, CurrentTime); - - long params[5] = { - Event.xmotion.x_root, Event.xmotion.y_root, - 8, // _NET_WM_MOVERESIZE_MOVE - 1, // button 1 - 1, // source indication: normal - }; - x11_send_ewmh_msg(x11, "_NET_WM_MOVERESIZE", params); - } else { - mp_input_set_mouse_pos(x11->input_ctx, Event.xmotion.x, - Event.xmotion.y); - } - x11->win_drag_button1_down = false; + mp_input_set_mouse_pos(x11->input_ctx, Event.xmotion.x, + Event.xmotion.y); break; case LeaveNotify: if (Event.xcrossing.mode != NotifyNormal) break; - x11->win_drag_button1_down = false; mp_input_put_key(x11->input_ctx, MP_KEY_MOUSE_LEAVE); break; case EnterNotify: @@ -1266,22 +1322,20 @@ void vo_x11_check_events(struct vo *vo) case ButtonPress: if (Event.xbutton.button - 1 >= MP_KEY_MOUSE_BTN_COUNT) break; - if (Event.xbutton.button == 1) - x11->win_drag_button1_down = true; mp_input_put_key(x11->input_ctx, (MP_MBTN_BASE + Event.xbutton.button - 1) | get_mods(Event.xbutton.state) | MP_KEY_STATE_DOWN); long msg[4] = {XEMBED_REQUEST_FOCUS}; vo_x11_xembed_send_message(x11, msg); + x11->last_button_event = Event; break; case ButtonRelease: if (Event.xbutton.button - 1 >= MP_KEY_MOUSE_BTN_COUNT) break; - if (Event.xbutton.button == 1) - x11->win_drag_button1_down = false; mp_input_put_key(x11->input_ctx, (MP_MBTN_BASE + Event.xbutton.button - 1) | get_mods(Event.xbutton.state) | MP_KEY_STATE_UP); + x11->last_button_event = Event; break; case MapNotify: x11->window_hidden = false; @@ -1343,6 +1397,7 @@ void vo_x11_check_events(struct vo *vo) } if (Event.type == x11->xrandr_event) { xrandr_read(x11); + vo_x11_get_dpi_scale(x11); vo_x11_update_geometry(vo); } break; @@ -1372,8 +1427,7 @@ static void vo_x11_sizehint(struct vo *vo, struct mp_rect rc, bool override_pos) override_pos; // for fullscreen and such XSizeHints *hint = XAllocSizeHints(); - if (!hint) - return; // OOM + MP_HANDLE_OOM(hint); hint->flags |= PSize | (force_pos ? PPosition : 0); hint->x = rc.x0; @@ -1579,7 +1633,7 @@ static void vo_x11_create_window(struct vo *vo, XVisualInfo *vis, if (x11->xim) { x11->xic = XCreateIC(x11->xim, - XNInputStyle, XIMPreeditNone | XIMStatusNone, + XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, x11->window, XNFocusWindow, x11->window, NULL); @@ -1601,7 +1655,7 @@ static void vo_x11_map_window(struct vo *vo, struct mp_rect rc) struct vo_x11_state *x11 = vo->x11; vo_x11_move_resize(vo, true, true, rc); - vo_x11_decoration(vo, x11->opts->border); + vo_x11_decoration(vo, x11->opts->border, x11->opts->title_bar); if (x11->opts->fullscreen && (x11->wm_type & vo_wm_FULLSCREEN)) { Atom state = XA(x11, _NET_WM_STATE_FULLSCREEN); @@ -1667,12 +1721,12 @@ static void vo_x11_map_window(struct vo *vo, struct mp_rect rc) vo_x11_xembed_update(x11, XEMBED_MAPPED); } -static void vo_x11_highlevel_resize(struct vo *vo, struct mp_rect rc) +static void vo_x11_highlevel_resize(struct vo *vo, struct mp_rect rc, bool force) { struct vo_x11_state *x11 = vo->x11; struct mp_vo_opts *opts = x11->opts; - bool reset_pos = opts->force_window_position; + bool reset_pos = opts->force_window_position || force; if (reset_pos) { x11->nofsrc = rc; } else { @@ -1742,10 +1796,6 @@ void vo_x11_config_vo_window(struct vo *vo) assert(x11->window); - // Don't attempt to change autofit/geometry on maximized windows. - if (x11->geometry_change && opts->window_maximized) - return; - vo_x11_update_screeninfo(vo); struct vo_win_geometry geo; @@ -1761,15 +1811,19 @@ void vo_x11_config_vo_window(struct vo *vo) bool reset_size = (x11->old_dw != RC_W(rc) || x11->old_dh != RC_H(rc)) && (opts->auto_window_resize || x11->geometry_change); + reset_size |= (x11->old_x != rc.x0 || x11->old_y != rc.y0) && + (x11->geometry_change); x11->old_dw = RC_W(rc); x11->old_dh = RC_H(rc); + x11->old_x = rc.x0; + x11->old_y = rc.y0; if (x11->window_hidden) { x11->nofsrc = rc; vo_x11_map_window(vo, rc); } else if (reset_size) { - vo_x11_highlevel_resize(vo, rc); + vo_x11_highlevel_resize(vo, rc, x11->geometry_change); } x11->geometry_change = false; @@ -1922,7 +1976,7 @@ static void vo_x11_fullscreen(struct vo *vo) rc = x11->screenrc; } - vo_x11_decoration(vo, opts->border && !x11->fs); + vo_x11_decoration(vo, opts->border && !x11->fs, opts->title_bar); vo_x11_sizehint(vo, rc, true); XMoveResizeWindow(x11->display, x11->window, rc.x0, rc.y0, @@ -2020,8 +2074,8 @@ int vo_x11_control(struct vo *vo, int *events, int request, void *arg) vo_x11_fullscreen(vo); if (opt == &opts->ontop) vo_x11_setlayer(vo, opts->ontop); - if (opt == &opts->border) - vo_x11_decoration(vo, opts->border); + if (opt == &opts->border || opt == &opts->title_bar) + vo_x11_decoration(vo, opts->border, opts->title_bar); if (opt == &opts->all_workspaces) vo_x11_sticky(vo, opts->all_workspaces); if (opt == &opts->window_minimized) @@ -2035,6 +2089,12 @@ int vo_x11_control(struct vo *vo, int *events, int request, void *arg) if (opt == &opts->geometry || opt == &opts->autofit || opt == &opts->autofit_smaller || opt == &opts->autofit_larger) { + if (opts->window_maximized && !opts->fullscreen) { + x11->opts->window_maximized = false; + m_config_cache_write_opt(x11->opts_cache, + &x11->opts->window_maximized); + vo_x11_maximize(vo); + } vo_x11_set_geometry(vo); } } @@ -2044,16 +2104,16 @@ int vo_x11_control(struct vo *vo, int *events, int request, void *arg) int *s = arg; if (!x11->window || x11->parent) return VO_FALSE; - s[0] = (x11->fs ? RC_W(x11->nofsrc) : RC_W(x11->winrc)) / x11->dpi_scale; - s[1] = (x11->fs ? RC_H(x11->nofsrc) : RC_H(x11->winrc)) / x11->dpi_scale; + s[0] = x11->fs ? RC_W(x11->nofsrc) : RC_W(x11->winrc); + s[1] = x11->fs ? RC_H(x11->nofsrc) : RC_H(x11->winrc); return VO_TRUE; } case VOCTRL_SET_UNFS_WINDOW_SIZE: { int *s = arg; if (!x11->window || x11->parent) return VO_FALSE; - int w = s[0] * x11->dpi_scale; - int h = s[1] * x11->dpi_scale; + int w = s[0]; + int h = s[1]; struct mp_rect rc = x11->winrc; rc.x1 = rc.x0 + w; rc.y1 = rc.y0 + h; @@ -2063,7 +2123,7 @@ int vo_x11_control(struct vo *vo, int *events, int request, void *arg) &x11->opts->window_maximized); vo_x11_maximize(vo); } - vo_x11_highlevel_resize(vo, rc); + vo_x11_highlevel_resize(vo, rc, false); if (!x11->fs) { // guess new window size, instead of waiting for X x11->winrc.x1 = x11->winrc.x0 + w; x11->winrc.y1 = x11->winrc.y0 + h; @@ -2151,6 +2211,9 @@ int vo_x11_control(struct vo *vo, int *events, int request, void *arg) case VOCTRL_GET_HIDPI_SCALE: *(double *)arg = x11->dpi_scale; return VO_TRUE; + case VOCTRL_BEGIN_DRAGGING: + vo_x11_begin_dragging(vo); + return VO_TRUE; } return VO_NOTIMPL; } diff --git a/video/out/x11_common.h b/video/out/x11_common.h index 62a96d7..b8ebfe0 100644 --- a/video/out/x11_common.h +++ b/video/out/x11_common.h @@ -61,7 +61,7 @@ struct vo_x11_state { int display_is_local; int ws_width; int ws_height; - int dpi_scale; + double dpi_scale; struct mp_rect screenrc; char *window_title; @@ -114,6 +114,7 @@ struct vo_x11_state { * stays the same (even if that size is different from the current * window size after the user modified the latter). */ int old_dw, old_dh; + int old_x, old_y; /* Video size changed during fullscreen when we couldn't tell the new * size to the window manager. Must set window size when turning * fullscreen off. */ @@ -137,10 +138,9 @@ struct vo_x11_state { Atom dnd_requested_action; Window dnd_src_window; - /* dragging the window */ - bool win_drag_button1_down; - Atom icc_profile_property; + + XEvent last_button_event; }; bool vo_x11_init(struct vo *vo); diff --git a/video/repack.c b/video/repack.c index ce3703a..0388493 100644 --- a/video/repack.c +++ b/video/repack.c @@ -63,21 +63,21 @@ struct mp_repack { int components[4]; // b[n] = mp_image.planes[components[n]] // pack: a is dst, b is src // unpack: a is src, b is dst - void (*packed_repack_scanline)(void *a, void *b[], int w); + void (*packed_repack_scanline)(void *restrict a, void *restrict b[], int w); // Fringe RGB/YUV. uint8_t comp_size; uint8_t comp_map[6]; uint8_t comp_shifts[3]; uint8_t *comp_lut; - void (*repack_fringe_yuv)(void *dst, void *src[], int w, uint8_t *c); + void (*repack_fringe_yuv)(void *restrict dst, void *restrict src[], int w, uint8_t *restrict c); // F32 repacking. int f32_comp_size; float f32_m[4], f32_o[4]; uint32_t f32_pmax[4]; - enum mp_csp f32_csp_space; - enum mp_csp_levels f32_csp_levels; + enum pl_color_system f32_csp_space; + enum pl_color_levels f32_csp_levels; // REPACK_STEP_REPACK: if true, need to copy this plane bool copy_buf[4]; @@ -95,7 +95,7 @@ static int find_gbrp_format(int depth, int num_planes) return 0; struct mp_regular_imgfmt desc = { .component_type = MP_COMPONENT_TYPE_UINT, - .forced_csp = MP_CSP_RGB, + .forced_csp = PL_COLOR_SYSTEM_RGB, .component_size = depth > 8 ? 2 : 1, .component_pad = depth - (depth > 8 ? 16 : 8), .num_planes = num_planes, @@ -133,8 +133,8 @@ static void copy_plane(struct mp_image *dst, int dst_x, int dst_y, assert(dst->fmt.bpp[p] == src->fmt.bpp[p]); for (int y = 0; y < h; y++) { - void *pd = mp_image_pixel_ptr_ny(dst, p, dst_x, dst_y + y); - void *ps = mp_image_pixel_ptr_ny(src, p, src_x, src_y + y); + void *restrict pd = mp_image_pixel_ptr_ny(dst, p, dst_x, dst_y + y); + void *restrict ps = mp_image_pixel_ptr_ny(src, p, src_x, src_y + y); memcpy(pd, ps, size); } } @@ -157,8 +157,8 @@ static void swap_endian(struct mp_image *dst, int dst_x, int dst_y, assert(src->fmt.bpp[p] == bpp * 8); for (int y = 0; y < h; y++) { - void *s = mp_image_pixel_ptr_ny(src, p, src_x, src_y + y); - void *d = mp_image_pixel_ptr_ny(dst, p, dst_x, dst_y + y); + void *restrict s = mp_image_pixel_ptr_ny(src, p, src_x, src_y + y); + void *restrict d = mp_image_pixel_ptr_ny(dst, p, dst_x, dst_y + y); switch (endian_size) { case 2: for (int x = 0; x < num_words; x++) @@ -191,7 +191,7 @@ static void swap_endian(struct mp_image *dst, int dst_x, int dst_y, // packers will use "z" because they write zero. #define PA_WORD_4(name, packed_t, plane_t, sh_c0, sh_c1, sh_c2, sh_c3) \ - static void name(void *dst, void *src[], int w) { \ + static void name(void *restrict dst, void *restrict src[], int w) { \ for (int x = 0; x < w; x++) { \ ((packed_t *)dst)[x] = \ ((packed_t)((plane_t *)src[0])[x] << (sh_c0)) | \ @@ -202,7 +202,7 @@ static void swap_endian(struct mp_image *dst, int dst_x, int dst_y, } #define UN_WORD_4(name, packed_t, plane_t, sh_c0, sh_c1, sh_c2, sh_c3, mask)\ - static void name(void *src, void *dst[], int w) { \ + static void name(void *restrict src, void *restrict dst[], int w) { \ for (int x = 0; x < w; x++) { \ packed_t c = ((packed_t *)src)[x]; \ ((plane_t *)dst[0])[x] = (c >> (sh_c0)) & (mask); \ @@ -214,7 +214,7 @@ static void swap_endian(struct mp_image *dst, int dst_x, int dst_y, #define PA_WORD_3(name, packed_t, plane_t, sh_c0, sh_c1, sh_c2, pad) \ - static void name(void *dst, void *src[], int w) { \ + static void name(void *restrict dst, void *restrict src[], int w) { \ for (int x = 0; x < w; x++) { \ ((packed_t *)dst)[x] = (pad) | \ ((packed_t)((plane_t *)src[0])[x] << (sh_c0)) | \ @@ -230,7 +230,7 @@ UN_WORD_4(un_cccc16, uint64_t, uint16_t, 0, 16, 32, 48, 0xFFFFu) PA_WORD_4(pa_cccc16, uint64_t, uint16_t, 0, 16, 32, 48) #define UN_WORD_3(name, packed_t, plane_t, sh_c0, sh_c1, sh_c2, mask) \ - static void name(void *src, void *dst[], int w) { \ + static void name(void *restrict src, void *restrict dst[], int w) { \ for (int x = 0; x < w; x++) { \ packed_t c = ((packed_t *)src)[x]; \ ((plane_t *)dst[0])[x] = (c >> (sh_c0)) & (mask); \ @@ -249,7 +249,7 @@ UN_WORD_3(un_ccc16x16, uint64_t, uint16_t, 0, 16, 32, 0xFFFFu) PA_WORD_3(pa_ccc16z16, uint64_t, uint16_t, 0, 16, 32, 0) #define PA_WORD_2(name, packed_t, plane_t, sh_c0, sh_c1, pad) \ - static void name(void *dst, void *src[], int w) { \ + static void name(void *restrict dst, void *restrict src[], int w) { \ for (int x = 0; x < w; x++) { \ ((packed_t *)dst)[x] = (pad) | \ ((packed_t)((plane_t *)src[0])[x] << (sh_c0)) | \ @@ -258,7 +258,7 @@ PA_WORD_3(pa_ccc16z16, uint64_t, uint16_t, 0, 16, 32, 0) } #define UN_WORD_2(name, packed_t, plane_t, sh_c0, sh_c1, mask) \ - static void name(void *src, void *dst[], int w) { \ + static void name(void *restrict src, void *restrict dst[], int w) { \ for (int x = 0; x < w; x++) { \ packed_t c = ((packed_t *)src)[x]; \ ((plane_t *)dst[0])[x] = (c >> (sh_c0)) & (mask); \ @@ -272,7 +272,7 @@ UN_WORD_2(un_cc16, uint32_t, uint16_t, 0, 16, 0xFFFFu) PA_WORD_2(pa_cc16, uint32_t, uint16_t, 0, 16, 0) #define PA_SEQ_3(name, comp_t) \ - static void name(void *dst, void *src[], int w) { \ + static void name(void *restrict dst, void *restrict src[], int w) { \ comp_t *r = dst; \ for (int x = 0; x < w; x++) { \ *r++ = ((comp_t *)src[0])[x]; \ @@ -282,7 +282,7 @@ PA_WORD_2(pa_cc16, uint32_t, uint16_t, 0, 16, 0) } #define UN_SEQ_3(name, comp_t) \ - static void name(void *src, void *dst[], int w) { \ + static void name(void *restrict src, void *restrict dst[], int w) { \ comp_t *r = src; \ for (int x = 0; x < w; x++) { \ ((comp_t *)dst[0])[x] = *r++; \ @@ -302,8 +302,8 @@ struct regular_repacker { int component_width; // number of bits for a single component int prepadding; // number of bits of LSB padding int num_components; // number of components that can be accessed - void (*pa_scanline)(void *a, void *b[], int w); - void (*un_scanline)(void *a, void *b[], int w); + void (*pa_scanline)(void *restrict a, void *restrict b[], int w); + void (*un_scanline)(void *restrict a, void *restrict b[], int w); }; static const struct regular_repacker regular_repackers[] = { @@ -384,7 +384,7 @@ static void setup_packed_packer(struct mp_repack *rp) int prepad = components[0] ? 0 : 8; int first_comp = components[0] ? 0 : 1; - void (*repack_cb)(void *pa, void *pb[], int w) = + void (*repack_cb)(void *restrict pa, void *restrict pb[], int w) = rp->pack ? pa->pa_scanline : pa->un_scanline; if (pa->packed_width != desc.bpp[0] || @@ -408,8 +408,8 @@ static void setup_packed_packer(struct mp_repack *rp) } #define PA_SHIFT_LUT8(name, packed_t) \ - static void name(void *dst, void *src[], int w, uint8_t *lut, \ - uint8_t s0, uint8_t s1, uint8_t s2) { \ + static void name(void *restrict dst, void *restrict src[], int w, \ + uint8_t *restrict lut, uint8_t s0, uint8_t s1, uint8_t s2) { \ for (int x = 0; x < w; x++) { \ ((packed_t *)dst)[x] = \ (lut[((uint8_t *)src[0])[x] + 256 * 0] << s0) | \ @@ -420,8 +420,8 @@ static void setup_packed_packer(struct mp_repack *rp) #define UN_SHIFT_LUT8(name, packed_t) \ - static void name(void *src, void *dst[], int w, uint8_t *lut, \ - uint8_t s0, uint8_t s1, uint8_t s2) { \ + static void name(void *restrict src, void *restrict dst[], int w, \ + uint8_t *restrict lut, uint8_t s0, uint8_t s1, uint8_t s2) { \ for (int x = 0; x < w; x++) { \ packed_t c = ((packed_t *)src)[x]; \ ((uint8_t *)dst[0])[x] = lut[((c >> s0) & 0xFF) + 256 * 0]; \ @@ -449,7 +449,7 @@ static void fringe_rgb_repack(struct mp_repack *rp, assert(rp->comp_size == 1 || rp->comp_size == 2); - void (*repack)(void *pa, void *pb[], int w, uint8_t *lut, + void (*repack)(void *restrict pa, void *restrict pb[], int w, uint8_t *restrict lut, uint8_t s0, uint8_t s1, uint8_t s2) = NULL; if (rp->pack) { repack = rp->comp_size == 1 ? pa_shift_lut8_8 : pa_shift_lut8_16; @@ -467,7 +467,7 @@ static void setup_fringe_rgb_packer(struct mp_repack *rp) return; if (desc.bpp[0] > 16 || (desc.bpp[0] % 8u) || - mp_imgfmt_get_forced_csp(rp->imgfmt_a) != MP_CSP_RGB || + mp_imgfmt_get_forced_csp(rp->imgfmt_a) != PL_COLOR_SYSTEM_RGB || desc.num_planes != 1 || desc.comps[3].size) return; @@ -525,10 +525,10 @@ static void unpack_pal(struct mp_repack *rp, struct mp_image *a, int a_x, int a_y, struct mp_image *b, int b_x, int b_y, int w) { - uint8_t *src = mp_image_pixel_ptr(a, 0, a_x, a_y); + uint8_t *restrict src = mp_image_pixel_ptr(a, 0, a_x, a_y); uint32_t *pal = (void *)a->planes[1]; - uint8_t *dst[4] = {0}; + uint8_t *restrict dst[4] = {0}; for (int p = 0; p < b->num_planes; p++) dst[p] = mp_image_pixel_ptr(b, p, b_x, b_y); @@ -545,8 +545,8 @@ static void bitmap_repack(struct mp_repack *rp, struct mp_image *a, int a_x, int a_y, struct mp_image *b, int b_x, int b_y, int w) { - uint8_t *pa = mp_image_pixel_ptr(a, 0, a_x, a_y); - uint8_t *pb = mp_image_pixel_ptr(b, 0, b_x, b_y); + uint8_t *restrict pa = mp_image_pixel_ptr(a, 0, a_x, a_y); + uint8_t *restrict pb = mp_image_pixel_ptr(b, 0, b_x, b_y); if (rp->pack) { for (unsigned x = 0; x < w; x += 8) { @@ -596,7 +596,7 @@ static void setup_misc_packer(struct mp_repack *rp) } #define PA_P422(name, comp_t) \ - static void name(void *dst, void *src[], int w, uint8_t *c) { \ + static void name(void *restrict dst, void *restrict src[], int w, uint8_t *restrict c) { \ for (int x = 0; x < w; x += 2) { \ ((comp_t *)dst)[x * 2 + c[0]] = ((comp_t *)src[0])[x + 0]; \ ((comp_t *)dst)[x * 2 + c[1]] = ((comp_t *)src[0])[x + 1]; \ @@ -607,7 +607,7 @@ static void setup_misc_packer(struct mp_repack *rp) #define UN_P422(name, comp_t) \ - static void name(void *src, void *dst[], int w, uint8_t *c) { \ + static void name(void *restrict src, void *restrict dst[], int w, uint8_t *restrict c) { \ for (int x = 0; x < w; x += 2) { \ ((comp_t *)dst[0])[x + 0] = ((comp_t *)src)[x * 2 + c[0]]; \ ((comp_t *)dst[0])[x + 1] = ((comp_t *)src)[x * 2 + c[1]]; \ @@ -621,7 +621,7 @@ PA_P422(pa_p422_16, uint16_t) UN_P422(un_p422_8, uint8_t) UN_P422(un_p422_16, uint16_t) -static void pa_p411_8(void *dst, void *src[], int w, uint8_t *c) +static void pa_p411_8(void *restrict dst, void *restrict src[], int w, uint8_t *restrict c) { for (int x = 0; x < w; x += 4) { ((uint8_t *)dst)[x / 4 * 6 + c[0]] = ((uint8_t *)src[0])[x + 0]; @@ -634,7 +634,7 @@ static void pa_p411_8(void *dst, void *src[], int w, uint8_t *c) } -static void un_p411_8(void *src, void *dst[], int w, uint8_t *c) +static void un_p411_8(void *restrict src, void *restrict dst[], int w, uint8_t *restrict c) { for (int x = 0; x < w; x += 4) { ((uint8_t *)dst[0])[x + 0] = ((uint8_t *)src)[x / 4 * 6 + c[0]]; @@ -773,7 +773,7 @@ static void setup_nv_packer(struct mp_repack *rp) for (int i = 0; i < MP_ARRAY_SIZE(regular_repackers); i++) { const struct regular_repacker *pa = ®ular_repackers[i]; - void (*repack_cb)(void *pa, void *pb[], int w) = + void (*repack_cb)(void *restrict pa, void *restrict pb[], int w) = rp->pack ? pa->pa_scanline : pa->un_scanline; if (pa->packed_width != desc.component_size * 2 * 8 || @@ -794,8 +794,8 @@ static void setup_nv_packer(struct mp_repack *rp) } #define PA_F32(name, packed_t) \ - static void name(void *dst, float *src, int w, float m, float o, \ - uint32_t p_max) { \ + static void name(void *restrict dst, float *restrict src, int w, float m, \ + float o, uint32_t p_max) { \ for (int x = 0; x < w; x++) { \ ((packed_t *)dst)[x] = \ MPCLAMP(lrint((src[x] + o) * m), 0, (packed_t)p_max); \ @@ -803,8 +803,8 @@ static void setup_nv_packer(struct mp_repack *rp) } #define UN_F32(name, packed_t) \ - static void name(void *src, float *dst, int w, float m, float o, \ - uint32_t unused) { \ + static void name(void *restrict src, float *restrict dst, int w, float m, \ + float o, uint32_t unused) { \ for (int x = 0; x < w; x++) \ dst[x] = ((packed_t *)src)[x] * m + o; \ } @@ -821,7 +821,7 @@ static void repack_float(struct mp_repack *rp, { assert(rp->f32_comp_size == 1 || rp->f32_comp_size == 2); - void (*packer)(void *a, float *b, int w, float fm, float fb, uint32_t max) + void (*packer)(void *restrict a, float *restrict b, int w, float fm, float fb, uint32_t max) = rp->pack ? (rp->f32_comp_size == 1 ? pa_f32_8 : pa_f32_16) : (rp->f32_comp_size == 1 ? un_f32_8 : un_f32_16); @@ -845,8 +845,8 @@ static void update_repack_float(struct mp_repack *rp) // Image in input format. struct mp_image *ui = rp->pack ? rp->steps[rp->num_steps - 1].buf[1] : rp->steps[0].buf[0]; - enum mp_csp csp = ui->params.color.space; - enum mp_csp_levels levels = ui->params.color.levels; + enum pl_color_system csp = ui->params.repr.sys; + enum pl_color_levels levels = ui->params.repr.levels; if (rp->f32_csp_space == csp && rp->f32_csp_levels == levels) return; @@ -989,8 +989,8 @@ static bool setup_format_ne(struct mp_repack *rp) (desc.component_size != 1 && desc.component_size != 2)) return false; rp->f32_comp_size = desc.component_size; - rp->f32_csp_space = MP_CSP_COUNT; - rp->f32_csp_levels = MP_CSP_LEVELS_COUNT; + rp->f32_csp_space = PL_COLOR_SYSTEM_COUNT; + rp->f32_csp_levels = PL_COLOR_LEVELS_COUNT; rp->steps[rp->num_steps++] = (struct repack_step) { .type = REPACK_STEP_FLOAT, .fmt = { diff --git a/video/sws_utils.c b/video/sws_utils.c index 5e9c358..a07bb55 100644 --- a/video/sws_utils.c +++ b/video/sws_utils.c @@ -24,6 +24,7 @@ #if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 37, 100) #include <libavutil/pixdesc.h> #endif +#include <libplacebo/utils/libav.h> #include "config.h" @@ -156,11 +157,11 @@ bool mp_sws_supports_formats(struct mp_sws_context *ctx, sws_isSupportedOutput(imgfmt2pixfmt(imgfmt_out)); } -static int mp_csp_to_sws_colorspace(enum mp_csp csp) +static int pl_csp_to_sws_colorspace(enum pl_color_system csp) { // The SWS_CS_* macros are just convenience redefinitions of the // AVCOL_SPC_* macros, inside swscale.h. - return mp_csp_to_avcol_spc(csp); + return pl_system_to_av(csp); } static bool cache_valid(struct mp_sws_context *ctx) @@ -289,11 +290,11 @@ int mp_sws_reinit(struct mp_sws_context *ctx) return -1; } - int s_csp = mp_csp_to_sws_colorspace(src.color.space); - int s_range = src.color.levels == MP_CSP_LEVELS_PC; + int s_csp = pl_csp_to_sws_colorspace(src.repr.sys); + int s_range = src.repr.levels == PL_COLOR_LEVELS_FULL; - int d_csp = mp_csp_to_sws_colorspace(dst.color.space); - int d_range = dst.color.levels == MP_CSP_LEVELS_PC; + int d_csp = pl_csp_to_sws_colorspace(src.repr.sys); + int d_range = dst.repr.levels == PL_COLOR_LEVELS_FULL; av_opt_set_int(ctx->sws, "sws_flags", ctx->flags, 0); @@ -308,8 +309,8 @@ int mp_sws_reinit(struct mp_sws_context *ctx) av_opt_set_double(ctx->sws, "param0", ctx->params[0], 0); av_opt_set_double(ctx->sws, "param1", ctx->params[1], 0); - int cr_src = mp_chroma_location_to_av(src.chroma_location); - int cr_dst = mp_chroma_location_to_av(dst.chroma_location); + int cr_src = pl_chroma_to_av(src.chroma_location); + int cr_dst = pl_chroma_to_av(dst.chroma_location); int cr_xpos, cr_ypos; #if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 37, 100) if (av_chroma_location_enum_to_pos(&cr_xpos, &cr_ypos, cr_src) >= 0) { @@ -407,11 +408,11 @@ int mp_sws_scale(struct mp_sws_context *ctx, struct mp_image *dst, return mp_zimg_convert(ctx->zimg, dst, src) ? 0 : -1; #endif - if (src->params.color.space == MP_CSP_XYZ && dst->params.color.space != MP_CSP_XYZ) { + if (src->params.repr.sys == PL_COLOR_SYSTEM_XYZ && dst->params.repr.sys != PL_COLOR_SYSTEM_XYZ) { // swsscale has hardcoded gamma 2.2 internally and 2.6 for XYZ - dst->params.color.gamma = MP_CSP_TRC_GAMMA22; + dst->params.color.transfer = PL_COLOR_TRC_GAMMA22; // and sRGB primaries... - dst->params.color.primaries = MP_CSP_PRIM_BT_709; + dst->params.color.primaries = PL_COLOR_PRIM_BT_709; // it doesn't adjust white point though, but it is not worth to support // this case. It would require custom prim with equal energy white point // and sRGB primaries. diff --git a/video/vaapi.c b/video/vaapi.c index 08248a7..666fd00 100644 --- a/video/vaapi.c +++ b/video/vaapi.c @@ -21,6 +21,7 @@ #include "vaapi.h" #include "common/common.h" +#include "common/global.h" #include "common/msg.h" #include "osdep/threads.h" #include "mp_image.h" @@ -28,9 +29,28 @@ #include "mp_image_pool.h" #include "options/m_config.h" +#ifdef _WIN32 +#include "osdep/windows_utils.h" +#include "out/d3d11/context.h" +#include "out/gpu/d3d11_helpers.h" +#endif + #include <libavutil/hwcontext.h> #include <libavutil/hwcontext_vaapi.h> +#ifdef _WIN32 +#define DEV_PATH_DEFAULT NULL +#define DEV_PATH_VALIDATE mp_dxgi_validate_adapter +#else +#define DEV_PATH_DEFAULT "/dev/dri/renderD128" +#define DEV_PATH_VALIDATE validate_path + +static inline OPT_STRING_VALIDATE_FUNC(validate_path) +{ + return (*value && **value) ? 0 : M_OPT_INVALID; +} +#endif + struct vaapi_opts { char *path; }; @@ -38,21 +58,21 @@ struct vaapi_opts { #define OPT_BASE_STRUCT struct vaapi_opts const struct m_sub_options vaapi_conf = { .opts = (const struct m_option[]) { - {"device", OPT_STRING(path)}, + {"device", OPT_STRING_VALIDATE(path, DEV_PATH_VALIDATE)}, {0}, }, .defaults = &(const struct vaapi_opts) { - .path = "/dev/dri/renderD128", + .path = DEV_PATH_DEFAULT, }, .size = sizeof(struct vaapi_opts), }; -int va_get_colorspace_flag(enum mp_csp csp) +int va_get_colorspace_flag(enum pl_color_system csp) { switch (csp) { - case MP_CSP_BT_601: return VA_SRC_BT601; - case MP_CSP_BT_709: return VA_SRC_BT709; - case MP_CSP_SMPTE_240M: return VA_SRC_SMPTE_240; + case PL_COLOR_SYSTEM_BT_601: return VA_SRC_BT601; + case PL_COLOR_SYSTEM_BT_709: return VA_SRC_BT709; + case PL_COLOR_SYSTEM_SMPTE_240M: return VA_SRC_SMPTE_240; } return 0; } @@ -70,7 +90,7 @@ static void va_error_callback(void *context, const char *msg) static void va_info_callback(void *context, const char *msg) { - va_message_callback(context, msg, MSGL_DEBUG); + va_message_callback(context, msg, MSGL_V); } static void free_device_ref(struct AVHWDeviceContext *hwctx) @@ -166,8 +186,8 @@ bool va_guess_if_emulated(struct mp_vaapi_ctx *ctx) } struct va_native_display { - void (*create)(VADisplay **out_display, void **out_native_ctx, - const char *path); + void (*create)(struct mp_log *log, VADisplay **out_display, + void **out_native_ctx, const char *path); void (*destroy)(void *native_ctx); }; @@ -180,8 +200,8 @@ static void x11_destroy(void *native_ctx) XCloseDisplay(native_ctx); } -static void x11_create(VADisplay **out_display, void **out_native_ctx, - const char *path) +static void x11_create(struct mp_log *log, VADisplay **out_display, + void **out_native_ctx, const char *path) { void *native_display = XOpenDisplay(NULL); if (!native_display) @@ -200,6 +220,31 @@ static const struct va_native_display disp_x11 = { }; #endif +#if HAVE_VAAPI_WIN32 +#include <va/va_win32.h> + +static void win32_create(struct mp_log *log, VADisplay **out_display, + void **out_native_ctx, const char *path) +{ + LUID *luid = NULL; + DXGI_ADAPTER_DESC1 desc = {0}; + if (path && path[0]) { + IDXGIAdapter1 *adapter = mp_get_dxgi_adapter(log, bstr0(path), NULL); + if (!adapter || FAILED(IDXGIAdapter1_GetDesc1(adapter, &desc))) { + mp_err(log, "Failed to get adapter LUID for name: %s\n", path); + } else { + luid = &desc.AdapterLuid; + } + SAFE_RELEASE(adapter); + } + *out_display = vaGetDisplayWin32(luid); +} + +static const struct va_native_display disp_win32 = { + .create = win32_create, +}; +#endif + #if HAVE_VAAPI_DRM #include <unistd.h> #include <fcntl.h> @@ -216,8 +261,8 @@ static void drm_destroy(void *native_ctx) talloc_free(ctx); } -static void drm_create(VADisplay **out_display, void **out_native_ctx, - const char *path) +static void drm_create(struct mp_log *log, VADisplay **out_display, + void **out_native_ctx, const char *path) { int drm_fd = open(path, O_RDWR); if (drm_fd < 0) @@ -245,6 +290,9 @@ static const struct va_native_display *const native_displays[] = { #if HAVE_VAAPI_DRM &disp_drm, #endif +#if HAVE_VAAPI_WIN32 + &disp_win32, +#endif #if HAVE_VAAPI_X11 &disp_x11, #endif @@ -260,13 +308,14 @@ static struct AVBufferRef *va_create_standalone(struct mpv_global *global, for (int n = 0; native_displays[n]; n++) { VADisplay *display = NULL; void *native_ctx = NULL; - native_displays[n]->create(&display, &native_ctx, opts->path); + native_displays[n]->create(global->log, &display, &native_ctx, opts->path); if (display) { struct mp_vaapi_ctx *ctx = va_initialize(display, log, params->probing); if (!ctx) { vaTerminate(display); - native_displays[n]->destroy(native_ctx); + if (native_displays[n]->destroy) + native_displays[n]->destroy(native_ctx); goto end; } ctx->native_ctx = native_ctx; diff --git a/video/vaapi.h b/video/vaapi.h index 56235bc..3fdf3c1 100644 --- a/video/vaapi.h +++ b/video/vaapi.h @@ -42,7 +42,7 @@ struct mp_vaapi_ctx { #define CHECK_VA_STATUS(ctx, msg) \ CHECK_VA_STATUS_LEVEL(ctx, msg, MSGL_ERR) -int va_get_colorspace_flag(enum mp_csp csp); +int va_get_colorspace_flag(enum pl_color_system csp); struct mp_vaapi_ctx * va_initialize(VADisplay *display, struct mp_log *plog, bool probing); void va_destroy(struct mp_vaapi_ctx *ctx); diff --git a/video/vdpau_mixer.c b/video/vdpau_mixer.c index b1aed70..e062dcc 100644 --- a/video/vdpau_mixer.c +++ b/video/vdpau_mixer.c @@ -193,7 +193,7 @@ static int create_vdp_mixer(struct mp_vdpau_mixer *mixer, if (!opts->chroma_deint) SET_VIDEO_ATTR(SKIP_CHROMA_DEINTERLACE, uint8_t, 1); - struct mp_cmat yuv2rgb; + struct pl_transform3x3 yuv2rgb; VdpCSCMatrix matrix; struct mp_csp_params cparams = MP_CSP_PARAMS_DEFAULTS; @@ -204,7 +204,7 @@ static int create_vdp_mixer(struct mp_vdpau_mixer *mixer, for (int r = 0; r < 3; r++) { for (int c = 0; c < 3; c++) - matrix[r][c] = yuv2rgb.m[r][c]; + matrix[r][c] = yuv2rgb.mat.m[r][c]; matrix[r][3] = yuv2rgb.c[r]; } @@ -277,7 +277,7 @@ int mp_vdpau_mixer_render(struct mp_vdpau_mixer *mixer, CHECK_VDP_ERROR(mixer, "Error when calling vdp_video_surface_get_parameters"); if (!mixer->initialized || !opts_equal(opts, &mixer->opts) || - !mp_image_params_equal(&video->params, &mixer->image_params) || + !mp_image_params_static_equal(&video->params, &mixer->image_params) || mixer->current_w != s_w || mixer->current_h != s_h || mixer->current_chroma_type != s_chroma_type) { diff --git a/video/zimg.c b/video/zimg.c index 5ff300c..907e81d 100644 --- a/video/zimg.c +++ b/video/zimg.c @@ -121,76 +121,79 @@ static void mp_zimg_update_from_cmdline(struct mp_zimg_context *ctx) ctx->opts = *opts; } -static zimg_chroma_location_e mp_to_z_chroma(enum mp_chroma_location cl) +static zimg_chroma_location_e pl_to_z_chroma(enum pl_chroma_location cl) { switch (cl) { - case MP_CHROMA_TOPLEFT: return ZIMG_CHROMA_TOP_LEFT; - case MP_CHROMA_LEFT: return ZIMG_CHROMA_LEFT; - case MP_CHROMA_CENTER: return ZIMG_CHROMA_CENTER; - default: return ZIMG_CHROMA_LEFT; + case PL_CHROMA_LEFT: return ZIMG_CHROMA_LEFT; + case PL_CHROMA_CENTER: return ZIMG_CHROMA_CENTER; + case PL_CHROMA_TOP_LEFT: return ZIMG_CHROMA_TOP_LEFT; + case PL_CHROMA_TOP_CENTER: return ZIMG_CHROMA_TOP; + case PL_CHROMA_BOTTOM_LEFT: return ZIMG_CHROMA_BOTTOM_LEFT; + case PL_CHROMA_BOTTOM_CENTER: return ZIMG_CHROMA_BOTTOM; + default: return ZIMG_CHROMA_LEFT; } } -static zimg_matrix_coefficients_e mp_to_z_matrix(enum mp_csp csp) +static zimg_matrix_coefficients_e pl_to_z_matrix(enum pl_color_system csp) { switch (csp) { - case MP_CSP_BT_601: return ZIMG_MATRIX_BT470_BG; - case MP_CSP_BT_709: return ZIMG_MATRIX_BT709; - case MP_CSP_SMPTE_240M: return ZIMG_MATRIX_ST240_M; - case MP_CSP_BT_2020_NC: return ZIMG_MATRIX_BT2020_NCL; - case MP_CSP_BT_2020_C: return ZIMG_MATRIX_BT2020_CL; - case MP_CSP_RGB: return ZIMG_MATRIX_RGB; - case MP_CSP_XYZ: return ZIMG_MATRIX_RGB; - case MP_CSP_YCGCO: return ZIMG_MATRIX_YCGCO; + case PL_COLOR_SYSTEM_BT_601: return ZIMG_MATRIX_BT470_BG; + case PL_COLOR_SYSTEM_BT_709: return ZIMG_MATRIX_BT709; + case PL_COLOR_SYSTEM_SMPTE_240M: return ZIMG_MATRIX_ST240_M; + case PL_COLOR_SYSTEM_BT_2020_NC: return ZIMG_MATRIX_BT2020_NCL; + case PL_COLOR_SYSTEM_BT_2020_C: return ZIMG_MATRIX_BT2020_CL; + case PL_COLOR_SYSTEM_RGB: return ZIMG_MATRIX_RGB; + case PL_COLOR_SYSTEM_XYZ: return ZIMG_MATRIX_RGB; + case PL_COLOR_SYSTEM_YCGCO: return ZIMG_MATRIX_YCGCO; default: return ZIMG_MATRIX_BT709; } } -static zimg_transfer_characteristics_e mp_to_z_trc(enum mp_csp_trc trc) +static zimg_transfer_characteristics_e pl_to_z_trc(enum pl_color_transfer trc) { switch (trc) { - case MP_CSP_TRC_BT_1886: return ZIMG_TRANSFER_BT709; - case MP_CSP_TRC_SRGB: return ZIMG_TRANSFER_IEC_61966_2_1; - case MP_CSP_TRC_LINEAR: return ZIMG_TRANSFER_LINEAR; - case MP_CSP_TRC_GAMMA22: return ZIMG_TRANSFER_BT470_M; - case MP_CSP_TRC_GAMMA28: return ZIMG_TRANSFER_BT470_BG; - case MP_CSP_TRC_PQ: return ZIMG_TRANSFER_ST2084; - case MP_CSP_TRC_HLG: return ZIMG_TRANSFER_ARIB_B67; + case PL_COLOR_TRC_BT_1886: return ZIMG_TRANSFER_BT709; + case PL_COLOR_TRC_SRGB: return ZIMG_TRANSFER_IEC_61966_2_1; + case PL_COLOR_TRC_LINEAR: return ZIMG_TRANSFER_LINEAR; + case PL_COLOR_TRC_GAMMA22: return ZIMG_TRANSFER_BT470_M; + case PL_COLOR_TRC_GAMMA28: return ZIMG_TRANSFER_BT470_BG; + case PL_COLOR_TRC_PQ: return ZIMG_TRANSFER_ST2084; + case PL_COLOR_TRC_HLG: return ZIMG_TRANSFER_ARIB_B67; #if HAVE_ZIMG_ST428 - case MP_CSP_TRC_ST428: return ZIMG_TRANSFER_ST428; + case PL_COLOR_TRC_ST428: return ZIMG_TRANSFER_ST428; #endif - case MP_CSP_TRC_GAMMA18: // ? - case MP_CSP_TRC_GAMMA20: - case MP_CSP_TRC_GAMMA24: - case MP_CSP_TRC_GAMMA26: - case MP_CSP_TRC_PRO_PHOTO: - case MP_CSP_TRC_V_LOG: - case MP_CSP_TRC_S_LOG1: - case MP_CSP_TRC_S_LOG2: // ? + case PL_COLOR_TRC_GAMMA18: // ? + case PL_COLOR_TRC_GAMMA20: + case PL_COLOR_TRC_GAMMA24: + case PL_COLOR_TRC_GAMMA26: + case PL_COLOR_TRC_PRO_PHOTO: + case PL_COLOR_TRC_V_LOG: + case PL_COLOR_TRC_S_LOG1: + case PL_COLOR_TRC_S_LOG2: // ? default: return ZIMG_TRANSFER_BT709; } } -static zimg_color_primaries_e mp_to_z_prim(enum mp_csp_prim prim) +static zimg_color_primaries_e mp_to_z_prim(enum pl_color_primaries prim) { switch (prim) { - case MP_CSP_PRIM_BT_601_525:return ZIMG_PRIMARIES_ST170_M; - case MP_CSP_PRIM_BT_601_625:return ZIMG_PRIMARIES_BT470_BG; - case MP_CSP_PRIM_BT_709: return ZIMG_PRIMARIES_BT709; - case MP_CSP_PRIM_BT_2020: return ZIMG_PRIMARIES_BT2020; - case MP_CSP_PRIM_BT_470M: return ZIMG_PRIMARIES_BT470_M; - case MP_CSP_PRIM_DCI_P3: return ZIMG_PRIMARIES_ST431_2; - case MP_CSP_PRIM_DISPLAY_P3:return ZIMG_PRIMARIES_ST432_1; - case MP_CSP_PRIM_EBU_3213: return ZIMG_PRIMARIES_EBU3213_E; - case MP_CSP_PRIM_FILM_C: return ZIMG_PRIMARIES_FILM; - case MP_CSP_PRIM_CIE_1931: - case MP_CSP_PRIM_APPLE: // ? - case MP_CSP_PRIM_ADOBE: - case MP_CSP_PRIM_PRO_PHOTO: - case MP_CSP_PRIM_V_GAMUT: - case MP_CSP_PRIM_S_GAMUT: // ? - case MP_CSP_PRIM_ACES_AP0: - case MP_CSP_PRIM_ACES_AP1: + case PL_COLOR_PRIM_BT_601_525:return ZIMG_PRIMARIES_ST170_M; + case PL_COLOR_PRIM_BT_601_625:return ZIMG_PRIMARIES_BT470_BG; + case PL_COLOR_PRIM_BT_709: return ZIMG_PRIMARIES_BT709; + case PL_COLOR_PRIM_BT_2020: return ZIMG_PRIMARIES_BT2020; + case PL_COLOR_PRIM_BT_470M: return ZIMG_PRIMARIES_BT470_M; + case PL_COLOR_PRIM_DCI_P3: return ZIMG_PRIMARIES_ST431_2; + case PL_COLOR_PRIM_DISPLAY_P3:return ZIMG_PRIMARIES_ST432_1; + case PL_COLOR_PRIM_EBU_3213: return ZIMG_PRIMARIES_EBU3213_E; + case PL_COLOR_PRIM_FILM_C: return ZIMG_PRIMARIES_FILM; + case PL_COLOR_PRIM_CIE_1931: + case PL_COLOR_PRIM_APPLE: // ? + case PL_COLOR_PRIM_ADOBE: + case PL_COLOR_PRIM_PRO_PHOTO: + case PL_COLOR_PRIM_V_GAMUT: + case PL_COLOR_PRIM_S_GAMUT: // ? + case PL_COLOR_PRIM_ACES_AP0: + case PL_COLOR_PRIM_ACES_AP1: default: return ZIMG_PRIMARIES_BT709; } } @@ -375,7 +378,7 @@ static bool setup_format(zimg_image_format *zfmt, struct mp_zimg_repack *r, r->z_planes[3] = n; // alpha, always plane 4 in zimg #if HAVE_ZIMG_ALPHA - zfmt->alpha = fmt.alpha == MP_ALPHA_PREMUL + zfmt->alpha = fmt.repr.alpha == PL_ALPHA_PREMULTIPLIED ? ZIMG_ALPHA_PREMULTIPLIED : ZIMG_ALPHA_STRAIGHT; #else return false; @@ -414,7 +417,7 @@ static bool setup_format(zimg_image_format *zfmt, struct mp_zimg_repack *r, zfmt->color_family = ZIMG_COLOR_YUV; if (desc.num_planes <= 2) { zfmt->color_family = ZIMG_COLOR_GREY; - } else if (fmt.color.space == MP_CSP_RGB || fmt.color.space == MP_CSP_XYZ) { + } else if (fmt.repr.sys == PL_COLOR_SYSTEM_RGB || fmt.repr.sys == PL_COLOR_SYSTEM_XYZ) { zfmt->color_family = ZIMG_COLOR_RGB; } @@ -441,16 +444,16 @@ static bool setup_format(zimg_image_format *zfmt, struct mp_zimg_repack *r, // (Formats like P010 are basically reported as P016.) zfmt->depth = desc.component_size * 8 + MPMIN(0, desc.component_pad); - zfmt->pixel_range = fmt.color.levels == MP_CSP_LEVELS_PC ? + zfmt->pixel_range = fmt.repr.levels == PL_COLOR_LEVELS_FULL ? ZIMG_RANGE_FULL : ZIMG_RANGE_LIMITED; - zfmt->matrix_coefficients = mp_to_z_matrix(fmt.color.space); - zfmt->transfer_characteristics = mp_to_z_trc(fmt.color.gamma); - // For MP_CSP_XYZ only valid primaries are defined in ST 428-1 - zfmt->color_primaries = fmt.color.space == MP_CSP_XYZ + zfmt->matrix_coefficients = pl_to_z_matrix(fmt.repr.sys); + zfmt->transfer_characteristics = pl_to_z_trc(fmt.color.transfer); + // For PL_COLOR_SYSTEM_XYZ only valid primaries are defined in ST 428-1 + zfmt->color_primaries = fmt.repr.sys == PL_COLOR_SYSTEM_XYZ ? ZIMG_PRIMARIES_ST428 : mp_to_z_prim(fmt.color.primaries); - zfmt->chroma_location = mp_to_z_chroma(fmt.chroma_location); + zfmt->chroma_location = pl_to_z_chroma(fmt.chroma_location); if (ctx && ctx->opts.fast) { // mpv's default for RGB output slows down zimg significantly. @@ -548,7 +551,7 @@ static bool mp_zimg_state_init(struct mp_zimg_context *ctx, params.allow_approximate_gamma = 1; // leave at default for SDR, which means 100 cd/m^2 for zimg - if (ctx->dst.color.hdr.max_luma > 0 && mp_trc_is_hdr(ctx->dst.color.gamma)) + if (ctx->dst.color.hdr.max_luma > 0 && pl_color_space_is_hdr(&ctx->dst.color)) params.nominal_peak_luminance = ctx->dst.color.hdr.max_luma; st->graph = zimg_filter_graph_build(&src_fmt, &dst_fmt, ¶ms); |