Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d893bebdc | ||
|
|
011b9dc46a | ||
|
|
83299b8382 | ||
|
|
899ce94693 |
No files matched your search
@@ -0,0 +1,25 @@
|
||||
### mpv version and platform
|
||||
|
||||
If you're not using git master or the latest release, update.
|
||||
Releases are listed here: https://github.com/mpv-player/mpv/releases
|
||||
|
||||
### Reproduction steps
|
||||
|
||||
### Expected behavior
|
||||
|
||||
### Actual behavior
|
||||
|
||||
### Log file
|
||||
|
||||
Make a log file made with -v or --log-file=output.txt, paste it to
|
||||
http://sprunge.us or a similar site, and replace this text with a link to it.
|
||||
|
||||
Providing a log file is strongly encouraged. It is very helpful for reproducing
|
||||
bugs or getting important technical context. Without a log it might not be
|
||||
possible to analyze and fix certain bugs, and your bug report will be closed
|
||||
or ignored.
|
||||
|
||||
### 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.)
|
||||
@@ -1,16 +0,0 @@
|
||||
---
|
||||
name: 'README: DO NOT IGNORE OR DELETE THE ISSUE TEMPLATE'
|
||||
about: 'Chose and fill out one of the following templates!'
|
||||
title: ''
|
||||
labels: priority:ignored-issue-template
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
We ask you to not ignore the issue template. Fill it out as good and correct as
|
||||
possible. Issues that don't adhere to our request will be closed for ignoring
|
||||
the issue template. This is because analyzing a bug without a log file is harder
|
||||
than necessary. Low quality bug reports are noise.
|
||||
|
||||
Please go back and chose the proper issue template. Opening issues with this
|
||||
template will be closed immediately.
|
||||
@@ -1,49 +0,0 @@
|
||||
---
|
||||
name: 'Report a Linux Issue'
|
||||
about: 'Create a report for a runtime related Linux Issue'
|
||||
title: ''
|
||||
labels: 'os:linux'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### Important Information
|
||||
|
||||
Provide following Information:
|
||||
- mpv version
|
||||
- Linux Distribution and Version
|
||||
- Source of the mpv binary
|
||||
- If known which version of mpv introduced the problem
|
||||
- Window Manager and version
|
||||
- GPU 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
|
||||
|
||||
### Reproduction steps
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
differes from your own.
|
||||
|
||||
### Expected behavior
|
||||
|
||||
### Actual behavior
|
||||
|
||||
### 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.
|
||||
|
||||
The issue will be closed for ignoring the issue template.
|
||||
|
||||
### 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.)
|
||||
Do not use garbage like "cloud storage", especially not Google Drive.
|
||||
@@ -1,51 +0,0 @@
|
||||
---
|
||||
name: 'Report a macOS Issue'
|
||||
about: 'Create a report for a runtime related macOS Issue'
|
||||
title: ''
|
||||
labels: 'os:mac'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### Important Information
|
||||
|
||||
Provide following Information:
|
||||
- mpv version
|
||||
- macOS Version
|
||||
- Source of the mpv binary or bundle
|
||||
- If known which version of mpv introduced the problem
|
||||
- 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
|
||||
|
||||
### Reproduction steps
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
differes from your own.
|
||||
|
||||
### Expected behavior
|
||||
|
||||
### Actual behavior
|
||||
|
||||
### 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.
|
||||
|
||||
In the case of a crash please provide the macOS Crash Report (Backtrace).
|
||||
|
||||
The issue will be closed for ignoring the issue template.
|
||||
|
||||
### 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.)
|
||||
Do not use garbage like "cloud storage", especially not Google Drive.
|
||||
@@ -1,47 +0,0 @@
|
||||
---
|
||||
name: 'Report a Windows Issue'
|
||||
about: 'Create a report for a runtime related Windows Issue'
|
||||
title: ''
|
||||
labels: 'os:win'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### Important Information
|
||||
|
||||
Provide following Information:
|
||||
- mpv version
|
||||
- Windows Version
|
||||
- Source of the mpv binary
|
||||
- If known which version of mpv introduced the problem
|
||||
- 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
|
||||
|
||||
### Reproduction steps
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
differes from your own.
|
||||
|
||||
### Expected behavior
|
||||
|
||||
### Actual behavior
|
||||
|
||||
### 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.
|
||||
|
||||
The issue will be closed for ignoring the issue template.
|
||||
|
||||
### 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.)
|
||||
Do not use garbage like "cloud storage", especially not Google Drive.
|
||||
@@ -1,43 +0,0 @@
|
||||
---
|
||||
name: 'Report a different Issue'
|
||||
about: 'Create a report for a runtime related Issue'
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### Important Information
|
||||
|
||||
Provide following Information:
|
||||
- mpv version
|
||||
- Platform and Version
|
||||
- Source of the mpv binary
|
||||
|
||||
If you're not using git master or the latest release, update.
|
||||
Releases are listed here: https://github.com/mpv-player/mpv/releases
|
||||
|
||||
### Reproduction steps
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
differes from your own.
|
||||
|
||||
### Expected behavior
|
||||
|
||||
### Actual behavior
|
||||
|
||||
### 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.
|
||||
|
||||
### 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.)
|
||||
Do not use garbage like "cloud storage", especially not Google Drive.
|
||||
@@ -1,30 +0,0 @@
|
||||
---
|
||||
name: 'Report a build Problem'
|
||||
about: 'Create a report for a build related Issue'
|
||||
title: ''
|
||||
labels: 'core:waf'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### mpv version and platform versions
|
||||
|
||||
If you're not using git master or the latest release, update.
|
||||
Releases are listed here: https://github.com/mpv-player/mpv/releases
|
||||
|
||||
### Reproduction steps
|
||||
|
||||
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
|
||||
differes from your own.
|
||||
|
||||
### Expected behavior
|
||||
|
||||
### Actual behavior
|
||||
|
||||
### Log file
|
||||
|
||||
Upload build/config.log or ./waf configure output to https://0x0.st/ or attach
|
||||
it to the github issue, and replace this text with a link to it.
|
||||
|
||||
The issue will be closed for ignoring the issue template.
|
||||
@@ -1,22 +0,0 @@
|
||||
---
|
||||
name: 'Request a new Feature'
|
||||
about: 'Create a request for a new feature'
|
||||
title: ''
|
||||
labels: 'meta:feature-request'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Before requesting a new feature make sure it hasn't been requested yet.
|
||||
https://github.com/mpv-player/mpv/labels/meta%3Afeature-request
|
||||
|
||||
### Expected behavior of the wanted feature
|
||||
|
||||
### Alternative behavior of the wanted feature
|
||||
|
||||
### Log file
|
||||
|
||||
Even if you think it's not necessary at first, it might help us later to find
|
||||
possible issues. Make a log file made with -v -v or --log-file=output.txt, paste
|
||||
it to https://0x0.st/ or attach it to the github issue, and replace this text
|
||||
with a link to it.
|
||||
@@ -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.
|
||||
@@ -1,9 +0,0 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: mpv irc channel
|
||||
url: https://mpv.io/community/
|
||||
about: Feel free to ask questions here irc://irc.freenode.net/mpv
|
||||
- name: mpv irc developer channel
|
||||
url: https://mpv.io/community/
|
||||
about: Ask questions related to the development of mpv here
|
||||
irc://irc.freenode.net/mpv-devel
|
||||
@@ -1,5 +1,7 @@
|
||||
Read this before you submit this pull request:
|
||||
https://github.com/mpv-player/mpv/blob/master/DOCS/contribute.md
|
||||
Ideally, you shouldn't need enter any text here, and your commit messages should
|
||||
explain your changes sufficiently (especially why they are needed). Read
|
||||
https://github.com/mpv-player/mpv/blob/master/DOCS/contribute.md for coding
|
||||
style and development conventions. Remove this text block, but if you haven't
|
||||
agreed to it before, leave the following sentence in place:
|
||||
|
||||
Reading this link and following the rules will get your pull request reviewed
|
||||
and merged faster. Nobody wants lazy pull requests.
|
||||
I agree that my changes can be relicensed to LGPL 2.1 or later.
|
||||
+25
-139
@@ -1,166 +1,52 @@
|
||||
language: c
|
||||
|
||||
_macbase:
|
||||
- &mac
|
||||
os: osx
|
||||
compiler: clang
|
||||
env:
|
||||
- HOMEBREW_NO_AUTO_UPDATE=1
|
||||
- HOMEBREW_NO_INSTALL_CLEANUP=1
|
||||
before_cache:
|
||||
- brew cleanup -s
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/Library/Caches/Homebrew
|
||||
- /usr/local/Homebrew
|
||||
|
||||
_mingwbase:
|
||||
- &mingw
|
||||
os: linux
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- 'autoconf'
|
||||
- 'automake'
|
||||
- 'pkg-config'
|
||||
- 'gcc-mingw-w64'
|
||||
- 'gcc-multilib'
|
||||
- 'nasm'
|
||||
- 'yasm'
|
||||
cache:
|
||||
directories:
|
||||
- mingw_prefix/
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- <<: *mac
|
||||
osx_image: xcode11.6
|
||||
- <<: *mac
|
||||
osx_image: xcode10.1
|
||||
- <<: *mac
|
||||
osx_image: xcode9.2
|
||||
env:
|
||||
- HOMEBREW_NO_AUTO_UPDATE=1
|
||||
- HOMEBREW_NO_INSTALL_CLEANUP=1
|
||||
- CI_SWIFT_FLAGS="\-target x86_64-apple-macosx10.12"
|
||||
- os: freebsd
|
||||
compiler: clang
|
||||
- os: linux
|
||||
compiler: gcc
|
||||
env: CONTAINER=registry.cirno.systems/kiwi/containers/mpv-ci:stable-deps CI_SCRIPT=ci/build-tumbleweed.sh
|
||||
- os: linux
|
||||
compiler: clang
|
||||
env: CONTAINER=registry.cirno.systems/kiwi/containers/mpv-ci:stable-deps CI_SCRIPT=ci/build-tumbleweed.sh
|
||||
- <<: *mingw
|
||||
env: CI_SCRIPT=ci/build-mingw64.sh TARGET=i686-w64-mingw32
|
||||
- <<: *mingw
|
||||
env: CI_SCRIPT=ci/build-mingw64.sh TARGET=x86_64-w64-mingw32
|
||||
allow_failures:
|
||||
- os: osx
|
||||
osx_image: xcode9.2
|
||||
fast_finish: true
|
||||
|
||||
dist: focal
|
||||
services:
|
||||
- docker
|
||||
|
||||
os:
|
||||
- linux
|
||||
#- osx
|
||||
env:
|
||||
matrix:
|
||||
- LIBAV=libav-git
|
||||
- LIBAV=ffmpeg-stable
|
||||
- LIBAV=ffmpeg-git
|
||||
global:
|
||||
# Coverity token
|
||||
- secure: "H21mSRlMhk4BKS0xHZvCFGJxteCP0hRVUxTuNfM2Z9HBsyutuLEYMtViLO86VtM+Tqla3xXPzUdS4ozLwI72Ax/5ZUDXACROj73yW6QhFB5D6rLut12+FjqC7M33Qv2hl0xwgNBmR5dsm1ToP37+Wn+ecJQNvN8fkTXF+HVzOEw="
|
||||
# Travis token for mpv.io
|
||||
- secure: "nlTVLJK6kRhtXvhKCoJ3YdFGHuKaq/eHowfPw25hqRWuBOZd+HjHY5KIYjV7SxuKFDpJE4GpNcvA3Q31nsqomxpkLYgrwjg6TSazN7ZP+x85ZgV1QGFebrPfGm2n5UR5CAPAwFoeF3pZheLi4bajVzwq1fWW+x3grS188P9OZso="
|
||||
compiler:
|
||||
- clang
|
||||
- gcc
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- ci
|
||||
- coverity_scan
|
||||
- /release\/.*$/
|
||||
|
||||
before_install:
|
||||
- if [ "$TRAVIS_COMPILER" = "clang" ]; then export CXX="clang++"; fi
|
||||
- if [ "$TRAVIS_COMPILER" = "gcc" ]; then export CXX="g++"; fi
|
||||
- if [ -n "$CONTAINER" ]; then docker pull $CONTAINER; fi
|
||||
- |
|
||||
if [ "$TRAVIS_OS_NAME" = "freebsd" ]; then
|
||||
# Requested in ci/build-freebsd.sh
|
||||
sudo pkg install -y \
|
||||
evdev-proto \
|
||||
ffmpeg \
|
||||
libplacebo \
|
||||
libxkbcommon \
|
||||
luajit \
|
||||
openal-soft \
|
||||
pkgconf \
|
||||
python3 \
|
||||
sdl2 \
|
||||
vulkan-headers \
|
||||
wayland-protocols \
|
||||
$NULL
|
||||
# Optionally auto-enabled
|
||||
sudo pkg install -y \
|
||||
alsa-lib \
|
||||
jackit \
|
||||
libXv \
|
||||
libarchive \
|
||||
libbluray \
|
||||
libcaca \
|
||||
libcdio-paranoia \
|
||||
libdvdnav \
|
||||
mujs \
|
||||
pulseaudio \
|
||||
rubberband \
|
||||
sekrit-twc-zimg \
|
||||
uchardet \
|
||||
v4l_compat \
|
||||
$NULL
|
||||
fi
|
||||
- |
|
||||
if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
|
||||
remove=$(brew list)
|
||||
keep="gettext pcre2 git"
|
||||
install="autoconf automake pkg-config libtool python freetype fribidi little-cms2 luajit libass ffmpeg"
|
||||
for formula in ${keep[@]}; do remove=("${remove[@]/$formula}"); done
|
||||
for formula in ${install[@]}; do remove=("${remove[@]/$formula}"); done
|
||||
brew remove --force $remove --ignore-dependencies
|
||||
if [[ "$TRAVIS_OSX_IMAGE" == "xcode9.2" ]]; then
|
||||
brew untap caskroom/cask
|
||||
fi
|
||||
brew update
|
||||
if [[ "$TRAVIS_OSX_IMAGE" == "xcode9.2" ]]; then
|
||||
pushd "/usr/local/Homebrew/Library/Taps/homebrew/homebrew-core"
|
||||
git checkout --force 55e02323812604add9a69bab8730319b9255a697
|
||||
fi
|
||||
brew install $install
|
||||
brew link --overwrite python
|
||||
if [[ "$TRAVIS_OSX_IMAGE" == "xcode9.2" ]]; then
|
||||
git checkout master
|
||||
popd
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
matrix:
|
||||
exclude:
|
||||
- os: osx
|
||||
env: LIBAV=libav-git
|
||||
- os: osx
|
||||
env: LIBAV=ffmpeg-git
|
||||
- os: osx
|
||||
compiler: gcc
|
||||
- os: linux
|
||||
env: LIBAV=ffmpeg-stable
|
||||
- os: linux
|
||||
compiler: clang
|
||||
|
||||
before_install: TOOLS/travis-deps libass-stable $LIBAV
|
||||
script:
|
||||
- ./bootstrap.py
|
||||
- |
|
||||
if [ "$TRAVIS_OS_NAME" = "linux" ]; then
|
||||
if [ -n "$CONTAINER" ]; then
|
||||
docker run --env CC --env TARGET -v $TRAVIS_BUILD_DIR:/build $CONTAINER /bin/sh -c "cd /build && $CI_SCRIPT"
|
||||
else
|
||||
$CI_SCRIPT
|
||||
fi
|
||||
fi
|
||||
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then ./ci/build-macos.sh; fi
|
||||
- if [ "$TRAVIS_OS_NAME" = "freebsd" ]; then ./ci/build-freebsd.sh; fi
|
||||
- ./waf configure
|
||||
- ./waf build
|
||||
after_failure: cat ./build/config.log
|
||||
after_script: TOOLS/travis-rebuild-website
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
irc:
|
||||
if: fork = false
|
||||
channels:
|
||||
- "irc.freenode.org#mpv-devel"
|
||||
on_success: change
|
||||
|
||||
@@ -1,74 +1,351 @@
|
||||
mpv is a fork of mplayer2, which is a fork of MPlayer.
|
||||
|
||||
mpv as a whole is licensed under the GNU General Public License GPL version 2
|
||||
or later (called GPLv2+ in this document, see LICENSE.GPL for full license
|
||||
text) by default, or the GNU Lesser General Public License LGPL version 2 or
|
||||
later (LGPLv2.1+ in this document, see LICENSE.LGPL for full license text) if
|
||||
built with the --enable-lgpl configure switch.
|
||||
mpv as a whole is licensed as GPL version 2 or later (see LICENSE). Most source
|
||||
files are GPLv2+, but some files are available under a more liberal license,
|
||||
such as LGPLv2.1+, BSD, MIT, ISC, and possibly others. Look at the copyright
|
||||
header of each source file, and grep the sources for "Copyright" if you need
|
||||
to know details. C source files without Copyright notice are licensed as
|
||||
LGPLv2.1+. Also see the list of files with specific licenses below (not all
|
||||
files can have a standard license header).
|
||||
|
||||
Most source files are LGPLv2.1+ or GPLv2+, but some files are available under
|
||||
more liberal licenses, such as BSD, MIT, ISC, and possibly others. Look at the
|
||||
copyright header of each source file, and grep the sources for "Copyright" if
|
||||
you need to know details. C source files without Copyright notice are usually
|
||||
licensed as LGPLv2.1+. Also see the list of files with specific licenses below
|
||||
(not all files can have a standard license header).
|
||||
|
||||
All new contributions must be LGPLv2.1+ licensed. Using a more liberal license
|
||||
compatible to LGPLv2.1+ is also ok.
|
||||
|
||||
Changes done to GPL code must come with the implicit/explicit agreement that the
|
||||
project can relicense the changes to LGPLv2.1+ at a later point without asking
|
||||
the contributor. This is a safeguard for making potential relicensing of
|
||||
remaining GPL code to LGPLv2.1+ easier.
|
||||
|
||||
See DOCS/contribute.md for binding rules wrt. licensing for contributions.
|
||||
All new contributions must be LGPLv2.1+ licensed, or if the changes are done on
|
||||
GPL code, must come with the implicit agreement that the project can relicense
|
||||
the code to LGPLv2.1+ at a later point without asking the contributor. (This
|
||||
is a safeguard for making potential relicensing of the project to LGPLv2.1+
|
||||
easier.) Using a more liberal license compatible to LGPLv2.1+ is also ok.
|
||||
|
||||
For information about authors and contributors, consult the git log, which
|
||||
contains the complete SVN and CVS history as well.
|
||||
|
||||
Note that mplayer2 as a whole is licensed under GPLv3+. This is because it uses
|
||||
a copy of talloc (part of Samba), which is LGPLv3+, and the next compatible
|
||||
license for this mix is GPLv3+.
|
||||
|
||||
MPlayer as a whole is licensed under GPLv2 (incompatible to GPLv3!), because
|
||||
some files are licensed to GPLv2 (and _not_ any later version of the license).
|
||||
In particular, this affects the file libmpdemux/demux_ty_osd.c. It is disabled
|
||||
under mplayer2, and has been removed from mpv.
|
||||
|
||||
"v2.1+" in this context means "version 2.1 or later".
|
||||
|
||||
Some libraries are GPLv2+ or GPLv3+ only. Building mpv with Samba support makes
|
||||
it GPLv3+.
|
||||
|
||||
mpv can be built as LGPLv2.1+ with the --enable-lgpl configure option. To add
|
||||
a LGPL mode to mpv, MPlayer code had to be relicensed from GPLv2+ to LGPLv2.1+
|
||||
by asking the MPlayer authors for permission. Since permission could not be
|
||||
obtained from everyone, LGPL mode disables the following features, some of
|
||||
them quite central:
|
||||
- Linux X11 video output
|
||||
- BSD audio output via OSS
|
||||
- NVIDIA/Linux hardware decoding (vdpau, although nvdec usually works)
|
||||
- Linux TV input
|
||||
- minor features: jack, DVD, CDDA, SMB, CACA, legacy direct3d VO
|
||||
Some of these will be fixed in the future. The intended use for LGPL mode is
|
||||
with libmpv, and currently it's not recommended to build mpv CLI in LGPL mode
|
||||
at all.
|
||||
Source files with specific licenses:
|
||||
- etc/input.conf is LGPLv2.1+
|
||||
- etc/builtin.conf is LGPLv2.1+
|
||||
- etc/encoding-profiles.conf is LGPLv2.1+
|
||||
- all mpv icons and derived files are LGPLv2.1+ (mpv-icon-8bit-16x16.png etc.)
|
||||
- everything else under etc/ is unknown
|
||||
- everything under DOCS/man/ is GPLv2+
|
||||
- sub/osd_font.otf is LGPLv2.1+
|
||||
- version.sh is LGPLv2+
|
||||
- bootstrap.py is unknown (probably GPLv2+ or LGPLv2+)
|
||||
- the build system (wscript, waftools/*) is LGPLv2+, some parts BSD
|
||||
|
||||
The following files are still GPL only (--enable-lgpl disables them):
|
||||
Some files are marked with "Almost LGPL." These files are GPL, but all authors
|
||||
have agreed to relicense them to LGPL. the problem is that one of the authors is
|
||||
Michael Niedermayer, who cited the following conditions for relicensing:
|
||||
|
||||
http://lists.mplayerhq.hu/pipermail/mplayer-dev-eng/2016-September/073535.html
|
||||
|
||||
We assume the first 3 conditions are fulfilled. The last condition can be
|
||||
interpreted as that his code can be changed to LGPL only as soon as the "core"
|
||||
of mpv changes to LGPL. We interpret "core" as something minimal, that can
|
||||
actually be built and run, with all GPL code disabled.
|
||||
|
||||
Some files are LGPLv3+. This is due to the contributions of a single developer
|
||||
going by the SVN username "iive". The chosen license of this project is
|
||||
LGPLv2.1+. The affected files will be changed to LGPLv2.1+ at the earliest
|
||||
opportunity, for example if his contributions disappear by being replaced
|
||||
or removed. All new contributions to these files are implied to be LGPLv2.1+.
|
||||
|
||||
LGPL relicensing status. Files which require some sort of action to get a basic
|
||||
LGPL player core (relicensing, replacement, or partial/full rewrites) are marked
|
||||
with an "x".
|
||||
|
||||
audio/decode/ad.h LGPL
|
||||
audio/decode/ad_lavc.c LGPL
|
||||
audio/decode/ad_spdif.c LGPL
|
||||
audio/decode/dec_audio.* LGPL
|
||||
x audio/filter/af.* must be killed (main author disagreed)
|
||||
(required for LGPL core: a new filter chain must be written)
|
||||
audio/filter/af_channel.c must be killed (main author disagreed)
|
||||
audio/filter/af_equalizer.c must be killed (main author disagreed)
|
||||
audio/filter/af_pan.c must be killed (main author disagreed)
|
||||
audio/filter/af_volume.c must be killed (main author disagreed)
|
||||
audio/filter/equalizer.h must be killed (main author disagreed)
|
||||
audio/filter/tools.c must be killed (main author disagreed)
|
||||
audio/filter/af_format.c LGPL
|
||||
audio/filter/af_lavc3enc.c LGPL
|
||||
audio/filter/af_lavfi.c LGPL
|
||||
audio/filter/af_scaletempo.c LGPL
|
||||
audio/filter/af_rubberband.c LGPL
|
||||
audio/out/ao.c LGPL
|
||||
audio/out/ao.h LGPL
|
||||
audio/out/ao_alsa.c extremely hard (original author did not decide)
|
||||
audio/out/ao_audiounit.m LGPL
|
||||
audio/out/ao_coreaudio.c LGPL
|
||||
audio/out/ao_coreaudio_chmap.c LGPL
|
||||
audio/out/ao_coreaudio_chmap.h LGPL
|
||||
audio/out/ao_coreaudio_exclusiv LGPL
|
||||
audio/out/ao_coreaudio_properti LGPL
|
||||
audio/out/ao_coreaudio_properti LGPL
|
||||
audio/out/ao_coreaudio_utils.c LGPL
|
||||
audio/out/ao_coreaudio_utils.h LGPL
|
||||
audio/out/ao_jack.c will stay GPL
|
||||
audio/out/ao_lavc.c LGPL
|
||||
audio/out/ao_null.c LGPL
|
||||
audio/out/ao_openal.c LGPL
|
||||
audio/out/ao_opensles.c LGPL
|
||||
audio/out/ao_oss.c will stay GPL
|
||||
audio/out/ao_pcm.c LGPL
|
||||
audio/out/ao_pulse.c LGPL
|
||||
audio/out/ao_rsound.c LGPL
|
||||
audio/out/ao_sdl.c LGPL
|
||||
audio/out/ao_sndio.c LGPL (BSD)
|
||||
audio/out/ao_wasapi.c LGPL
|
||||
audio/out/ao_wasapi_changenotif LGPL
|
||||
audio/out/ao_wasapi.h LGPL
|
||||
audio/out/ao_wasapi_utils.c LGPL
|
||||
audio/out/internal.h LGPL
|
||||
audio/out/pull.c LGPL
|
||||
audio/out/push.c LGPL
|
||||
x audio/audio.* very hard (mp_audio based of anders' af_audio)
|
||||
(required for LGPL core: determine if the struct can be used anyway, or
|
||||
devise a cheap AVFrame wrapper)
|
||||
audio/audio_buffer.* LGPL
|
||||
audio/chmap.* LGPL
|
||||
audio/chmap_sel.* LGPL
|
||||
audio/fmt-conversion.* LGPL
|
||||
audio/format.* LGPL
|
||||
common/av_common.* LGPL
|
||||
common/av_log.c almost LGPL
|
||||
common/av_log.h LGPL
|
||||
common/codecs.* LGPL
|
||||
common/common.* LGPL
|
||||
common/encode.h LGPL
|
||||
common/encode_lavc.* LGPL
|
||||
common/global.h LGPL
|
||||
common/msg.c almost LGPL
|
||||
common/msg_control.h LGPL
|
||||
common/msg.h LGPL
|
||||
common/playlist.* LGPL
|
||||
common/recorder.* LGPL
|
||||
common/tags.* LGPL
|
||||
common/version.c LGPL
|
||||
demux/codec_tags.* LGPL
|
||||
demux/cue.* LGPL
|
||||
demux/demux.* LGPL
|
||||
demux/demux_cue.c LGPL
|
||||
demux/demux_disc.c LGPL
|
||||
demux/demux_edl.c LGPL
|
||||
demux/demux_lavf.c almost LGPL
|
||||
demux/demux_libarchive.c LGPL
|
||||
demux/demux_mf.c LGPLv3+
|
||||
demux/demux_mkv.c LGPL (mostly)
|
||||
demux/demux_mkv_timeline.c LGPL
|
||||
demux/demux_null.c LGPL
|
||||
demux/demux_playlist.c LGPL
|
||||
demux/demux_rar.c LGPL
|
||||
demux/demux_raw.c almost LGPL
|
||||
demux/demux_timeline.c LGPL
|
||||
demux/demux_tv.c will stay GPL
|
||||
demux/ebml.* LGPL
|
||||
demux/matroska.h LGPL
|
||||
demux/packet.* LGPL
|
||||
demux/stheader.h LGPL
|
||||
demux/timeline.* LGPL
|
||||
input/cmd_* LGPL
|
||||
input/event.* LGPL
|
||||
input/input.* LGPL
|
||||
input/ipc.c LGPL
|
||||
input/ipc-unix.c LGPL
|
||||
input/ipc-win.c LGPL
|
||||
input/keycodes.* LGPL
|
||||
input/pipe-win32.c LGPL
|
||||
libmpv/*.* LGPL
|
||||
misc/*.* LGPL
|
||||
options/m_config.* LGPL
|
||||
options/m_option.c almost LGPL
|
||||
options/m_option.h LGPL
|
||||
options/m_property.* LGPL
|
||||
options/options.* LGPL
|
||||
options/parse_commandline.* LGPL
|
||||
options/parse_configfile.* LGPL
|
||||
options/path.* LGPL
|
||||
osdep/android/* LGPL (BSD)
|
||||
osdep/ar/* LGPL (BSD)
|
||||
osdep/atomic.h LGPL
|
||||
osdep/compiler.h LGPL
|
||||
osdep/endian.h LGPL
|
||||
osdep/glob-win.c LGPL
|
||||
osdep/io.* LGPL
|
||||
osdep/macosx_application.h LGPL
|
||||
osdep/macosx_application.m LGPL
|
||||
osdep/macosx_application_objc.h LGPL
|
||||
osdep/macosx_compat.h LGPL
|
||||
osdep/macosx_events.* LGPL
|
||||
osdep/macosx_events_objc.h LGPL
|
||||
osdep/macosx_touchbar.* LGPL
|
||||
osdep/macosx_versions.h LGPL
|
||||
osdep/main-fn-cocoa.c LGPL
|
||||
osdep/main-fn.h LGPL
|
||||
osdep/main-fn-unix.c LGPL
|
||||
osdep/main-fn-win.c LGPL
|
||||
osdep/mpv.exe.manifest LGPL
|
||||
osdep/mpv.rc LGPL
|
||||
osdep/path.h LGPL
|
||||
osdep/path-macosx.m LGPL
|
||||
osdep/path-unix.c LGPL
|
||||
osdep/path-win.c LGPL
|
||||
osdep/semaphore.h LGPL
|
||||
osdep/semaphore_osx.c LGPL
|
||||
osdep/strnlen.h LGPL
|
||||
osdep/subprocess.* LGPL
|
||||
osdep/subprocess-posix.c LGPL
|
||||
osdep/subprocess-win.c LGPL
|
||||
osdep/terminal.h LGPL
|
||||
osdep/terminal-unix.c LGPL
|
||||
osdep/terminal-win.c LGPL
|
||||
osdep/threads.* LGPL
|
||||
osdep/timer.c LGPL
|
||||
osdep/timer.h LGPL
|
||||
osdep/timer-darwin.c LGPL (MIT)
|
||||
osdep/timer-linux.c LGPL
|
||||
osdep/timer-win2.c LGPL
|
||||
osdep/w32_keyboard.c LGPL
|
||||
osdep/w32_keyboard.h LGPL
|
||||
osdep/win32-console-wrapper.c LGPL (BSD)
|
||||
osdep/win32/* LGPL (ISC)
|
||||
osdep/windows_utils.* LGPL
|
||||
x player/audio.c LGPL (dysfunctional due to libaf)
|
||||
player/client.* LGPL (ISC)
|
||||
player/command.c LGPL
|
||||
player/command.h LGPL
|
||||
player/configfiles.c LGPL
|
||||
player/core.h LGPL
|
||||
player/external_files.* LGPL
|
||||
player/lavfi.* LGPL
|
||||
player/loadfile.c LGPL
|
||||
player/lua/*.* LGPL
|
||||
player/lua.c LGPL
|
||||
player/misc.c LGPL
|
||||
player/osd.c LGPL
|
||||
player/playloop.c LGPL
|
||||
player/screenshot.* LGPL
|
||||
player/scripting.* LGPL
|
||||
player/sub.c LGPL
|
||||
player/video.c LGPL
|
||||
stream/ai_* will stay GPL (TV code)
|
||||
stream/audio_in.* will stay GPL (TV code)
|
||||
stream/cache.c LGPLv3+
|
||||
stream/cache_file.c LGPL
|
||||
stream/cookies.* LGPL
|
||||
stream/dvb* must stay GPL
|
||||
stream/frequencies.* must stay GPL
|
||||
stream/rar.* LGPL
|
||||
stream/stream_avdevice.c LGPL
|
||||
stream/stream_bluray.c LGPL
|
||||
stream/stream.* LGPL
|
||||
stream/stream_cb.c LGPL
|
||||
stream/stream_cdda.c unknown
|
||||
stream/stream_dvb.* must stay GPL
|
||||
stream/stream_dvd.c unknown
|
||||
stream/stream_dvd_common.* unknown
|
||||
stream/stream_dvdnav.c unknown
|
||||
stream/stream_edl.c LGPL
|
||||
stream/stream_file.c LGPL
|
||||
stream/stream_lavf.c LGPL
|
||||
stream/stream_libarchive.* LGPL
|
||||
stream/stream_memory.c LGPL
|
||||
stream/stream_mf.c LGPL
|
||||
stream/stream_null.c LGPL
|
||||
stream/stream_rar.c LGPL
|
||||
stream/stream_smb.c will stay GPLv3
|
||||
stream/stream_tv.c will stay GPL
|
||||
stream/tv* will stay GPL
|
||||
sub/* LGPL
|
||||
ta/* LGPL (ISC)
|
||||
video/decode/d3d.* LGPL
|
||||
video/decode/dec_video.* almost LGPL
|
||||
video/decode/hw_cuda.c LGPL
|
||||
video/decode/hw_d3d11va.c LGPL
|
||||
video/decode/hw_dxva2.c LGPL
|
||||
video/decode/hw_videotoolbox.c LGPL
|
||||
video/decode/lavc.h almost LGPLv3+
|
||||
video/decode/vd.h LGPL
|
||||
video/decode/vd_lavc.c almost LGPLv3+
|
||||
video/filter/refqueue.* LGPL
|
||||
video/filter/vf.c LGPL
|
||||
video/filter/vf.h LGPL (mostly)
|
||||
video/filter/vf_buffer.c LGPL
|
||||
video/filter/vf_crop.c will be deleted
|
||||
video/filter/vf_d3d11vpp.c LGPL
|
||||
video/filter/vf_dsize.c will be deleted
|
||||
video/filter/vf_expand.c will be deleted
|
||||
video/filter/vf_flip.c will be deleted
|
||||
video/filter/vf_format.c will be deleted
|
||||
video/filter/vf_gradfun.c will be deleted
|
||||
video/filter/vf_lavfi.* LGPL
|
||||
video/filter/vf_mirror.c will be deleted
|
||||
video/filter/vf_noformat.c will be deleted
|
||||
video/filter/vf_pullup.c will be deleted
|
||||
video/filter/vf_rotate.c will be deleted
|
||||
video/filter/vf_scale.c will be deleted
|
||||
video/filter/vf_stereo3d.c will be deleted
|
||||
video/filter/vf_sub.c will be deleted
|
||||
video/filter/vf_vapoursynth.c LGPL
|
||||
video/filter/vf_vavpp.c LGPL
|
||||
video/filter/vf_vdpaupp.c LGPL
|
||||
video/filter/vf_yadif.c will be deleted
|
||||
video/csputils.* LGPL
|
||||
video/fmt-conversion.* LGPL
|
||||
video/gpu_memcpy.* will be deleted
|
||||
video/hwdec.* LGPL
|
||||
video/image_writer.* LGPL
|
||||
video/img_format.* LGPL
|
||||
video/mp_image.* almost LGPL
|
||||
video/mp_image_pool.* LGPL
|
||||
video/out/aspect.* LGPL
|
||||
video/out/bitmap_packer.* LGPL
|
||||
video/out/cocoa* LGPL
|
||||
video/out/d3d_shader_420p.h LGPL
|
||||
video/out/d3d_shader_nv12.h LGPL
|
||||
video/out/d3d_shader_yuv.hlsl LGPL
|
||||
video/out/dither.* LGPL
|
||||
video/out/drm_common.* LGPL
|
||||
video/out/filter_kernels.* LGPL (BSD)
|
||||
video/out/opengl/hwdec_vaglx.c GPL
|
||||
video/out/opengl/* LGPL
|
||||
video/out/vo.c LGPL
|
||||
video/out/vo.h LGPL
|
||||
video/out/vo_caca.c unknown
|
||||
video/out/vo_direct3d.c unknown
|
||||
video/out/vo_drm.c LGPL
|
||||
video/out/vo_image.c LGPL
|
||||
video/out/vo_lavc.c LGPL
|
||||
video/out/vo_null.c LGPL
|
||||
video/out/vo_opengl.c LGPL
|
||||
video/out/vo_opengl_cb.c LGPL
|
||||
video/out/vo_rpi.c LGPL
|
||||
video/out/vo_sdl.c LGPL
|
||||
video/out/vo_tct.c LGPL
|
||||
video/out/vo_vaapi.c probably impossible (some company's code)
|
||||
video/out/vo_vdpau.c probably impossible (nVidia's code)
|
||||
video/out/vo_wayland.c LGPL
|
||||
video/out/vo_x11.c probably impossible
|
||||
video/out/vo_xv.c probably impossible
|
||||
video/out/w32_common.* LGPL
|
||||
video/out/wayland* LGPL
|
||||
video/out/win32/* LGPL
|
||||
video/out/win_state.* LGPL
|
||||
video/out/x11_common.* probably impossible
|
||||
video/out/x11_icon.bin LGPL
|
||||
video/sws_utils.* almost LGPL
|
||||
video/vaapi.* hard (GPL-only parts must be ifdefed)
|
||||
video/vdpau.c hard (GPL-only parts must be ifdefed)
|
||||
video/vdpau_functions.inc LGPL
|
||||
video/vdpau.h unknown
|
||||
video/vdpau_mixer.* actual code must be rewritten
|
||||
DOCS/man/ GPLv2+
|
||||
bootstrap.py unknown license, probably GPLv2+ or LGPLv2+
|
||||
etc/mplayer-input.conf unknown license, probably GPLv2+
|
||||
mpv.desktop unknown license, probably GPLv2+
|
||||
etc/restore-old-bindings.conf unknown license, probably GPLv2+
|
||||
|
||||
None of the cases listed above affect the final binary if it's built as
|
||||
LGPL. Linked libraries still can affect the final license (for example if
|
||||
FFmpeg was built as GPL).
|
||||
video/vt.* LGPL
|
||||
+1
-103
@@ -16,7 +16,7 @@ The version number is the same as used for MPV_CLIENT_API_VERSION (see
|
||||
``client.h`` how to convert between major/minor version numbers and the flat
|
||||
32 bit integer).
|
||||
|
||||
Also, read the section ``Compatibility`` in ``client.h``, and compatibility.rst.
|
||||
Also, read the section ``Compatibility`` in ``client.h``.
|
||||
|
||||
Options, commands, properties
|
||||
=============================
|
||||
@@ -32,108 +32,6 @@ API changes
|
||||
|
||||
::
|
||||
|
||||
--- mpv 0.33.0 ---
|
||||
1.109 - add MPV_RENDER_API_TYPE_SW and related (software rendering API)
|
||||
- inactivate the opengl_cb API (always fails to initialize now)
|
||||
The opengl_cb API was deprecated over 2 years ago. Use the render API
|
||||
instead.
|
||||
1.108 - Deprecate MPV_EVENT_IDLE
|
||||
- add mpv_event_start_file
|
||||
- add the following fields to mpv_event_end_file: playlist_entry_id,
|
||||
playlist_insert_id, playlist_insert_num_entries
|
||||
- add mpv_event_to_node()
|
||||
- add mpv_client_id()
|
||||
1.107 - Remove the deprecated qthelper.hpp. This was obviously not part of the
|
||||
libmpv API, only an "additionally" provided helper, thus this is not
|
||||
considered an API change. If you are maintaining a project that relies
|
||||
on this header, you can simply download this file and adjust the
|
||||
include statement to use it instead:
|
||||
|
||||
https://raw.githubusercontent.com/mpv-player/mpv/v0.32.0/libmpv/qthelper.hpp
|
||||
|
||||
It is a good idea to write better wrappers for your use, though.
|
||||
--- mpv 0.31.0 ---
|
||||
1.107 - Deprecate MPV_EVENT_TICK
|
||||
|
||||
--- mpv 0.30.0 ---
|
||||
1.106 - Add cancel_fn to mpv_stream_cb_info
|
||||
1.105 - Fix deadlock problems with MPV_RENDER_PARAM_ADVANCED_CONTROL and if
|
||||
the "vd-lavc-dr" option is enabled (which it is by default).
|
||||
There were no actual API changes.
|
||||
API users on older API versions and mpv releases should set
|
||||
"vd-lavc-dr" to "no" to avoid these issues.
|
||||
API users must still adhere to the tricky rules documented in render.h
|
||||
to avoid other deadlocks.
|
||||
1.104 - Deprecate struct mpv_opengl_drm_params. Replaced by mpv_opengl_drm_params_v2
|
||||
- Deprecate MPV_RENDER_PARAM_DRM_DISPLAY. Replaced by MPV_RENDER_PARAM_DRM_DISPLAY_V2.
|
||||
1.103 - redo handling of async commands
|
||||
- add mpv_event_command and make it possible to return values from
|
||||
commands issued with mpv_command_async() or mpv_command_node_async()
|
||||
- add mpv_abort_async_command()
|
||||
1.102 - rename struct mpv_opengl_drm_osd_size to mpv_opengl_drm_draw_surface_size
|
||||
- rename MPV_RENDER_PARAM_DRM_OSD_SIZE to MPV_RENDER_PARAM_DRM_DRAW_SURFACE_SIZE
|
||||
|
||||
--- mpv 0.29.0 ---
|
||||
1.101 - add MPV_RENDER_PARAM_ADVANCED_CONTROL and related API
|
||||
- add MPV_RENDER_PARAM_NEXT_FRAME_INFO and related symbols
|
||||
- add MPV_RENDER_PARAM_BLOCK_FOR_TARGET_TIME
|
||||
- add MPV_RENDER_PARAM_SKIP_RENDERING
|
||||
- add mpv_render_context_get_info()
|
||||
1.100 - bump API number to avoid confusion with mpv release versions
|
||||
- actually apply the GL_MP_MPGetNativeDisplay change for the new render
|
||||
API. This also means compatibility for anything but x11 and wayland
|
||||
through the old opengl-cb GL_MP_MPGetNativeDisplay method is now
|
||||
unsupported.
|
||||
- deprecate mpv_get_wakeup_pipe(). It's complex, but easy to replace
|
||||
using normal API (just set a wakeup callback to a function which
|
||||
writes to a pipe).
|
||||
- add a 1st class hook API, which replaces the hacky mpv_command()
|
||||
based one. The old API is deprecated and will be removed soon. The
|
||||
old API was never meant to be stable, while the new API is.
|
||||
1.29 - the behavior of mpv_terminate_destroy() and mpv_detach_destroy()
|
||||
changes subtly (see documentation in the header file). In particular,
|
||||
mpv_detach_destroy() will not leave the player running in all
|
||||
situations anymore (it gets closer to refcounting).
|
||||
- rename mpv_detach_destroy() to mpv_destroy() (the old function will
|
||||
remain valid as deprecated alias)
|
||||
- add mpv_create_weak_client(), which makes use of above changes
|
||||
- MPV_EVENT_SHUTDOWN is now returned exactly once if a mpv_handle
|
||||
should terminate, instead of spamming the event queue with this event
|
||||
1.28 - deprecate the render opengl_cb API, and replace it with render.h
|
||||
and render_gl.h. The goal is allowing support for APIs other than
|
||||
OpenGL. The old API is emulated with the new API.
|
||||
Likewise, the "opengl-cb" VO is renamed to "libmpv".
|
||||
mpv_get_sub_api() is deprecated along the opengl_cb API.
|
||||
The new API is relatively similar, but not the same. The rough
|
||||
equivalents are:
|
||||
mpv_opengl_cb_init_gl => mpv_render_context_create
|
||||
mpv_opengl_cb_set_update_callback => mpv_render_context_set_update_callback
|
||||
mpv_opengl_cb_draw => mpv_render_context_render
|
||||
mpv_opengl_cb_report_flip => mpv_render_context_report_swap
|
||||
mpv_opengl_cb_uninit_gl => mpv_render_context_free
|
||||
The VO opengl-cb is also renamed to "libmpv".
|
||||
Also, the GL_MP_MPGetNativeDisplay pseudo extension is not used by the
|
||||
render API anymore, and the old opengl-cb API only handles the "x11"
|
||||
and "wl" names anymore. Support for everything else has been removed.
|
||||
The new render API uses proper API parameters, e.g. for X11 you pass
|
||||
MPV_RENDER_PARAM_X11_DISPLAY directly.
|
||||
- deprecate the qthelper.hpp header file. This provided some C++ helper
|
||||
utility functions for Qt with use of libmpv. There is no reason to
|
||||
keep this in the mpv git repository, nor to make it part of the libmpv
|
||||
API. If you're using this header, you can safely copy it into your
|
||||
project - it uses only libmpv public API. Alternatively, it could be
|
||||
maintained in a separate repository by interested parties.
|
||||
1.27 - make opengl-cb the default VO. This causes a subtle behavior change
|
||||
if the API user called mpv_opengl_cb_init_gl(), but does not set
|
||||
the "vo" option. Before, it would still have used another VO (like
|
||||
on the CLI, e.g. vo=gpu). Now it'll behave as if vo=opengl-cb was
|
||||
used.
|
||||
--- mpv 0.28.0 ---
|
||||
1.26 - remove glMPGetNativeDisplay("drm") support
|
||||
- add mpv_opengl_cb_window_pos and mpv_opengl_cb_drm_params and
|
||||
support via glMPGetNativeDisplay() for using it
|
||||
- make --stop-playback-on-init-failure=no the default in libmpv (just
|
||||
like in mpv CLI)
|
||||
--- mpv 0.27.0 ---
|
||||
1.25 - remove setting "no-" options via mpv_set_option*(). (See corresponding
|
||||
deprecation in 0.23.0.)
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
CLI and API compatibility policy
|
||||
================================
|
||||
|
||||
Human users and API users rely on the mpv/libmpv/scripting/IPC interface not
|
||||
breaking their use case. On the other hand, active development occasionally
|
||||
requires breaking things, such as removing old options or changing options in
|
||||
a way that may break.
|
||||
|
||||
This document lists rules when, what, and how incompatible changes can be made.
|
||||
It's interesting both for mpv developers, who want to change user-visible parts,
|
||||
and mpv users, who want to know what is guaranteed to remain stable.
|
||||
|
||||
Any of the rules below may be overridden by statements in more specific
|
||||
documentation (for example, if the manpage says that a particular option may be
|
||||
removed any time, it means that, and the option probably won't even go through
|
||||
deprecation).
|
||||
|
||||
Additions
|
||||
---------
|
||||
|
||||
Additions are basically always allowed. API users etc. are supposed to deal with
|
||||
the possibility that for example new API functions are added. Some parts of the
|
||||
API may document how they are extended.
|
||||
|
||||
Options, commands, properties, events, hooks (command interface)
|
||||
----------------------------------------------------------------
|
||||
|
||||
All of these are important for interfacing both with end users and API users
|
||||
(which include Lua scripts, libmpv, and the JSON IPC). As such, they constitute
|
||||
a large part of the user interface and APIs.
|
||||
|
||||
All incompatible changes to this must be declared in interface-changes.rst.
|
||||
(This may also list compatible additions, but it's not a requirement.)
|
||||
|
||||
Degrees of importance and compatibility preservation
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Critical and central parts of the command interface have the strictest
|
||||
requirements. It may not be reasonable to break them, and other means to achieve
|
||||
some change have to be found. For example, the "seek" command is a bit of a
|
||||
mess, but since changing it would likely affect almost every user, it may be
|
||||
impossible to break at least the commonly used syntax. If changed anyway, there
|
||||
should be a deprecation period of at least 1 year, during which the command
|
||||
still works, and possibly a warning should remain even after this.
|
||||
|
||||
Important/often used parts must be deprecated for at least 2 releases before
|
||||
they can be broken. There must be at least 1 release where the deprecated
|
||||
functionality still works, and a replacement is available (if technically
|
||||
reasonable). For example, a feature deprecated in mpv 0.30.0 may be removed in
|
||||
mpv 0.32.0. Minor releases do not count towards this.
|
||||
|
||||
Less useful parts can be broken immediately, but must come with some sort of
|
||||
removal warning-
|
||||
|
||||
Parts for debugging and testing may be removed any time, potentially even
|
||||
without any sort of documentation.
|
||||
|
||||
Currently, the importance of a part is not documented and not even well-defined,
|
||||
which is probably a mistake.
|
||||
|
||||
Renaming or removing options
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Typically, renaming an option can be done in a compatible way with OPT_REPLACED.
|
||||
You may need to check whether the corresponding properties still work (including
|
||||
messy details like observing properties for changes).
|
||||
|
||||
OPT_REMOVED can be used to inform the user of alternatives or reasons for the
|
||||
removal, which is better than an option not found error. Likewise,
|
||||
m_option.deprecation_message should be set to something helpful.
|
||||
|
||||
Both OPT_REPLACED and OPT_REMOVED can remain in the code for a long time, since
|
||||
they're unintrusive and hopefully make incompatible changes less painful for
|
||||
users.
|
||||
|
||||
Scripting APIs
|
||||
--------------
|
||||
|
||||
This affects internal scripting APIs (currently Lua and JavaScript).
|
||||
|
||||
Vaguely the same rules as with the command interface apply. Since there is a
|
||||
large number of scripts, an effort should be made to provide compatibility
|
||||
for old scripts, but it does not need to be stronger than that of the command
|
||||
interface.
|
||||
|
||||
Undocumented parts of the scripting APIs are _not_ guaranteed for compatibility.
|
||||
This applies especially for internals. Languages like Lua do not have strict
|
||||
access control (nor does the mpv code try to emulate any), so if a script
|
||||
accesses private parts, and breaks on the next mpv release, it's not mpv's
|
||||
problem.
|
||||
|
||||
JSON IPC
|
||||
--------
|
||||
|
||||
The JSON IPC is a thin protocol wrapping the libmpv API and the command
|
||||
interface. Compatibility-wise, it's about the same as the scripting APIs.
|
||||
The JSON protocol commands should remain as compatible as possible, and it
|
||||
should probably accept the current way commands are delimited (line breaks)
|
||||
forever.
|
||||
|
||||
The protocol may accept non-standard JSON extensions, but only standard JSON
|
||||
(possibly with restrictions) is guaranteed for compatibility. Clients which want
|
||||
to remain compatible should not use any extensions.
|
||||
|
||||
CLI
|
||||
---
|
||||
|
||||
Things such as default key bindings do not necessarily require compatibility.
|
||||
However, the release notes should be extremely clear on changes to "important"
|
||||
key bindings. Bindings which restore the old behavior should be added to
|
||||
restore-old-bindings.conf.
|
||||
|
||||
Some option parsing is CLI-only and not available from libmpv or scripting. No
|
||||
compatibility guarantees come with them. However, the rules which mpv uses to
|
||||
distinguish between options and filenames must remain consistent (if the
|
||||
non-deprecated options syntax is used).
|
||||
|
||||
Terminal and log output
|
||||
-----------------------
|
||||
|
||||
There are no compatibility guarantees for the terminal output, or the text
|
||||
logged via ``MPV_EVENT_LOG_MESSAGE`` and similar APIs. In particular, scripts
|
||||
invoking mpv CLI are extremely discouraged from trying to parse text output,
|
||||
and should use other mechanisms such as the JSON IPC.
|
||||
|
||||
Protocols, filters, demuxers, etc.
|
||||
----------------------------------
|
||||
|
||||
Which of these are present is generally not guaranteed, and can even depend
|
||||
on compile time settings.
|
||||
|
||||
The filter list and their sub-options are considered part of the
|
||||
command-interface.
|
||||
|
||||
libmpv C API
|
||||
------------
|
||||
|
||||
The libmpv client API (such as ``<libmpv/client.h>``) mostly gives access to
|
||||
the command interface. The API itself (if looked at as a component separate
|
||||
from the command interface) is intended to be extremely stable.
|
||||
|
||||
All API changes are documented in client-api-changes.rst.
|
||||
|
||||
API compatibility
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
The API is *always* compatible. Incompatible changes are only allowed on major
|
||||
API version changes (see ``MPV_CLIENT_API_VERSION``). A major version change is
|
||||
an extremely rare event, which means usually no API symbols are ever removed.
|
||||
|
||||
Essentially removing API functions by making them always return an error, or
|
||||
making it do nothing is allowed in cases where it is unlikely to break most
|
||||
clients, but requires a deprecation period of 2 releases. (This has happened to
|
||||
``mpv_suspend()`` for example.)
|
||||
|
||||
API symbols can be deprecated. This should be clearly marked in the doxygen
|
||||
with ``@deprecated``, and if possible, the affected API symbols should not be
|
||||
visible if the API user defines ``MPV_ENABLE_DEPRECATED`` to 0.
|
||||
|
||||
ABI compatibility
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
The ABI must never be broken, except on major API version changes. For example,
|
||||
constants don't change their values.
|
||||
|
||||
Structs are tricky. If a struct can be allocated by a user (such as ``mpv_node``),
|
||||
no fields can be added. (Unless it's an union, and the addition does not change
|
||||
the offset or alignment of any of the fields or the struct itself. This has
|
||||
happened to ``mpv_node`` in the past.) If a struct is allocated by libmpv only,
|
||||
new fields can be appended to the end (for example ``mpv_event``).
|
||||
|
||||
The ABI is only backward compatible. This means if a host application is linked
|
||||
to an older libmpv, and libmpv is updated to a newer version, it will still
|
||||
work (as in not causing any undefined behavior).
|
||||
|
||||
Forward compatibility (an application would work with an older libmpv than it
|
||||
was linked to) is not required.
|
||||
+24
-26
@@ -68,7 +68,7 @@ echo "MXE_TARGETS := i686-w64-mingw32.static" >> settings.mk
|
||||
# Build required packages. The following provide a minimum required to build
|
||||
# a reasonable mpv binary (though not an absolute minimum).
|
||||
|
||||
make gcc ffmpeg libass jpeg lua luajit
|
||||
make gcc ffmpeg libass jpeg lua
|
||||
|
||||
# Add MXE binaries to $PATH
|
||||
export PATH=/opt/mxe/usr/bin/:$PATH
|
||||
@@ -101,14 +101,11 @@ Installing MSYS2
|
||||
|
||||
1. Download an installer from https://msys2.github.io/
|
||||
|
||||
Both the i686 and the x86_64 version of MSYS2 can build 32-bit and 64-bit
|
||||
mpv binaries when running on a 64-bit version of Windows, but the x86_64
|
||||
version is preferred since the larger address space makes it less prone to
|
||||
fork() errors.
|
||||
It doesn't matter whether the i686 or the x86_64 version is used. Both can
|
||||
build 32-bit and 64-bit binaries when running on a 64-bit version of Windows.
|
||||
|
||||
2. Start a MinGW-w64 shell (``mingw64.exe``). **Note:** This is different from
|
||||
the MSYS2 shell that is started from the final installation dialog. You must
|
||||
close that shell and open a new one.
|
||||
2. Start a MinGW-w64 shell (``mingw64.exe``). Note that this is different
|
||||
from the MSYS2 shell that is started from the final installation dialog.
|
||||
|
||||
For a 32-bit build, use ``mingw32.exe``.
|
||||
|
||||
@@ -132,48 +129,49 @@ Installing mpv dependencies
|
||||
|
||||
```bash
|
||||
# Install MSYS2 build dependencies and a MinGW-w64 compiler
|
||||
pacman -S git python $MINGW_PACKAGE_PREFIX-{pkg-config,gcc}
|
||||
pacman -S git mingw-w64-x86_64-pkg-config python mingw-w64-x86_64-gcc
|
||||
|
||||
# Install the most important MinGW-w64 dependencies. libass and lcms2 are also
|
||||
# pulled in as dependencies of ffmpeg.
|
||||
pacman -S $MINGW_PACKAGE_PREFIX-{ffmpeg,libjpeg-turbo,lua51}
|
||||
# Install the most important MinGW-w64 dependencies. libass, libbluray and
|
||||
# lcms2 are also pulled in as dependencies of ffmpeg.
|
||||
pacman -S mingw-w64-x86_64-ffmpeg mingw-w64-x86_64-libjpeg-turbo mingw-w64-x86_64-lua51
|
||||
|
||||
# Install additional (optional) dependencies
|
||||
pacman -S mingw-w64-x86_64-libdvdnav mingw-w64-x86_64-libguess mingw-w64-x86_64-angleproject-git
|
||||
```
|
||||
|
||||
For a 32-bit build, install ``mingw-w64-i686-*`` packages instead.
|
||||
|
||||
Building mpv
|
||||
------------
|
||||
|
||||
Clone the latest mpv from git and install waf. **Note:** ``/usr/bin/python3``
|
||||
is invoked directly here, since an MSYS2 version of Python is required.
|
||||
Clone the latest mpv from git and install waf:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/mpv-player/mpv.git && cd mpv
|
||||
/usr/bin/python3 bootstrap.py
|
||||
./bootstrap.py
|
||||
```
|
||||
|
||||
Finally, compile and install mpv. Binaries will be installed to
|
||||
``/mingw64/bin`` or ``/mingw32/bin``.
|
||||
``/mingw64/bin``.
|
||||
|
||||
```bash
|
||||
/usr/bin/python3 waf configure CC=gcc.exe --check-c-compiler=gcc --prefix=$MSYSTEM_PREFIX
|
||||
/usr/bin/python3 waf install
|
||||
# For a 32-bit build, use --prefix=/mingw32 instead
|
||||
./waf configure CC=gcc.exe --check-c-compiler=gcc --prefix=/mingw64
|
||||
./waf install
|
||||
```
|
||||
|
||||
Or, compile and install both libmpv and mpv:
|
||||
|
||||
```bash
|
||||
/usr/bin/python3 waf configure CC=gcc.exe --check-c-compiler=gcc --enable-libmpv-shared --prefix=$MSYSTEM_PREFIX
|
||||
/usr/bin/python3 waf install
|
||||
./waf configure CC=gcc.exe --check-c-compiler=gcc --enable-libmpv-shared --prefix=/mingw64
|
||||
./waf install
|
||||
```
|
||||
|
||||
Linking libmpv with MSVC programs
|
||||
---------------------------------
|
||||
|
||||
mpv/libmpv cannot be built with Visual Studio (Microsoft is too incompetent to
|
||||
support C99/C11 properly and/or hates open source and Linux too much to
|
||||
seriously do it). But you can build C++ programs in Visual Studio and link them
|
||||
with a libmpv built with MinGW.
|
||||
|
||||
To do this, you need a Visual Studio which supports ``stdint.h`` (recent ones do),
|
||||
You can build C++ programs in Visual Studio and link them with libmpv. To do
|
||||
this, you need a Visual Studio which supports ``stdint.h`` (recent ones do),
|
||||
and you need to create a import library for the mpv DLL:
|
||||
|
||||
```bash
|
||||
|
||||
+30
-113
@@ -5,60 +5,35 @@ General
|
||||
-------
|
||||
|
||||
The main contact for mpv development is IRC, specifically #mpv
|
||||
and #mpv-devel on Freenode. Github is used for code review and
|
||||
long term discussions.
|
||||
and #mpv-devel on Freenode.
|
||||
|
||||
Sending patches
|
||||
---------------
|
||||
|
||||
- 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
|
||||
commit message and authorship information.
|
||||
- Never send patches to any of the developers email addresses.
|
||||
- If your changes are not supposed to be merged immediately, mark them as
|
||||
"[RFC]" in the commit message or the pull request title.
|
||||
- Be sure to test your changes. If you didn't, please say so in the commit
|
||||
message and the pull request text.
|
||||
|
||||
Copyright of contributions
|
||||
--------------------------
|
||||
|
||||
- The copyright belongs to contributors. The project is a collaborative work. By
|
||||
sending your changes, you agree to license your contributions according to the
|
||||
requirements of this project.
|
||||
``git format-patch``. diffs posted as pastebins (especially if the http link
|
||||
returns HTML) just cause extra work for everyone, because they lack commit
|
||||
message and authorship information.
|
||||
- All new code must be LGPLv2.1+ licensed, or come with the implicit agreement
|
||||
that it will be relicensed to LGPLv2.1+ later (see ``Copyright`` in the
|
||||
repository root directory).
|
||||
- 100% compatible licenses are allowed too.
|
||||
- Changes in files with more liberal licenses (such as BSD, MIT, or ISC) are
|
||||
assumed to be dual-licensed under LGPLv2.1+ and the license indicated in the
|
||||
file header.
|
||||
- You must be either the exclusive author of the patch, or acknowledge all
|
||||
authors involved in the commit message. If you take 3rd party code, authorship
|
||||
and copyright must be properly acknowledged. If you're making changes on
|
||||
behalf of your employer, and the employer owns the copyright, you must mention
|
||||
this. If the license of the code is not LGPLv2.1+, you must mention this.
|
||||
- These license statements are legally binding.
|
||||
- Don't use fake names (something that looks like an actual name, and may be
|
||||
and copyright must be properly acknowledged. If the license of the code is not
|
||||
LGPLv2.1+, this must be mentioned.
|
||||
- Don't use fake names (something that looks like an actual names, and may be
|
||||
someone else's name, but is not your legal name). Using a pseudonyms is
|
||||
allowed if it can be used to identify or contact you, even if whatever
|
||||
account you used to submit the patch dies.
|
||||
- Do not add your name to the license header. This convention is not used by
|
||||
this project, and neither copyright law nor any of the used licenses require
|
||||
it.
|
||||
|
||||
Write good commit messages
|
||||
--------------------------
|
||||
|
||||
- When creating pull requests, be sure to test your changes. If you didn't,
|
||||
please say so in the pull request message.
|
||||
- Write informative commit messages. Use present tense to describe the
|
||||
situation with the patch applied, and past tense for the situation before
|
||||
the change.
|
||||
- The subject line (the first line in a commit message) must contain a
|
||||
- The subject line (the first line in a commit message) should contain a
|
||||
prefix identifying the sub system, followed by a short description what
|
||||
impact this commit has. This subject line and the commit message body
|
||||
must not be longer than 72 characters per line, because it messes up the
|
||||
shouldn't be longer than 72 characters per line, because it messes up the
|
||||
output of many git tools otherwise.
|
||||
|
||||
For example, you fixed a crash in af_volume.c:
|
||||
@@ -68,11 +43,7 @@ Write good commit messages
|
||||
|
||||
Having a prefix gives context, and is especially useful when trying to find
|
||||
a specific change by looking at the history, or when running ``git blame``.
|
||||
- The first word after the ``:`` is lower case.
|
||||
- Don't end the subject line with a ``.``.
|
||||
- Put an empty line between the subject line and the commit message.
|
||||
If this is missing, it will break display in common git tools.
|
||||
- The body of the commit message (everything else after the subject line) must
|
||||
- The body of the commit message (everything else after the subject line) should
|
||||
be as informative as possible and contain everything that isn't obvious. Don't
|
||||
hesitate to dump as much information as you can - it doesn't cost you
|
||||
anything. Put some effort into it. If someone finds a bug months or years
|
||||
@@ -81,56 +52,25 @@ Write good commit messages
|
||||
information to test the original bug. The old bug might be reintroduced while
|
||||
fixing the new bug.
|
||||
|
||||
The commit message must be wrapped on 72 characters per line, because git
|
||||
The commit message should be wrapped on 72 characters per line, because git
|
||||
tools usually do not break text automatically. On the other hand, you do not
|
||||
need to break text that would be unnatural to break (like data for test cases,
|
||||
or long URLs).
|
||||
- Another summary of good conventions: https://chris.beams.io/posts/git-commit/
|
||||
|
||||
Split changes into multiple commits
|
||||
-----------------------------------
|
||||
|
||||
- Follow git good practices, and split independent changes into several commits.
|
||||
It's usually OK to put them into a single pull request.
|
||||
Important: put an empty line between the subject line and the commit message.
|
||||
If this is missing, it will break display in common git tools.
|
||||
- Try to separate cosmetic and functional changes. It's ok to make a few
|
||||
additional cosmetic changes in the same file you're working on. But don't do
|
||||
something like reformatting a whole file, and hiding an actual functional
|
||||
change in the same commit.
|
||||
- Splitting changes does _not_ mean that you should make them as fine-grained
|
||||
as possible. Commits should form logical steps in development. The way you
|
||||
split changes is important for code review and analyzing bugs.
|
||||
|
||||
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
|
||||
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
|
||||
"changes from all commits" view (which just encourages bad git and review
|
||||
practices).
|
||||
|
||||
Touching user-visible parts may require updating the mpv docs
|
||||
-------------------------------------------------------------
|
||||
|
||||
- Most user-visible things are normally documented in DOCS/man/. If your commit
|
||||
touches documented behavior, list of sub-options, etc., you need to adjust the
|
||||
documentation.
|
||||
- These changes usually go into the same commit that changes the code.
|
||||
- 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.
|
||||
documented in options.rst. Changes to input properties or input commands must
|
||||
be documented in input.rst. All changes to the user interface (options,
|
||||
properties, commands) must be documented with a small note in
|
||||
interface-changes.rst (although documenting additions is optional, and
|
||||
obscure corner cases and potentially be skipped too). Changes to the libmpv
|
||||
API must be reflected in the libmpv's headers doxygen, and should be
|
||||
documented in client-api-changes.rst.
|
||||
|
||||
Code formatting
|
||||
---------------
|
||||
@@ -140,7 +80,7 @@ mpv uses C99 with K&R formatting, with some exceptions.
|
||||
- Use the K&R indent style.
|
||||
- Use 4 spaces of indentation, never use tabs (except in Makefiles).
|
||||
- Add a single space between keywords and binary operators. There are some other
|
||||
cases where spaces must be added. Example:
|
||||
cases where spaces should be added. Example:
|
||||
|
||||
```C
|
||||
if ((a * b) > c) {
|
||||
@@ -173,7 +113,7 @@ mpv uses C99 with K&R formatting, with some exceptions.
|
||||
do_something();
|
||||
}
|
||||
```
|
||||
- If the if has an else branch, both branches must use braces, even if they're
|
||||
- If the if has an else branch, both branches should use braces, even if they're
|
||||
technically redundant.
|
||||
|
||||
Example:
|
||||
@@ -203,31 +143,18 @@ mpv uses C99 with K&R formatting, with some exceptions.
|
||||
|
||||
(If the if body is simple enough, this rule can be skipped.)
|
||||
- Remove any trailing whitespace.
|
||||
- Do not make stray whitespaces changes.
|
||||
|
||||
Header #include statement order
|
||||
-------------------------------
|
||||
|
||||
The order of ``#include`` statements in the source code is not very consistent.
|
||||
New code must follow the following conventions:
|
||||
|
||||
- Put standard includes (``#include <stdlib.h>`` etc.) on the top,
|
||||
- then after a blank line, add library includes (``#include <zlib.h>`` etc.)
|
||||
- then after a blank line, add internal includes (``#include "player/core.h"``)
|
||||
- sort them alphabetically within these sections
|
||||
|
||||
General coding
|
||||
--------------
|
||||
|
||||
- Use C99. Also freely make use of C99 features if it's appropriate, such as
|
||||
stdbool.h. (Except VLA and complex number types.)
|
||||
- Don't use non-standard language (such as GNU C-only features). In some cases
|
||||
they may be warranted, if they are optional (such as attributes enabling
|
||||
printf-like format string checks). "#pragma once" is allowed as an exception.
|
||||
But in general, standard C99 must be used.
|
||||
stdbool.h.
|
||||
- Don't use GNU-only features. In some cases they may be warranted, if they
|
||||
are optional (such as attributes enabling printf-like format string checks).
|
||||
But in general, standard C99 should be used.
|
||||
- The same applies to libc functions. We have to be Windows-compatible too. Use
|
||||
functions guaranteed by C99 or POSIX only, unless your use is guarded by a
|
||||
configure check. There is some restricted use of C11 (ask on IRC for details).
|
||||
configure check.
|
||||
- Prefer fusing declaration and initialization, rather than putting declarations
|
||||
on the top of a block. Obvious data flow is more important than avoiding
|
||||
mixing declarations and statements, which is just a C90 artifact.
|
||||
@@ -235,14 +162,6 @@ General coding
|
||||
channel first. There might be a better way to add a feature and it can avoid
|
||||
wasted work.
|
||||
|
||||
Code of Conduct
|
||||
---------------
|
||||
|
||||
Please note that this project is released with a Contributor Code of Conduct.
|
||||
By participating in this project you agree to abide by its terms.
|
||||
The Contributor Code of Conduct can be found here:
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct/
|
||||
|
||||
Rules for git push access
|
||||
-------------------------
|
||||
|
||||
@@ -261,9 +180,7 @@ you got access, the following rules must be followed:
|
||||
major bug fixes, or you're the original author of the code). If there is more
|
||||
than one maintainer, you may need to come to an agreement with the others how
|
||||
to handle this to avoid conflict.
|
||||
- If you make a pull requests (especially if it's to code you maintain), and you
|
||||
want reviews, explicitly ping the people from which you expect reviews.
|
||||
- As a maintainer, you can approve pull requests by others to "your" code.
|
||||
- As a maintainer, you can approve pushes by others to "your" code.
|
||||
- If you approve or merge 3rd party changes, make sure they follow the general
|
||||
development rules.
|
||||
- Changes to user interface and public API must always be approved by the
|
||||
|
||||
+1
-220
@@ -100,18 +100,6 @@ entries affect all other file entries in the EDL file. Their format is highly
|
||||
implementation specific. They should generally follow the file header, and come
|
||||
before any file entries.
|
||||
|
||||
Disabling chapter generation and copying
|
||||
========================================
|
||||
|
||||
By default, chapters from the source ranges are copied to the virtual file's
|
||||
chapters. Also, a chapter is inserted after each range. This can be disabled
|
||||
with the ``no_chapters`` header.
|
||||
|
||||
Example::
|
||||
|
||||
!no_chapters
|
||||
|
||||
|
||||
MP4 DASH
|
||||
========
|
||||
|
||||
@@ -143,164 +131,6 @@ The current implementation will
|
||||
- not add segment boundaries as chapter points
|
||||
- require full compatibility between all segments (same codec etc.)
|
||||
|
||||
Another header part of this mechanism is ``no_clip``. This header is similar
|
||||
to ``mp4_dash``, but does not include on-demand opening/closing of segments,
|
||||
and does not support init segments. It also exists solely to support internal
|
||||
ytdl requirements. Using ``no_clip`` with segments is not recommended and
|
||||
probably breaks. ``mp4_dash`` already implicitly does a variant of ``no_clip``.
|
||||
|
||||
The ``mp4_dash`` and ``no_clip`` headers are not part of the core EDL format.
|
||||
They may be changed or removed at any time, depending on mpv's internal
|
||||
requirements.
|
||||
|
||||
Separate files for tracks
|
||||
=========================
|
||||
|
||||
The special ``new_stream`` header lets you specify separate parts and time
|
||||
offsets for separate tracks. This can for example be used to source audio and
|
||||
video track from separate files.
|
||||
|
||||
Example::
|
||||
|
||||
# mpv EDL v0
|
||||
video.mkv
|
||||
!new_stream
|
||||
audio.mkv
|
||||
|
||||
This adds all tracks from both files to the virtual track list. Upon playback,
|
||||
the tracks will be played at the same time, instead of appending them. The files
|
||||
can contain more than 1 stream; the apparent effect is the same as if the second
|
||||
part after the ``!new_stream`` part were in a separate ``.edl`` file and added
|
||||
with ``--external-file``.
|
||||
|
||||
Note that all metadata between the stream sets created by ``new_stream`` is
|
||||
disjoint. Global metadata is taken from the first part only.
|
||||
|
||||
In context of mpv, this is redundant to the ``--audio-file`` and
|
||||
``--external-file`` options, but (as of this writing) has the advantage that
|
||||
this will use a unified cache for all streams.
|
||||
|
||||
The ``new_stream`` header is not part of the core EDL format. It may be changed
|
||||
or removed at any time, depending on mpv's internal requirements.
|
||||
|
||||
If the first ``!new_stream`` is redundant, it is ignored. This is the same
|
||||
example as above::
|
||||
|
||||
# mpv EDL v0
|
||||
!new_stream
|
||||
video.mkv
|
||||
!new_stream
|
||||
audio.mkv
|
||||
|
||||
Note that ``!new_stream`` must be the first header. Whether the parser accepts
|
||||
(i.e. ignores) or rejects other headers before that is implementation specific.
|
||||
|
||||
Track metadata
|
||||
==============
|
||||
|
||||
The special ``track_meta`` header can set some specific metadata fields of the
|
||||
current ``!new_stream`` partition. The tags are applied to all tracks within
|
||||
the partition. It is not possible to set the metadata for individual tracks (the
|
||||
feature was needed only for single-track media).
|
||||
|
||||
It provides following parameters change track metadata:
|
||||
|
||||
``lang``
|
||||
Set the language tag.
|
||||
|
||||
``title``
|
||||
Set the title tag.
|
||||
|
||||
``byterate``
|
||||
Number of bytes per second this stream uses. (Purely informational.)
|
||||
|
||||
``index``
|
||||
The numeric index of the track this should map to (default: -1). This is
|
||||
the 0-based index of the virtual stream as seen by the player, enumerating
|
||||
all audio/video/subtitle streams. If nothing matches, this is silently
|
||||
discarded. The special index -1 (the default) has two meanings: if there
|
||||
was a previous meta data entry (either ``!track_meta`` or ``!delay_open``
|
||||
element since the last ``!new_stream``), then this element manipulates
|
||||
the previous meta data entry. If there was no previous entry, a new meta
|
||||
data entry that matches all streams is created.
|
||||
|
||||
Example::
|
||||
|
||||
# mpv EDL v0
|
||||
!track_meta,lang=bla,title=blabla
|
||||
file.mkv
|
||||
!new_stream
|
||||
!track_meta,title=ducks
|
||||
sub.srt
|
||||
|
||||
If ``file.mkv`` has an audio and a video stream, both will use ``blabla`` as
|
||||
title. The subtitle stream will use ``ducks`` as title.
|
||||
|
||||
The ``track_meta`` header is not part of the core EDL format. It may be changed
|
||||
or removed at any time, depending on mpv's internal requirements.
|
||||
|
||||
Delayed media opening
|
||||
=====================
|
||||
|
||||
The special ``delay_open`` header can be used to open the media URL of the
|
||||
stream only when the track is selected for the first time. This is supposed to
|
||||
be an optimization to speed up opening of a remote stream if there are many
|
||||
tracks for whatever reasons.
|
||||
|
||||
This has various tricky restrictions, and also will defer failure to open a
|
||||
stream to "later". By design, it's supposed to be used for single-track streams.
|
||||
|
||||
Using multiple segments requires you to specify all offsets and durations (also
|
||||
it was never tested whether it works at all). Interaction with ``mp4_dash`` may
|
||||
be strange.
|
||||
|
||||
You can describe multiple sub-tracks by using multiple ``delay_open`` headers
|
||||
before the same source URL. (If there are multiple sub-tracks of the same media
|
||||
type, then the mapping to the real stream is probably rather arbitrary.) If the
|
||||
source contains tracks not described, a warning is logged when the delayed
|
||||
opening happens, and the track is hidden.
|
||||
|
||||
This has the following parameters:
|
||||
|
||||
``media_type``
|
||||
Required. Must be set to ``video``, ``audio``, or ``sub``. (Other tracks in
|
||||
the opened URL are ignored.)
|
||||
|
||||
``codec``
|
||||
The mpv codec name that is expected. Although mpv tries to initialize a
|
||||
decoder with it currently (and will fail track selection if it does not
|
||||
initialize successfully), it is not used for decoding - decoding still uses
|
||||
the information retrieved from opening the actual media information, and may
|
||||
be a different codec (you should try to avoid this, of course). Defaults to
|
||||
``null``.
|
||||
|
||||
Above also applies for similar fields such as ``w``. These fields are
|
||||
mostly to help with user track pre-selection.
|
||||
|
||||
``flags``
|
||||
A ``+`` separated list of boolean flags. Currently defined flags:
|
||||
|
||||
``default``
|
||||
Set the default track flag.
|
||||
|
||||
``forced``
|
||||
Set the forced track flag.
|
||||
|
||||
Other values are ignored after triggering a warning.
|
||||
|
||||
``w``, ``h``
|
||||
For video codecs: expected video size. See ``codec`` for details.
|
||||
|
||||
``fps``
|
||||
For video codecs: expected video framerate, as integer. (The rate is usually
|
||||
only crudely reported, and it makes no sense to expect exact values.)
|
||||
|
||||
``samplerate``
|
||||
For audio codecs: expected sample rate, as integer.
|
||||
|
||||
The ``delay_open`` header is not part of the core EDL format. It may be changed
|
||||
or removed at any time, depending on mpv's internal requirements.
|
||||
|
||||
Timestamp format
|
||||
================
|
||||
|
||||
@@ -308,8 +138,7 @@ Currently, time values are floating point values in seconds.
|
||||
|
||||
As an extension, you can set the ``timestamps=chapters`` option. If this option
|
||||
is set, timestamps have to be integers, and refer to chapter numbers, starting
|
||||
with 0. The default value for this parameter is ``seconds``, which means the
|
||||
time is as described in the previous paragraph.
|
||||
with 0.
|
||||
|
||||
Example::
|
||||
|
||||
@@ -318,54 +147,6 @@ Example::
|
||||
|
||||
Plays chapter 3 and ends with the start of chapter 7 (4 chapters later).
|
||||
|
||||
Implicit chapters
|
||||
=================
|
||||
|
||||
mpv will add one chapter per segment entry to the virtual timeline.
|
||||
|
||||
By default, the chapter's titles will match the entries' filenames.
|
||||
You can override set the ``title`` option to override the chapter title for
|
||||
that segment.
|
||||
|
||||
Example::
|
||||
|
||||
# mpv EDL v0
|
||||
cap.ts,5,240
|
||||
OP.mkv,0,90,title=Show Opening
|
||||
|
||||
The virtual timeline will have two chapters, one called "cap.ts" from 0-240s
|
||||
and a second one called "Show Opening" from 240-330s.
|
||||
|
||||
Entry which defines the track layout
|
||||
====================================
|
||||
|
||||
Normally, you're supposed to put only files with compatible layouts into an EDL
|
||||
file. However, at least the mpv implementation accepts entries that use
|
||||
different codecs, or even have a different number of audio/video/subtitle
|
||||
tracks. In this case, it's not obvious, which virtual tracks the EDL show should
|
||||
expose when being played.
|
||||
|
||||
Currently, mpv will apply an arbitrary heuristic which tracks the EDL file
|
||||
should expose. (Before mpv 0.30.0, it always used the first source file in the
|
||||
segment list.)
|
||||
|
||||
You can set the ``layout`` option to ``this`` to make a specific entry define
|
||||
the track layout.
|
||||
|
||||
Example::
|
||||
|
||||
# mpv EDL v0
|
||||
file_with_2_streams.ts,5,240
|
||||
file_with_5_streams.mkv,0,90,layout=this
|
||||
|
||||
The way the different virtual EDL tracks are associated with the per-segment
|
||||
ones is highly implementation-defined, and uses a heuristic. If a segment is
|
||||
missing a track, there will be a "hole", and bad behavior may result. Improving
|
||||
this is subject to further development (due to being fringe cases, they don't
|
||||
have a high priority).
|
||||
|
||||
If future versions of mpv change this again, this option may be ignored.
|
||||
|
||||
Syntax of EDL URIs
|
||||
==================
|
||||
|
||||
|
||||
+9
-12
@@ -3,7 +3,8 @@ General usage
|
||||
|
||||
::
|
||||
|
||||
mpv infile -o outfile [-of outfileformat] [-ofopts formatoptions] [-orawts] \
|
||||
mpv infile -o outfile [-of outfileformat] [-ofopts formatoptions] \
|
||||
[-ofps outfps | -oautofps] [-oharddup] [-ocopyts | -orawts] [-oneverdrop] \
|
||||
[(any other mpv options)] \
|
||||
-ovc outvideocodec [-ovcopts outvideocodecoptions] \
|
||||
-oac outaudiocodec [-oacopts outaudiocodecoptions]
|
||||
@@ -30,13 +31,9 @@ section::
|
||||
[myencprofile]
|
||||
vf-add = scale=480:-2
|
||||
ovc = libx264
|
||||
ovcopts-add = preset=medium
|
||||
ovcopts-add = tune=fastdecode
|
||||
ovcopts-add = preset=medium,tune=fastdecode
|
||||
ovcopts-add = crf=23
|
||||
ovcopts-add = maxrate=1500k
|
||||
ovcopts-add = bufsize=1000k
|
||||
ovcopts-add = rc_init_occupancy=900k
|
||||
ovcopts-add = refs=2
|
||||
ovcopts-add = maxrate=1500k,bufsize=1000k,rc_init_occupancy=900k,refs=2
|
||||
ovcopts-add = profile=baseline
|
||||
oac = aac
|
||||
oacopts-add = b=96k
|
||||
@@ -63,13 +60,13 @@ for.
|
||||
Typical MPEG-4 Part 2 ("ASP", "DivX") encoding, AVI container::
|
||||
|
||||
mpv infile -o outfile.avi \
|
||||
--vf=fps=25 \
|
||||
-ofps 25 \
|
||||
-ovc mpeg4 -ovcopts qscale=4 \
|
||||
-oac libmp3lame -oacopts ab=128k
|
||||
|
||||
Note: AVI does not support variable frame rate, so the fps filter must be used.
|
||||
The frame rate should ideally match the input (25 for PAL, 24000/1001 or
|
||||
30000/1001 for NTSC)
|
||||
Note: AVI does not support variable frame rate, so -ofps must be used. The
|
||||
frame rate should ideally match the input (25 for PAL, 24000/1001 or 30000/1001
|
||||
for NTSC)
|
||||
|
||||
Typical MPEG-4 Part 10 ("AVC", "H.264") encoding, Matroska (MKV) container::
|
||||
|
||||
@@ -132,7 +129,7 @@ What works
|
||||
==========
|
||||
|
||||
* Encoding at variable frame rate (default)
|
||||
* Encoding at constant frame rate using --vf=fps=RATE
|
||||
* Encoding at constant frame rate using -ofps framerate -oharddup
|
||||
* 2-pass encoding (specify flags=+pass1 in the first pass's -ovcopts, specify
|
||||
flags=+pass2 in the second pass)
|
||||
* Hardcoding subtitles using vobsub, ass or srt subtitle rendering (just
|
||||
|
||||
+1
-345
@@ -7,362 +7,18 @@ mpv provides access to its internals via the following means:
|
||||
- commands
|
||||
- properties
|
||||
- events
|
||||
- hooks
|
||||
|
||||
The sum of these mechanisms is sometimes called command interface.
|
||||
|
||||
All of these are important for interfacing both with end users and API users
|
||||
(which include Lua scripts, libmpv, and the JSON IPC). As such, they constitute
|
||||
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 changes are added to the top.
|
||||
|
||||
Interface changes
|
||||
=================
|
||||
|
||||
::
|
||||
|
||||
--- mpv 0.33.0 ---
|
||||
- add `--d3d11-exclusive-fs` flag to enable D3D11 exclusive fullscreen mode
|
||||
when the player enters fullscreen.
|
||||
- directories in ~/.mpv/scripts/ (or equivalent) now have special semantics
|
||||
(see mpv Lua scripting docs)
|
||||
- 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
|
||||
- deprecate the --cache-secs option (once removed, the cache cannot be
|
||||
limited by time anymore)
|
||||
- remove deprecated legacy hook API ("hook-add", "hook-ack"). Use either the
|
||||
libmpv API (mpv_hook_add(), mpv_hook_continue()), or the Lua scripting
|
||||
wrappers (mp.add_hook()).
|
||||
- improve how property change notifications are delivered on events and on
|
||||
hooks. In particular, a hook event is only returned to a client after all
|
||||
changes initiated before the hook point were delivered to the same client.
|
||||
In addition, it should no longer happen that events and property change
|
||||
notifications were interleaved in bad ways (it could happen that a
|
||||
property notification delivered after an event contained a value that was
|
||||
valid only before the event happened).
|
||||
- the playlist-pos and playlist-pos-1 properties now can return and accept
|
||||
-1, and are never unavailable. Out of range indexes are now accepted, but
|
||||
behave like writing -1.
|
||||
- the playlist-pos and playlist-pos-1 properties deprecate the current
|
||||
behavior when writing back the current value to the property: currently,
|
||||
this restarts playback, but in the future, it will do nothing.
|
||||
Using the "playlist-play-index" command is recommended instead.
|
||||
- add "playlist-play-index" command
|
||||
- add playlist-current-pos, playlist-playing-pos properties
|
||||
- Lua end-file events set the "error" field; this is deprecated; use the
|
||||
"file_error" instead for this specific event. Scripts relying on the
|
||||
"error" field for end-file will silently break at some point in the
|
||||
future.
|
||||
- remove deprecated --input-file option, add --input-ipc-client, which is
|
||||
vaguely a replacement of the removed option, but not the same
|
||||
- change another detail for track selection options (see --aid manpage
|
||||
entry)
|
||||
- reading loop-file property as native property or mpv_node will now return
|
||||
"inf" instead of boolean true (also affects loop option)
|
||||
- remove some --vo-direct3d-... options (it got dumbed down; use --vo=gpu)
|
||||
- remove video-params/plane-depth property (was too vaguely defined)
|
||||
- remove --video-sync-adrop-size option (implementation was changed, no
|
||||
replacement for what this option did)
|
||||
- undeprecate --video-sync=display-adrop
|
||||
- deprecate legacy auto profiles (profiles starting with "extension." and
|
||||
"protocol."). Use conditional auto profiles instead.
|
||||
- the "subprocess" command does not connect spawned processes' stdin to
|
||||
mpv's stdin anymore. Instead, stdin is connected to /dev/null by default.
|
||||
To get the old behavior, set the "passthrough_stdin" argument to true.
|
||||
- key/value list options do not accept ":" as item separator anymore,
|
||||
only ",". This means ":" is always considered part of the value.
|
||||
- remove deprecated --vo-vdpau-deint option
|
||||
- add `delete-watch-later-config` command to complement
|
||||
`write-watch-later-config`
|
||||
--- mpv 0.32.0 ---
|
||||
- change behavior when using legacy option syntax with options that start
|
||||
with two dashes (``--`` instead of a ``-``). Now, using the recommended
|
||||
syntax is required for options starting with ``--``, which means an option
|
||||
value must be strictly passed after a ``=``, instead of as separate
|
||||
argument. For example, ``--log-file f.txt`` was previously accepted and
|
||||
behaved like ``--log-file=f.txt``, but now causes an error. Use of legacy
|
||||
syntax that is still supported now prints a deprecation warning.
|
||||
--- mpv 0.31.0 ---
|
||||
- add `--resume-playback-check-mtime` to check consistent mtime when
|
||||
restoring playback state.
|
||||
- add `--d3d11-output-csp` to enable explicit selection of a D3D11
|
||||
swap chain color space.
|
||||
- the --sws- options and similar now affect vo_image and screenshot
|
||||
conversion (does not matter as much for vo_gpu, which does most of this
|
||||
with shaders)
|
||||
- add a builtin "sw-fast" profile, which restores performance settings
|
||||
for software video conversion. These were switched to higher quality since
|
||||
mpv 0.30.0 (related to the previous changelog entry). This affects video
|
||||
outputs like vo_x11 and vo_drm, and screenshots, but not much else.
|
||||
- deprecate --input-file (there are no plans to remove this short-term,
|
||||
but it will probably eventually go away <- that was a lie)
|
||||
- deprecate --video-sync=display-adrop (might be removed if it's in the way;
|
||||
undeprecated or readded if it's not too much of a problem)
|
||||
- deprecate all input section commands (these will be changed/removed, as
|
||||
soon as mpv internals do not require them anymore)
|
||||
- remove deprecated --playlist-pos alias (use --playlist-start)
|
||||
- deprecate --display-fps, introduce --override-display-fps. The display-fps
|
||||
property now is unavailable if no VO exists (or the VO did not return a
|
||||
display FPS), instead of returning the option value in this case. The
|
||||
property will keep existing, but writing to it is deprecated.
|
||||
- the vf/af properties now do not reject the set value anymore, even if
|
||||
filter chain initialization fails. Instead, the vf/af options are always
|
||||
set to the user's value, even if it does not reflect the "runtime" vf/af
|
||||
chain.
|
||||
- the vid/aid/sid/secondary-sid properties (and their aliases: "audio",
|
||||
"video", "sub") will now allow setting any track ID; before this change,
|
||||
only IDs of actually existing tracks could be set (the restriction was
|
||||
active the MPV_EVENT_FILE_LOADED/"file-loaded" event was sent). Setting
|
||||
an ID for which no track exists is equivalent to disabling it. Note that
|
||||
setting the properties to non-existing tracks may report it as selected
|
||||
track for a small time window, until it's forced back to "no". The exact
|
||||
details how this is handled may change in the future.
|
||||
- remove old Apple Remote support, including --input-appleremote
|
||||
- add MediaPlayer support and remove the old Media Key event tap on macOS.
|
||||
this possibly also re-adds the Apple Remote support
|
||||
- the "edition" property now strictly returns the value of the option,
|
||||
instead of the runtime value. The new "current-edition" property needs to
|
||||
be queried to read the runtime-chosen edition. This is a breaking change
|
||||
for any users which expected "edition" to return the runtime-chosen
|
||||
edition at default settings (--edition=auto).
|
||||
- the "window-scale" property now strictly returns the value of the option,
|
||||
instead of the actual size of the window. The new "current-window-scale"
|
||||
property needs to be queried to read the value as indicated by the current
|
||||
window size. This is a breaking change.
|
||||
- explicitly deprecate passing more than 1 item to "-add" suffix in key/value
|
||||
options (for example --script-opts-add). This was actually always
|
||||
deprecated, like with other list options, but the option parser did not
|
||||
print a warning in this particular case.
|
||||
- deprecate -del for list options (use -remove instead, which is by content
|
||||
instead of by integer index)
|
||||
- if `--fs` is used but `--fs-screen` is not set, mpv will now use `--screen`
|
||||
instead.
|
||||
- change the default of --hwdec to "no" on RPI. The default used to be "mmal"
|
||||
specifically if 'Raspberry Pi support' was enabled at configure time
|
||||
(equivalent to --enable-rpi). Use --hwdec=mmal to get the old behavior.
|
||||
--- mpv 0.30.0 ---
|
||||
- add `--d3d11-output-format` to enable explicit selection of a D3D11
|
||||
swap chain format.
|
||||
- rewrite DVB channel switching to use an integer value
|
||||
`--dvbin-channel-switch-offset` for switching instead of the old
|
||||
stream controls which are now gone. Cycling this property up or down will
|
||||
change the offset to the channel which was initially tuned to.
|
||||
Example for `input.conf`: `H cycle dvbin-channel-switch-offset up`,
|
||||
`K cycle dvbin-channel-switch-offset down`.
|
||||
- adapt `stream_dvb` to support writing to `dvbin-prog` at runtime
|
||||
and also to consistently use dvbin-configuration over URI parameters
|
||||
when provided
|
||||
- add `--d3d11-adapter` to enable explicit selection of a D3D11 rendering
|
||||
adapter by name.
|
||||
- rename `--drm-osd-plane-id` to `--drm-draw-plane`, `--drm-video-plane-id` to
|
||||
`--drm-drmprime-video-plane` and `--drm-osd-size` to `--drm-draw-surface-size`
|
||||
to better reflect what the options actually control, that the values they
|
||||
accept aren't actually internal DRM ID's (like with similar options in
|
||||
ffmpeg's KMS support), and that the video plane is only used when the drmprime
|
||||
overlay hwdec interop is active, with the video being drawn to the draw plane
|
||||
otherwise.
|
||||
- in addition to the above, the `--drm-draw-plane` and `--drm-drmprime-video-plane`
|
||||
options now accept either an integer index, or the values primary or overlay.
|
||||
`--drm-draw-plane` now defaults to primary and `--drm-drmprime-video-plane`
|
||||
defaults to overlay. This should be similar to previous behavior on most drivers
|
||||
due to how planes are usually sorted.
|
||||
- rename --opensles-frames-per-buffer to --opensles-frames-per-enqueue to
|
||||
better reflect its purpose. In the past it overrides the buffer size the AO
|
||||
requests (but not the default/value of the generic --audio-buffer option).
|
||||
Now it only guarantees that the soft buffer size will not be smaller than
|
||||
itself while setting the size per Enqueue.
|
||||
- add --opensles-buffer-size-in-ms, allowing user to tune the soft buffer size.
|
||||
It overrides the --audio-buffer option unless it's set to 0 (with the default
|
||||
being 250).
|
||||
- remove `--linear-scaling`, replaced by `--linear-upscaling` and
|
||||
`--linear-downscaling`. This means that `--sigmoid-upscaling` no longer
|
||||
implies linear light downscaling as well, which was confusing.
|
||||
- the built-in `gpu-hq` profile now includes` --linear-downscaling`.
|
||||
- support for `--spirv-compiler=nvidia` has been removed, leaving `shaderc`
|
||||
as the only option. The `--spirv-compiler` option itself has been marked
|
||||
as deprecated, and may be removed in the future.
|
||||
- split up `--tone-mapping-desaturate`` into strength + exponent, instead of
|
||||
only using a single value (which previously just controlled the exponent).
|
||||
The strength now linearly blends between the linear and nonlinear tone
|
||||
mapped versions of a color.
|
||||
- add --hdr-peak-decay-rate and --hdr-scene-threshold-low/high
|
||||
- add --tone-mapping-max-boost
|
||||
- ipc: require that "request_id" fields are integers. Other types are still
|
||||
accepted for compatibility, but this will stop in the future. Also, if no
|
||||
request_id is provided, 0 will be assumed.
|
||||
- mpv_command_node() and mp.command_native() now support named arguments
|
||||
(see manpage). If you want to use them, use a new version of the manpage
|
||||
as reference, which lists the definitive names.
|
||||
- edition and disc title switching will now fully reload playback (may have
|
||||
consequences for scripts, client API, or when using file-local options)
|
||||
- with the removal of the stream cache, the following properties and options were
|
||||
dropped: `cache`, `cache-size`, `cache-free`, `cache-used`, `--cache-default`,
|
||||
`--cache-initial`, `--cache-seek-min`, `--cache-backbuffer`, `--cache-file`,
|
||||
`--cache-file-size`
|
||||
- the --cache option does not take a number value anymore
|
||||
- remove async playback abort hack. This may make it impossible to abort
|
||||
playback if --demuxer-thread=no is forced.
|
||||
- remove `--macos-title-bar-style`, replaced by `--macos-title-bar-material`
|
||||
and `--macos-title-bar-appearance`.
|
||||
- The default for `--vulkan-async-compute` has changed to `yes` from `no`
|
||||
with the move to libplacebo as the back-end for vulkan rendering.
|
||||
- Remove "disc-titles", "disc-title", "disc-title-list", and "angle"
|
||||
properties. dvd:// does not support title ranges anymore.
|
||||
- Remove all "tv-..." options and properties, along with the classic Linux
|
||||
analog TV support.
|
||||
- remove "program" property (no replacement)
|
||||
- always prefer EGL over GLX, which helps with AMD/vaapi, but will break
|
||||
vdpau with --vo=gpu - use --gpu-context=x11 to be able to use vdpau. This
|
||||
does not affect --vo=vdpau or --hwdec=vdpau-copy.
|
||||
- remove deprecated --chapter option
|
||||
- deprecate --record-file
|
||||
- add `--demuxer-cue-codepage`
|
||||
- add ``track-list/N/demux-bitrate``, ``track-list/N/demux-rotation`` and
|
||||
``track-list/N/demux-par`` property
|
||||
- Deprecate ``--video-aspect`` and add ``--video-aspect-override`` to
|
||||
replace it. (The `video-aspect` option remains unchanged.)
|
||||
--- mpv 0.29.0 ---
|
||||
- drop --opensles-sample-rate, as --audio-samplerate should be used if desired
|
||||
- drop deprecated --videotoolbox-format, --ff-aid, --ff-vid, --ff-sid,
|
||||
--ad-spdif-dtshd, --softvol options
|
||||
- fix --external-files: strictly never select any tracks from them, unless
|
||||
explicitly selected (this may or may not be expected)
|
||||
- --ytdl is now always enabled, even for libmpv
|
||||
- add a number of --audio-resample-* options, which should from now on be
|
||||
used instead of --af-defaults=lavrresample:...
|
||||
- deprecate --vf-defaults and --af-defaults. These didn't work with the
|
||||
lavfi bridge, so they have very little use left. The only potential use
|
||||
is with af_lavrresample (going to be deprecated, --audio-resample-... set
|
||||
its defaults), and various hw deinterlacing filters (like vf_vavpp), for
|
||||
which you will have to stop using --deinterlace=yes, and instead use the
|
||||
vf toggle commands and the filter enable/disable flag to customize it.
|
||||
- deprecate --af=lavrresample. Use the ``--audio-resample-...`` options to
|
||||
customize resampling, or the libavfilter ``--af=aresample`` filter.
|
||||
- add --osd-on-seek
|
||||
- remove outfmt sub-parameter from "format" video filter (no replacement)
|
||||
- some behavior changes in the video filter chain, including:
|
||||
- before, using an incompatible filter with hwdec would disable hwdec;
|
||||
now it disables the filter at runtime instead
|
||||
- inserting an incompatible filter with hwdec at runtime would refuse
|
||||
to insert the filter; now it will add it successfully, but disables
|
||||
the filter slightly later
|
||||
- some behavior changes in the audio filter chain, including:
|
||||
- a manually inserted lavrresample filter is not necessarily used for
|
||||
sample format conversion anymore, so it's pretty useless
|
||||
- changing playback speed will not respect --af-defaults anymore
|
||||
- having libavfilter based filters after the scaletempo or rubberband
|
||||
filters is not supported anymore, and may desync if playback speed is
|
||||
changed (libavfilter does not support the metadata for playback speed)
|
||||
- the lavcac3enc filter does not auto detach itself anymore; instead it
|
||||
passes through the data after converting it to the sample rate and
|
||||
channel configuration the ac3 encoder expects; also, if the audio
|
||||
format changes midstream in a way that causes the filter to switch
|
||||
between PCM and AC3 output, the audio output won't be reconfigured,
|
||||
and audio playback will fail due to libswresample being unable to
|
||||
convert between PCM and AC3 (Note: the responsible developer didn't
|
||||
give a shit. Later changes might have improved or worsened this.)
|
||||
- inserting a filter that changes the output sample format will not
|
||||
reconfigure the AO - you need to run an additional "ao-reload"
|
||||
command to force this if you want that
|
||||
- using "strong" gapless audio (--gapless-audio=yes) can fail if the
|
||||
audio formats are not convertible (such as switching between PCM and
|
||||
AC3 passthrough)
|
||||
- if filters do not pass through PTS values correctly, A/V sync can
|
||||
result over time. Some libavfilter filters are known to be affected by
|
||||
this, such as af_loudnorm, which can desync over time, depending on
|
||||
how the audio track was muxed (af_lavfi's fix-pts suboption can help).
|
||||
- remove out-format sub-parameter from "format" audio filter (no replacement)
|
||||
- --lavfi-complex now requires uniquely named filter pads. In addition,
|
||||
unconnected filter pads are not allowed anymore (that means every filter
|
||||
pad must be connected either to another filter, or to a video/audio track
|
||||
or video/audio output). If they are disconnected at runtime, the stream
|
||||
will probably stall.
|
||||
- rename --vo=opengl-cb to --vo=libmpv (goes in hand with the opengl-cb
|
||||
API deprecation, see client-api-changes.rst)
|
||||
- deprecate the OpenGL cocoa backend, option choice --gpu-context=cocoa
|
||||
when used with --gpu-api=opengl (use --vo=libmpv)
|
||||
- make --deinterlace=yes always deinterlace, instead of trying to check
|
||||
certain unreliable video metadata. Also flip the defaults of all builtin
|
||||
HW deinterlace filters to always deinterlace.
|
||||
- change vf_vavpp default to use the best deinterlace algorithm by default
|
||||
- remove a compatibility hack that allowed CLI aliases to be set as property
|
||||
(such as "sub-file"), deprecated in mpv 0.26.0
|
||||
- deprecate the old command based hook API, and introduce a proper C API
|
||||
(the high level Lua API for this does not change)
|
||||
- rename the the lua-settings/ config directory to script-opts/
|
||||
- the way the player waits for scripts getting loaded changes slightly. Now
|
||||
scripts are loaded in parallel, and block the player from continuing
|
||||
playback only in the player initialization phase. It could change again in
|
||||
the future. (This kind of waiting was always a feature to prevent that
|
||||
playback is started while scripts are only half-loaded.)
|
||||
- deprecate --ovoffset, --oaoffset, --ovfirst, --oafirst
|
||||
- remove the following encoding options: --ocopyts (now the default, old
|
||||
timestamp handling is gone), --oneverdrop (now default), --oharddup (you
|
||||
need to use --vf=fps=VALUE), --ofps, --oautofps, --omaxfps
|
||||
- remove --video-stereo-mode. This option was broken out of laziness, and
|
||||
nobody wants to fix it. Automatic 3D down-conversion to 2D is also broken,
|
||||
although you can just insert the stereo3d filter manually. The obscurity
|
||||
of 3D content doesn't justify such an option anyway.
|
||||
- change cycle-values command to use the current value, instead of an
|
||||
internal counter that remembered the current position.
|
||||
- remove deprecated ao/vo auto profiles. Consider using scripts like
|
||||
auto-profiles.lua instead.
|
||||
- --[c]scale-[w]param[1|2] and --tone-mapping-param now accept "default",
|
||||
and if set to that value, reading them as property will also return
|
||||
"default", instead of float nan as in previous versions
|
||||
--- mpv 0.28.0 ---
|
||||
- rename --hwdec=mediacodec option to mediacodec-copy, to reflect
|
||||
conventions followed by other hardware video decoding APIs
|
||||
- drop previously deprecated --heartbeat-cmd and --heartbeat--interval
|
||||
options
|
||||
- rename --vo=opengl to --vo=gpu
|
||||
- rename --opengl-backend to --gpu-context
|
||||
- rename --opengl-shaders to --glsl-shaders
|
||||
- rename --opengl-shader-cache-dir to --gpu-shader-cache-dir
|
||||
- rename --opengl-tex-pad-x/y to --gpu-tex-pad-x/y
|
||||
- rename --opengl-fbo-format to --fbo-format
|
||||
- rename --opengl-gamma to --gamma-factor
|
||||
- rename --opengl-debug to --gpu-debug
|
||||
- rename --opengl-sw to --gpu-sw
|
||||
- rename --opengl-vsync-fences to --swapchain-depth, and the interpretation
|
||||
slightly changed. Now defaults to 3.
|
||||
- rename the built-in profile `opengl-hq` to `gpu-hq`
|
||||
- the semantics of --opengl-es=yes are slightly changed -> now requires GLES
|
||||
- remove the (deprecated) alias --gpu-context=drm-egl
|
||||
- remove the (deprecated) --vo=opengl-hq
|
||||
- remove --opengl-es=force2 (use --opengl-es=yes --opengl-restrict=300)
|
||||
- the --msg-level option now affects --log-file
|
||||
- drop "audio-out-detected-device" property - this was unavailable on all
|
||||
audio output drivers for quite a while (coreaudio used to provide it)
|
||||
- deprecate --videotoolbox-format (use --hwdec-image-format, which affects
|
||||
most other hwaccels)
|
||||
- remove deprecated --demuxer-max-packets
|
||||
- remove most of the deprecated audio and video filters
|
||||
- remove the deprecated --balance option/property
|
||||
- rename the --opengl-hwdec-interop option to --gpu-hwdec-interop, and
|
||||
change some of its semantics: extend it take the strings "auto" and
|
||||
"all". "all" loads all backends. "auto" behaves like "all" for
|
||||
vo_opengl_cb, while on vo_gpu it loads nothing, but allows on demand
|
||||
loading by the decoder. The empty string as option value behaves like
|
||||
"auto". Old --hwdec values do not work anymore.
|
||||
This option is hereby declared as unstable and may change any time - its
|
||||
old use is deprecated, and it has very little use outside of debugging
|
||||
now.
|
||||
- change the --hwdec option from a choice to a plain string (affects
|
||||
introspection of the option/property), also affects some properties
|
||||
- rename --hwdec=rpi to --hwdec=mmal, same for the -copy variant (no
|
||||
backwards compatibility)
|
||||
- deprecate the --ff-aid, --ff-vid, --ff-sid options and properties (there is
|
||||
no replacement, but you can manually query the track property and use the
|
||||
"ff-index" field to find the mpv track ID to imitate this behavior)
|
||||
- rename --no-ometadata to --no-ocopy-metadata
|
||||
--- mpv 0.27.0 ---
|
||||
- drop previously deprecated --field-dominance option
|
||||
- drop previously deprecated "osd" command
|
||||
|
||||
+211
-51
@@ -5,8 +5,7 @@ Audio filters allow you to modify the audio stream and its properties. The
|
||||
syntax is:
|
||||
|
||||
``--af=...``
|
||||
Setup a chain of audio filters. See ``--vf`` (`VIDEO FILTERS`_) for the
|
||||
full syntax.
|
||||
Setup a chain of audio filters. See ``--vf`` for the syntax.
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -24,6 +23,37 @@ See ``--vf`` group of options for info on how ``--af-defaults``, ``--af-add``,
|
||||
|
||||
Available filters are:
|
||||
|
||||
``lavrresample[=option1:option2:...]``
|
||||
This filter uses libavresample (or libswresample, depending on the build)
|
||||
to change sample rate, sample format, or channel layout of the audio stream.
|
||||
This filter is automatically enabled if the audio output does not support
|
||||
the audio configuration of the file being played.
|
||||
|
||||
It supports only the following sample formats: u8, s16, s32, float.
|
||||
|
||||
``filter-size=<length>``
|
||||
Length of the filter with respect to the lower sampling rate. (default:
|
||||
16)
|
||||
``phase-shift=<count>``
|
||||
Log2 of the number of polyphase entries. (..., 10->1024, 11->2048,
|
||||
12->4096, ...) (default: 10->1024)
|
||||
``cutoff=<cutoff>``
|
||||
Cutoff frequency (0.0-1.0), default set depending upon filter length.
|
||||
``linear``
|
||||
If set then filters will be linearly interpolated between polyphase
|
||||
entries. (default: no)
|
||||
``no-detach``
|
||||
Do not detach if input and output audio format/rate/channels match.
|
||||
(If you just want to set defaults for this filter that will be used
|
||||
even by automatically inserted lavrresample instances, you should
|
||||
prefer setting them with ``--af-defaults=lavrresample:...``.)
|
||||
``normalize=<yes|no|auto>``
|
||||
Whether to normalize when remixing channel layouts (default: auto).
|
||||
``auto`` uses the value set by ``--audio-normalize-downmix``.
|
||||
``o=<string>``
|
||||
Set AVOptions on the SwrContext or AVAudioResampleContext. These should
|
||||
be documented by FFmpeg or Libav.
|
||||
|
||||
``lavcac3enc[=options]``
|
||||
Encode multi-channel audio to AC-3 at runtime using libavcodec. Supports
|
||||
16-bit native-endian input format, maximum 6 channels. The output is
|
||||
@@ -61,7 +91,82 @@ Available filters are:
|
||||
Select the libavcodec encoder used. Currently, this should be an AC-3
|
||||
encoder, and using another codec will fail horribly.
|
||||
|
||||
``format=format:srate:channels:out-srate:out-channels``
|
||||
``equalizer=g1:g2:g3:...:g10``
|
||||
10 octave band graphic equalizer, implemented using 10 IIR band-pass
|
||||
filters. This means that it works regardless of what type of audio is
|
||||
being played back. The center frequencies for the 10 bands are:
|
||||
|
||||
=== ==========
|
||||
No. frequency
|
||||
=== ==========
|
||||
0 31.25 Hz
|
||||
1 62.50 Hz
|
||||
2 125.00 Hz
|
||||
3 250.00 Hz
|
||||
4 500.00 Hz
|
||||
5 1.00 kHz
|
||||
6 2.00 kHz
|
||||
7 4.00 kHz
|
||||
8 8.00 kHz
|
||||
9 16.00 kHz
|
||||
=== ==========
|
||||
|
||||
If the sample rate of the sound being played is lower than the center
|
||||
frequency for a frequency band, then that band will be disabled. A known
|
||||
bug with this filter is that the characteristics for the uppermost band
|
||||
are not completely symmetric if the sample rate is close to the center
|
||||
frequency of that band. This problem can be worked around by upsampling
|
||||
the sound using a resampling filter before it reaches this filter.
|
||||
|
||||
``<g1>:<g2>:<g3>:...:<g10>``
|
||||
floating point numbers representing the gain in dB for each frequency
|
||||
band (-12-12)
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
``mpv --af=equalizer=11:11:10:5:0:-12:0:5:12:12 media.avi``
|
||||
Would amplify the sound in the upper and lower frequency region
|
||||
while canceling it almost completely around 1 kHz.
|
||||
|
||||
``channels=nch[:routes]``
|
||||
Can be used for adding, removing, routing and copying audio channels. If
|
||||
only ``<nch>`` is given, the default routing is used. It works as follows:
|
||||
If the number of output channels is greater than the number of input
|
||||
channels, empty channels are inserted (except when mixing from mono to
|
||||
stereo; then the mono channel is duplicated). If the number of output
|
||||
channels is less than the number of input channels, the exceeding
|
||||
channels are truncated.
|
||||
|
||||
``<nch>``
|
||||
number of output channels (1-8)
|
||||
``<routes>``
|
||||
List of ``,`` separated routes, in the form ``from1-to1,from2-to2,...``.
|
||||
Each pair defines where to route each channel. There can be at most
|
||||
8 routes. Without this argument, the default routing is used. Since
|
||||
``,`` is also used to separate filters, you must quote this argument
|
||||
with ``[...]`` or similar.
|
||||
|
||||
.. admonition:: Examples
|
||||
|
||||
``mpv --af=channels=4:[0-1,1-0,2-2,3-3] media.avi``
|
||||
Would change the number of channels to 4 and set up 4 routes that
|
||||
swap channel 0 and channel 1 and leave channel 2 and 3 intact.
|
||||
Observe that if media containing two channels were played back,
|
||||
channels 2 and 3 would contain silence but 0 and 1 would still be
|
||||
swapped.
|
||||
|
||||
``mpv --af=channels=6:[0-0,0-1,0-2,0-3] media.avi``
|
||||
Would change the number of channels to 6 and set up 4 routes that
|
||||
copy channel 0 to channels 0 to 3. Channel 4 and 5 will contain
|
||||
silence.
|
||||
|
||||
.. note::
|
||||
|
||||
You should probably not use this filter. If you want to change the
|
||||
output channel layout, try the ``format`` filter, which can make mpv
|
||||
automatically up- and downmix standard channel layouts.
|
||||
|
||||
``format=format:srate:channels:out-format:out-srate:out-channels``
|
||||
Does not do any format conversion itself. Rather, it may cause the
|
||||
filter system to insert necessary conversion filters before or after this
|
||||
filter if needed. It is primarily useful for controlling the audio format
|
||||
@@ -90,6 +195,8 @@ Available filters are:
|
||||
Force mixing to a specific channel layout. See ``--audio-channels`` option
|
||||
for possible values.
|
||||
|
||||
``<out-format>``
|
||||
|
||||
``<out-srate>``
|
||||
|
||||
``<out-channels>``
|
||||
@@ -98,6 +205,107 @@ Available filters are:
|
||||
used to do conversion itself, unlike this one which lets the filter system
|
||||
handle the conversion.
|
||||
|
||||
``volume[=<volumedb>[:...]]``
|
||||
Implements software volume control. Use this filter with caution since it
|
||||
can reduce the signal to noise ratio of the sound. In most cases it is
|
||||
best to use the *Master* volume control of your sound card or the volume
|
||||
knob on your amplifier.
|
||||
|
||||
*WARNING*: This filter is deprecated. Use the top-level options like
|
||||
``--volume`` and ``--replaygain...`` instead.
|
||||
|
||||
*NOTE*: This filter is not reentrant and can therefore only be enabled
|
||||
once for every audio stream.
|
||||
|
||||
``<volumedb>``
|
||||
Sets the desired gain in dB for all channels in the stream from -200 dB
|
||||
to +60 dB, where -200 dB mutes the sound completely and +60 dB equals a
|
||||
gain of 1000 (default: 0).
|
||||
``replaygain-track``
|
||||
Adjust volume gain according to the track-gain replaygain value stored
|
||||
in the file metadata.
|
||||
``replaygain-album``
|
||||
Like replaygain-track, but using the album-gain value instead.
|
||||
``replaygain-preamp``
|
||||
Pre-amplification gain in dB to apply to the selected replaygain gain
|
||||
(default: 0).
|
||||
``replaygain-clip=yes|no``
|
||||
Prevent clipping caused by replaygain by automatically lowering the
|
||||
gain (default). Use ``replaygain-clip=no`` to disable this.
|
||||
``replaygain-fallback``
|
||||
Gain in dB to apply if the file has no replay gain tags. This option
|
||||
is always applied if the replaygain logic is somehow inactive. If this
|
||||
is applied, no other replaygain options are applied.
|
||||
``softclip``
|
||||
Turns soft clipping on. Soft-clipping can make the
|
||||
sound more smooth if very high volume levels are used. Enable this
|
||||
option if the dynamic range of the loudspeakers is very low.
|
||||
|
||||
*WARNING*: This feature creates distortion and should be considered a
|
||||
last resort.
|
||||
``s16``
|
||||
Force S16 sample format if set. Lower quality, but might be faster
|
||||
in some situations.
|
||||
``detach``
|
||||
Remove the filter if the volume is not changed at audio filter config
|
||||
time. Useful with replaygain: if the current file has no replaygain
|
||||
tags, then the filter will be removed if this option is enabled.
|
||||
(If ``--softvol=yes`` is used and the player volume controls are used
|
||||
during playback, a different volume filter will be inserted.)
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
``mpv --af=volume=10.1 media.avi``
|
||||
Would amplify the sound by 10.1 dB and hard-clip if the sound level
|
||||
is too high.
|
||||
|
||||
``pan=n:[<matrix>]``
|
||||
Mixes channels arbitrarily. Basically a combination of the volume and the
|
||||
channels filter that can be used to down-mix many channels to only a few,
|
||||
e.g. stereo to mono, or vary the "width" of the center speaker in a
|
||||
surround sound system. This filter is hard to use, and will require some
|
||||
tinkering before the desired result is obtained. The number of options for
|
||||
this filter depends on the number of output channels. An example how to
|
||||
downmix a six-channel file to two channels with this filter can be found
|
||||
in the examples section near the end.
|
||||
|
||||
``<n>``
|
||||
Number of output channels (1-8).
|
||||
``<matrix>``
|
||||
A list of values ``[L00,L01,L02,...,L10,L11,L12,...,Ln0,Ln1,Ln2,...]``,
|
||||
where each element ``Lij`` means how much of input channel i is mixed
|
||||
into output channel j (range 0-1). So in principle you first have n
|
||||
numbers saying what to do with the first input channel, then n numbers
|
||||
that act on the second input channel etc. If you do not specify any
|
||||
numbers for some input channels, 0 is assumed.
|
||||
Note that the values are separated by ``,``, which is already used
|
||||
by the option parser to separate filters. This is why you must quote
|
||||
the value list with ``[...]`` or similar.
|
||||
|
||||
.. admonition:: Examples
|
||||
|
||||
``mpv --af=pan=1:[0.5,0.5] media.avi``
|
||||
Would downmix from stereo to mono.
|
||||
|
||||
``mpv --af=pan=3:[1,0,0.5,0,1,0.5] media.avi``
|
||||
Would give 3 channel output leaving channels 0 and 1 intact, and mix
|
||||
channels 0 and 1 into output channel 2 (which could be sent to a
|
||||
subwoofer for example).
|
||||
|
||||
.. note::
|
||||
|
||||
If you just want to force remixing to a certain output channel layout,
|
||||
it is easier to use the ``format`` filter. For example,
|
||||
``mpv '--af=format=channels=5.1' '--audio-channels=5.1'`` would always force
|
||||
remixing audio to 5.1 and output it like this.
|
||||
|
||||
This filter supports the following ``af-command`` commands:
|
||||
|
||||
``set-matrix``
|
||||
Set the ``<matrix>`` argument dynamically. This can be used to change
|
||||
the mixing matrix at runtime, without reinitializing the entire filter
|
||||
chain.
|
||||
|
||||
``scaletempo[=option1:option2:...]``
|
||||
Scales audio tempo without altering pitch, optionally synced to playback
|
||||
speed (default).
|
||||
@@ -161,28 +369,6 @@ Available filters are:
|
||||
Would play media at 1.2x normal speed, with audio at normal pitch.
|
||||
Changing playback speed would change pitch, leaving audio tempo at
|
||||
1.2x.
|
||||
|
||||
``scaletempo2[=option1:option2:...]``
|
||||
Scales audio tempo without altering pitch.
|
||||
The algorithm is ported from chromium and uses the
|
||||
Waveform Similarity Overlap-and-add (WSOLA) method.
|
||||
It seems to achieve a higher audio quality than scaletempo and rubberband.
|
||||
|
||||
By default, the ``search-interval`` and ``window-size`` parameters
|
||||
have the same values as in chromium.
|
||||
|
||||
``min-speed=<speed>``
|
||||
Mute audio if the playback speed is below ``<speed>``. (default: 0.25)
|
||||
|
||||
``max-speed=<speed>``
|
||||
Mute audio if the playback speed is above ``<speed>``
|
||||
and ``<speed> != 0``. (default: 4.0)
|
||||
|
||||
``search-interval=<amount>``
|
||||
Length in milliseconds to search for best overlap position. (default: 30)
|
||||
|
||||
``window-size=<amount>``
|
||||
Length in milliseconds of the overlap-and-add window. (default: 20)
|
||||
|
||||
``rubberband``
|
||||
High quality pitch correction with librubberband. This can be used in place
|
||||
@@ -209,12 +395,6 @@ Available filters are:
|
||||
change the playback pitch at runtime. Note that speed is controlled
|
||||
using the standard ``speed`` property, not ``af-command``.
|
||||
|
||||
``multiply-pitch <factor>``
|
||||
Multiply the current value of ``<pitch-scale>`` dynamically. For
|
||||
example: 0.5 to go down by an octave, 1.5 to go up by a perfect fifth.
|
||||
If you want to go up or down by semi-tones, use 1.059463094352953 and
|
||||
0.9438743126816935
|
||||
|
||||
``lavfi=graph``
|
||||
Filter audio using FFmpeg's libavfilter.
|
||||
|
||||
@@ -229,23 +409,3 @@ Available filters are:
|
||||
|
||||
``o=<string>``
|
||||
AVOptions.
|
||||
|
||||
``fix-pts=<yes|no>``
|
||||
Determine PTS based on sample count (default: no). If this is enabled,
|
||||
the player won't rely on libavfilter passing through PTS accurately.
|
||||
Instead, it pass a sample count as PTS to libavfilter, and compute the
|
||||
PTS used by mpv based on that and the input PTS. This helps with filters
|
||||
which output a recomputed PTS instead of the original PTS (including
|
||||
filters which require the PTS to start at 0). mpv normally expects
|
||||
filters to not touch the PTS (or only to the extent of changing frame
|
||||
boundaries), so this is not the default, but it will be needed to use
|
||||
broken filters. In practice, these broken filters will either cause slow
|
||||
A/V desync over time (with some files), or break playback completely if
|
||||
you seek or start playback from the middle of a file.
|
||||
|
||||
``drop``
|
||||
This filter drops or repeats audio frames to adapt to playback speed. It
|
||||
always operates on full audio frames, because it was made to handle SPDIF
|
||||
(compressed audio passthrough). This is used automatically if the
|
||||
``--video-sync=display-adrop`` option is used. Do not use this filter (or
|
||||
the given option); they are extremely low quality.
|
||||
+39
-24
@@ -14,7 +14,8 @@ in the list.
|
||||
|
||||
See ``--ao=help`` for a list of compiled-in audio output drivers. The
|
||||
driver ``--ao=alsa`` is preferred. ``--ao=pulse`` is preferred on systems
|
||||
where PulseAudio is used.
|
||||
where PulseAudio is used. On BSD systems, ``--ao=oss`` or ``--ao=sndio``
|
||||
may work (the latter being experimental).
|
||||
|
||||
Available audio output drivers are:
|
||||
|
||||
@@ -26,7 +27,7 @@ Available audio output drivers are:
|
||||
.. warning::
|
||||
|
||||
To get multichannel/surround audio, use ``--audio-channels=auto``. The
|
||||
default for this option is ``auto-safe``, which makes this audio output
|
||||
default for this option is ``auto-safe``, which makes this audio otuput
|
||||
explicitly reject multichannel output, as there is no way to detect
|
||||
whether a certain channel layout is actually supported.
|
||||
|
||||
@@ -35,6 +36,18 @@ Available audio output drivers are:
|
||||
with automatic upmixing with shared access, so playing stereo
|
||||
and multichannel audio at the same time will work as expected.
|
||||
|
||||
``oss``
|
||||
OSS audio output driver
|
||||
|
||||
The following global options are supported by this audio output:
|
||||
|
||||
``--oss-mixer-device``
|
||||
Sets the audio mixer device (default: ``/dev/mixer``).
|
||||
``--oss-mixer-channel``
|
||||
Sets the audio mixer channel (default: ``pcm``). Other valid values
|
||||
include **vol, pcm, line**. For a complete list of options look for
|
||||
``SOUND_DEVICE_NAMES`` in ``/usr/include/linux/soundcard.h``.
|
||||
|
||||
``jack``
|
||||
JACK (Jack Audio Connection Kit) audio output driver.
|
||||
|
||||
@@ -97,21 +110,10 @@ Available audio output drivers are:
|
||||
exclusive mode (bypasses the sound server).
|
||||
|
||||
``openal``
|
||||
OpenAL audio output driver. This is broken and does not work.
|
||||
Experimental OpenAL audio output driver
|
||||
|
||||
``--openal-num-buffers=<2-128>``
|
||||
Specify the number of audio buffers to use. Lower values are better for
|
||||
lower CPU usage. Default: 4.
|
||||
|
||||
``--openal-num-samples=<256-32768>``
|
||||
Specify the number of complete samples to use for each buffer. Higher
|
||||
values are better for lower CPU usage. Default: 8192.
|
||||
|
||||
``--openal-direct-channels=<yes|no>``
|
||||
Enable OpenAL Soft's direct channel extension when available to avoid
|
||||
tinting the sound with ambisonics or HRTF.
|
||||
Channels are dropped when when they are not available as downmixing
|
||||
will be disabled. Default: no.
|
||||
.. note:: This driver is not very useful. Playing multi-channel audio with
|
||||
it is slow.
|
||||
|
||||
``pulse``
|
||||
PulseAudio audio output driver
|
||||
@@ -126,7 +128,7 @@ Available audio output drivers are:
|
||||
Set the audio buffer size in milliseconds. A higher value buffers
|
||||
more data, and has a lower probability of buffer underruns. A smaller
|
||||
value makes the audio stream react faster, e.g. to playback speed
|
||||
changes.
|
||||
changes. Default: 250.
|
||||
|
||||
``--pulse-latency-hacks=<yes|no>``
|
||||
Enable hacks to workaround PulseAudio timing bugs (default: no). If
|
||||
@@ -139,10 +141,6 @@ Available audio output drivers are:
|
||||
If you have stuttering video when using pulse, try to enable this
|
||||
option. (Or try to update PulseAudio.)
|
||||
|
||||
``--pulse-allow-suspended=<yes|no>``
|
||||
Allow mpv to use PulseAudio even if the sink is suspended (default: no).
|
||||
Can be useful if PulseAudio is running as a bridge to jack and mpv has its sink-input set to the one jack is using.
|
||||
|
||||
``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
|
||||
@@ -160,6 +158,10 @@ Available audio output drivers are:
|
||||
obtained exact buffer size. A value of 0 selects the sound system
|
||||
default.
|
||||
|
||||
``--sdl-bufcnt=<count>``
|
||||
Sets the number of extra audio buffers in mpv. Usually needs not be
|
||||
changed.
|
||||
|
||||
``null``
|
||||
Produces no audio output but maintains video playback speed. You can use
|
||||
``--ao=null --ao-null-untimed`` for benchmarking.
|
||||
@@ -196,9 +198,6 @@ Available audio output drivers are:
|
||||
If not empty, this is a ``,`` separated list of channel layouts the
|
||||
AO allows. This can be used to test channel layout selection.
|
||||
|
||||
``--ao-null-format``
|
||||
Force the audio output format the AO will accept. If unset accepts any.
|
||||
|
||||
``pcm``
|
||||
Raw PCM/WAVE file writer audio output
|
||||
|
||||
@@ -216,5 +215,21 @@ Available audio output drivers are:
|
||||
``no-waveheader`` option - with ``waveheader`` it's broken, because
|
||||
it will write a WAVE header every time the file is opened.
|
||||
|
||||
``rsound``
|
||||
Audio output to an RSound daemon. Use ``--audio-device=rsound/<hostname>``
|
||||
to set the host name (with ``<hostname>`` replaced, without the ``< >``).
|
||||
|
||||
.. note:: Completely useless, unless you intend to run RSound. Not to be
|
||||
confused with RoarAudio, which is something completely
|
||||
different.
|
||||
|
||||
``sndio``
|
||||
Audio output to the OpenBSD sndio sound system
|
||||
|
||||
.. note:: Experimental. There are known bugs and issues.
|
||||
|
||||
(Note: only supports mono, stereo, 4.0, 5.1 and 7.1 channel
|
||||
layouts.)
|
||||
|
||||
``wasapi``
|
||||
Audio output to the Windows Audio Session API.
|
||||
@@ -8,12 +8,10 @@ There is no real changelog, but you can look at the following things:
|
||||
|
||||
https://github.com/mpv-player/mpv/releases
|
||||
* The git log, which is the "real" changelog
|
||||
* The file https://github.com/mpv-player/mpv/blob/master/DOCS/interface-changes.rst
|
||||
documents changes to the command and user interface, such as options and
|
||||
properties. (It usually documents breaking changes only, additions and
|
||||
enhancements are often not listed.)
|
||||
* C API changes are listed in
|
||||
https://github.com/mpv-player/mpv/blob/master/DOCS/client-api-changes.rst
|
||||
* The files ``client-api-changes.rst`` and ``interface-changes.rst`` in the
|
||||
``DOCS`` sub directoryon the git repository, which document API and user
|
||||
interface changes (the latter usually documents breaking changes only, rather
|
||||
than additions).
|
||||
* The file ``mplayer-changes.rst`` in the ``DOCS`` sub directory on the git
|
||||
repository, which used to be in place of this section. It documents some
|
||||
changes that happened since mplayer2 forked off MPlayer. (Not updated
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
CONSOLE
|
||||
=======
|
||||
|
||||
The console is a REPL for mpv input commands. It is displayed on the video
|
||||
window. It also shows log messages. It can be disabled entirely using the
|
||||
``--load-osd-console=no`` option.
|
||||
|
||||
Keybindings
|
||||
-----------
|
||||
|
||||
\`
|
||||
Show the console.
|
||||
|
||||
ESC
|
||||
Hide the console.
|
||||
|
||||
ENTER
|
||||
Run the typed command.
|
||||
|
||||
Shift+ENTER
|
||||
Type a literal newline character.
|
||||
|
||||
Ctrl+LEFT and Ctrl+RIGHT
|
||||
Move cursor to previous/next word.
|
||||
|
||||
UP and DOWN
|
||||
Navigate command history.
|
||||
|
||||
PGUP
|
||||
Go to the first command in the history.
|
||||
|
||||
PGDN
|
||||
Stop navigating command history.
|
||||
|
||||
INSERT
|
||||
Toggle insert mode.
|
||||
|
||||
Shift+INSERT
|
||||
Paste text (uses the primary selection on X11).
|
||||
|
||||
TAB
|
||||
Complete the command or property name at the cursor.
|
||||
|
||||
Ctrl+C
|
||||
Clear current line.
|
||||
|
||||
Ctrl+K
|
||||
Delete text from the cursor to the end of the line.
|
||||
|
||||
Ctrl+L
|
||||
Clear all log messages from the console.
|
||||
|
||||
Ctrl+U
|
||||
Delete text from the cursor to the beginning of the line.
|
||||
|
||||
Ctrl+V
|
||||
Paste text (uses the clipboard on X11).
|
||||
|
||||
Ctrl+W
|
||||
Delete text from the cursor to the beginning of the current word.
|
||||
|
||||
Commands
|
||||
--------
|
||||
|
||||
``script-message-to console type <text> [<cursor_pos>]``
|
||||
Show the console and pre-fill it with the provided text, optionally
|
||||
specifying the initial cursor position as a positive integer starting from
|
||||
1.
|
||||
|
||||
.. admonition:: Example for input.conf
|
||||
|
||||
``% script-message-to console type "seek absolute-percent" 6``
|
||||
|
||||
Known issues
|
||||
------------
|
||||
|
||||
- Pasting text is slow on Windows
|
||||
- Non-ASCII keyboard input has restrictions
|
||||
- The cursor keys move between Unicode code-points, not grapheme clusters
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
This script can be customized through a config file ``script-opts/console.conf``
|
||||
placed in mpv's user directory and through the ``--script-opts`` command-line
|
||||
option. The configuration syntax is described in `ON SCREEN CONTROLLER`_.
|
||||
|
||||
Key bindings can be changed in a standard way, see for example stats.lua
|
||||
documentation.
|
||||
|
||||
Configurable Options
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
``scale``
|
||||
Default: 1
|
||||
|
||||
All drawing is scaled by this value, including the text borders and the
|
||||
cursor.
|
||||
|
||||
If the VO backend in use has HiDPI scale reporting implemented, the option
|
||||
value is scaled with the reported HiDPI scale.
|
||||
|
||||
``font``
|
||||
Default: unset (picks a hardcoded font depending on detected platform)
|
||||
|
||||
Set the font used for the REPL and the console. This probably doesn't
|
||||
have to be a monospaced font.
|
||||
|
||||
``font_size``
|
||||
Default: 16
|
||||
|
||||
Set the font size used for the REPL and the console. This will be
|
||||
multiplied by "scale."
|
||||
+84
-52
@@ -8,29 +8,64 @@ You can encode files from one format/codec to another using this facility.
|
||||
|
||||
``--of=<format>``
|
||||
Specifies the output format (overrides autodetection by the file name
|
||||
extension of the file specified by ``-o``). See ``--of=help`` for a full
|
||||
list of supported formats.
|
||||
extension of the file specified by ``-o``). This can be a comma separated
|
||||
list of possible formats to try. See ``--of=help`` for a full list of
|
||||
supported formats.
|
||||
|
||||
``--ofopts=<options>``
|
||||
Specifies the output format options for libavformat.
|
||||
See ``--ofopts=help`` for a full list of supported options.
|
||||
|
||||
This is a key/value list option. See `List Options`_ for details.
|
||||
Options are managed in lists. There are a few commands to manage the
|
||||
options list.
|
||||
|
||||
``--ofopts-add=<option>``
|
||||
Appends the option given as an argument to the options list. (Passing
|
||||
multiple options is currently still possible, but deprecated.)
|
||||
``--ofopts-add=<options1[,options2,...]>``
|
||||
Appends the options given as arguments to the options list.
|
||||
|
||||
``--ofopts=""``
|
||||
``--ofopts-pre=<options1[,options2,...]>``
|
||||
Prepends the options given as arguments to the options list.
|
||||
|
||||
``--ofopts-del=<index1[,index2,...]>``
|
||||
Deletes the options at the given indexes. Index numbers start at 0,
|
||||
negative numbers address the end of the list (-1 is the last).
|
||||
|
||||
``--ofopts-clr``
|
||||
Completely empties the options list.
|
||||
|
||||
``--ofps=<float value>``
|
||||
Specifies the output format time base (default: 24000). Low values like 25
|
||||
limit video fps by dropping frames.
|
||||
|
||||
``--oautofps``
|
||||
Sets the output format time base to the guessed frame rate of the input
|
||||
video (simulates MEncoder behavior, useful for AVI; may cause frame drops).
|
||||
Note that not all codecs and not all formats support VFR encoding, and some
|
||||
which do have bugs when a target bitrate is specified - use ``--ofps`` or
|
||||
``--oautofps`` to force CFR encoding in these cases.
|
||||
|
||||
``--omaxfps=<float value>``
|
||||
Specifies the minimum distance of adjacent frames (default: 0, which means
|
||||
unset). Content of lower frame rate is not readjusted to this frame rate;
|
||||
content of higher frame rate is decimated to this frame rate.
|
||||
|
||||
``--oharddup``
|
||||
If set, the frame rate given by ``--ofps`` is attained not by skipping time
|
||||
codes, but by duplicating frames (constant frame rate mode).
|
||||
|
||||
``--oneverdrop``
|
||||
If set, frames are never dropped. Instead, time codes of video are
|
||||
readjusted to always increase. This may cause AV desync, though; to work
|
||||
around this, use a high-fps time base using ``--ofps`` and absolutely
|
||||
avoid ``--oautofps``.
|
||||
|
||||
``--oac=<codec>``
|
||||
Specifies the output audio codec. See ``--oac=help`` for a full list of
|
||||
supported codecs.
|
||||
Specifies the output audio codec. This can be a comma separated list of
|
||||
possible codecs to try. See ``--oac=help`` for a full list of supported
|
||||
codecs.
|
||||
|
||||
``--oaoffset=<value>``
|
||||
Shifts audio data by the given time (in seconds) by adding/removing
|
||||
samples at the start. Deprecated.
|
||||
samples at the start.
|
||||
|
||||
``--oacopts=<options>``
|
||||
Specifies the output audio codec options for libavcodec.
|
||||
@@ -41,28 +76,36 @@ You can encode files from one format/codec to another using this facility.
|
||||
"``--oac=libmp3lame --oacopts=b=128000``"
|
||||
selects 128 kbps MP3 encoding.
|
||||
|
||||
This is a key/value list option. See `List Options`_ for details.
|
||||
Options are managed in lists. There are a few commands to manage the
|
||||
options list.
|
||||
|
||||
``--oacopts-add=<option>``
|
||||
Appends the option given as an argument to the options list. (Passing
|
||||
multiple options is currently still possible, but deprecated.)
|
||||
``--oacopts-add=<options1[,options2,...]>``
|
||||
Appends the options given as arguments to the options list.
|
||||
|
||||
``--oacopts=""``
|
||||
``--oacopts-pre=<options1[,options2,...]>``
|
||||
Prepends the options given as arguments to the options list.
|
||||
|
||||
``--oacopts-del=<index1[,index2,...]>``
|
||||
Deletes the options at the given indexes. Index numbers start at 0,
|
||||
negative numbers address the end of the list (-1 is the last).
|
||||
|
||||
``--oacopts-clr``
|
||||
Completely empties the options list.
|
||||
|
||||
``--oafirst``
|
||||
Force the audio stream to become the first stream in the output.
|
||||
By default, the order is unspecified. Deprecated.
|
||||
By default, the order is unspecified.
|
||||
|
||||
``--ovc=<codec>``
|
||||
Specifies the output video codec. See ``--ovc=help`` for a full list of
|
||||
supported codecs.
|
||||
Specifies the output video codec. This can be a comma separated list of
|
||||
possible codecs to try. See ``--ovc=help`` for a full list of supported
|
||||
codecs.
|
||||
|
||||
``--ovoffset=<value>``
|
||||
Shifts video data by the given time (in seconds) by shifting the pts
|
||||
values. Deprecated.
|
||||
values.
|
||||
|
||||
``--ovcopts=<options>``
|
||||
``--ovcopts <options>``
|
||||
Specifies the output video codec options for libavcodec.
|
||||
See --ovcopts=help for a full list of supported options.
|
||||
|
||||
@@ -74,18 +117,32 @@ You can encode files from one format/codec to another using this facility.
|
||||
``"--ovc=libx264 --ovcopts=crf=23"``
|
||||
selects VBR quality factor 23 for H.264 encoding.
|
||||
|
||||
This is a key/value list option. See `List Options`_ for details.
|
||||
Options are managed in lists. There are a few commands to manage the
|
||||
options list.
|
||||
|
||||
``--ovcopts-add=<option>``
|
||||
Appends the option given as an argument to the options list. (Passing
|
||||
multiple options is currently still possible, but deprecated.)
|
||||
``--ovcopts-add=<options1[,options2,...]>``
|
||||
Appends the options given as arguments to the options list.
|
||||
|
||||
``--ovcopts=""``
|
||||
``--ovcopts-pre=<options1[,options2,...]>``
|
||||
Prepends the options given as arguments to the options list.
|
||||
|
||||
``--ovcopts-del=<index1[,index2,...]>``
|
||||
Deletes the options at the given indexes. Index numbers start at 0,
|
||||
negative numbers address the end of the list (-1 is the last).
|
||||
|
||||
``--ovcopts-clr``
|
||||
Completely empties the options list.
|
||||
|
||||
``--ovfirst``
|
||||
Force the video stream to become the first stream in the output.
|
||||
By default, the order is unspecified. Deprecated.
|
||||
By default, the order is unspecified.
|
||||
|
||||
``--ocopyts``
|
||||
Copies input pts to the output video (not supported by some output
|
||||
container formats, e.g. AVI). Discontinuities are still fixed.
|
||||
By default, audio pts are set to playback time and video pts are
|
||||
synchronized to match audio pts, as some output formats do not support
|
||||
anything else.
|
||||
|
||||
``--orawts``
|
||||
Copies input pts to the output video (not supported by some output
|
||||
@@ -93,31 +150,6 @@ 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``
|
||||
``--no-ometadata``
|
||||
Turns off copying of metadata from input files to output files when
|
||||
encoding (which is enabled by default).
|
||||
|
||||
``--oset-metadata=<metadata-tag[,metadata-tag,...]>``
|
||||
Specifies metadata to include in the output file.
|
||||
Supported keys vary between output formats. For example, Matroska (MKV) and
|
||||
FLAC allow almost arbitrary keys, while support in MP4 and MP3 is more
|
||||
limited.
|
||||
|
||||
This is a key/value list option. See `List Options`_ for details.
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
"``--oset-metadata=title="Output title",comment="Another tag"``"
|
||||
adds a title and a comment to the output file.
|
||||
|
||||
``--oremove-metadata=<metadata-tag[,metadata-tag,...]>``
|
||||
Specifies metadata to exclude from the output file when copying from the
|
||||
input file.
|
||||
|
||||
This is a string list option. See `List Options`_ for details.
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
"``--oremove-metadata=comment,genre``"
|
||||
excludes copying of the the comment and genre tags to the output
|
||||
file.
|
||||
+399
-1562
File diff suppressed because it is too large.
Load diff
+10
-129
@@ -71,18 +71,9 @@ To be able to simultaneously read and write from the IPC pipe, like on Linux,
|
||||
it's necessary to write an external program that uses overlapped file I/O (or
|
||||
some wrapper like .NET's NamedPipeClientStream.)
|
||||
|
||||
You can open the pipe in PuTTY as "serial" device. This is not very
|
||||
comfortable, but gives a way to test interactively without having to write code.
|
||||
|
||||
Protocol
|
||||
--------
|
||||
|
||||
The protocol uses UTF-8-only JSON as defined by RFC-8259. Unlike standard JSON,
|
||||
"\u" escape sequences are not allowed to construct surrogate pairs. To avoid
|
||||
getting conflicts, encode all text characters including and above codepoint
|
||||
U+0020 as UTF-8. mpv might output broken UTF-8 in corner cases (see "UTF-8"
|
||||
section below).
|
||||
|
||||
Clients can execute commands on the player by sending JSON messages of the
|
||||
following form:
|
||||
|
||||
@@ -117,10 +108,7 @@ Because events can occur at any time, it may be difficult at times to determine
|
||||
which response goes with which command. Commands may optionally include a
|
||||
``request_id`` which, if provided in the command request, will be copied
|
||||
verbatim into the response. mpv does not intrepret the ``request_id`` in any
|
||||
way; it is solely for the use of the requester. The only requirement is that
|
||||
the ``request_id`` field must be an integer (a number without fractional parts
|
||||
in the range ``-2^63..2^63-1``). Using other types is deprecated and will
|
||||
currently show a warning. In the future, this will raise an error.
|
||||
way; it is solely for the use of the requester.
|
||||
|
||||
For example, this request:
|
||||
|
||||
@@ -134,8 +122,6 @@ Would generate this response:
|
||||
|
||||
{ "error": "success", "data": 1.468135, "request_id": 100 }
|
||||
|
||||
If you don't specify a ``request_id``, command replies will set it to 0.
|
||||
|
||||
All commands, replies, and events are separated from each other with a line
|
||||
break character (``\n``).
|
||||
|
||||
@@ -147,72 +133,6 @@ with ``#`` and empty lines are ignored.
|
||||
Currently, embedded 0 bytes terminate the current line, but you should not
|
||||
rely on this.
|
||||
|
||||
Data flow
|
||||
---------
|
||||
|
||||
Currently, the mpv-side IPC implementation does not service the socket while a
|
||||
command is executed and the reply is written. It is for example not possible
|
||||
that other events, that happened during the execution of the command, are
|
||||
written to the socket before the reply is written.
|
||||
|
||||
This might change in the future. The only guarantee is that replies to IPC
|
||||
messages are sent in sequence.
|
||||
|
||||
Also, since socket I/O is inherently asynchronous, it is possible that you read
|
||||
unrelated event messages from the socket, before you read the reply to the
|
||||
previous command you sent. In this case, these events were queued by the mpv
|
||||
side before it read and started processing your command message.
|
||||
|
||||
If the mpv-side IPC implementation switches away from blocking writes and
|
||||
blocking command execution, it may attempt to send events at any time.
|
||||
|
||||
You can also use asynchronous commands, which can return in any order, and
|
||||
which do not block IPC protocol interaction at all while the command is
|
||||
executed in the background.
|
||||
|
||||
Asynchronous commands
|
||||
---------------------
|
||||
|
||||
Command can be run asynchronously. This behaves exactly as with normal command
|
||||
execution, except that execution is not blocking. Other commands can be sent
|
||||
while it's executing, and command completion can be arbitrarily reordered.
|
||||
|
||||
The ``async`` field controls this. If present, it must be a boolean. If missing,
|
||||
``false`` is assumed.
|
||||
|
||||
For example, this initiates an asynchronous command:
|
||||
|
||||
::
|
||||
|
||||
{ "command": ["screenshot"], "request_id": 123, "async": true }
|
||||
|
||||
And this is the completion:
|
||||
|
||||
::
|
||||
|
||||
{"request_id":123,"error":"success","data":null}
|
||||
|
||||
By design, you will not get a confirmation that the command was started. If a
|
||||
command is long running, sending the message will lead to any reply until much
|
||||
later when the command finishes.
|
||||
|
||||
Some commands execute synchronously, but these will behave like asynchronous
|
||||
commands that finished execution immediately.
|
||||
|
||||
Cancellation of asynchronous commands is available in the libmpv API, but has
|
||||
not yet been implemented in the IPC protocol.
|
||||
|
||||
Commands with named arguments
|
||||
-----------------------------
|
||||
|
||||
If the ``command`` field is a JSON object, named arguments are expected. This
|
||||
is described in the C API ``mpv_command_node()`` documentation (the
|
||||
``MPV_FORMAT_NODE_MAP`` case). In some cases, this may make commands more
|
||||
readable, while some obscure commands basically require using named arguments.
|
||||
|
||||
Currently, only "proper" commands (as listed by `List of Input Commands`_)
|
||||
support named arguments.
|
||||
|
||||
Commands
|
||||
--------
|
||||
|
||||
@@ -260,7 +180,14 @@ extra commands can also be used as part of the protocol:
|
||||
{ "error": "success" }
|
||||
|
||||
``set_property_string``
|
||||
Alias for ``set_property``. Both commands accept native values and strings.
|
||||
Like ``set_property``, but the argument value must be passed as string.
|
||||
|
||||
Example:
|
||||
|
||||
::
|
||||
|
||||
{ "command": ["set_property_string", "pause", "yes"] }
|
||||
{ "error": "success" }
|
||||
|
||||
``observe_property``
|
||||
Watch a property for changes. If the given property is changed, then an
|
||||
@@ -338,50 +265,4 @@ sometimes sends invalid JSON. If that is a problem for the client application's
|
||||
parser, it should filter the raw data for invalid UTF-8 sequences and perform
|
||||
the desired replacement, before feeding the data to its JSON parser.
|
||||
|
||||
mpv will not attempt to construct invalid UTF-8 with broken "\u" escape
|
||||
sequences. This includes surrogate pairs.
|
||||
|
||||
JSON extensions
|
||||
---------------
|
||||
|
||||
The following non-standard extensions are supported:
|
||||
|
||||
- a list or object item can have a trailing ","
|
||||
- object syntax accepts "=" in addition of ":"
|
||||
- object keys can be unquoted, if they start with a character in "A-Za-z\_"
|
||||
and contain only characters in "A-Za-z0-9\_"
|
||||
- byte escapes with "\xAB" are allowed (with AB being a 2 digit hex number)
|
||||
|
||||
Example:
|
||||
|
||||
::
|
||||
|
||||
{ objkey = "value\x0A" }
|
||||
|
||||
Is equivalent to:
|
||||
|
||||
::
|
||||
|
||||
{ "objkey": "value\n" }
|
||||
|
||||
Alternative ways of starting clients
|
||||
------------------------------------
|
||||
|
||||
You can create an anonymous IPC connection without having to set
|
||||
``--input-ipc-server``. This is achieved through a mpv pseudo scripting backend
|
||||
that starts processes.
|
||||
|
||||
You can put ``.run`` file extension in the mpv scripts directory in its config
|
||||
directory (see the `FILES`_ section for details), or load them through other
|
||||
means (see `Script location`_). These scripts are simply executed with the OS
|
||||
native mechanism (as if you ran them in the shell). They must have a proper
|
||||
shebang and have the executable bit set.
|
||||
|
||||
When executed, a socket (the IPC connection) is passed to them through file
|
||||
descriptor inheritance. The file descriptor is indicated as the special command
|
||||
line argument ``--mpv-ipc-fd=N``, where ``N`` is the numeric file descriptor.
|
||||
|
||||
The rest is the same as with a normal ``--input-ipc-server`` IPC connection. mpv
|
||||
does not attempt to observe or other interact with the started script process.
|
||||
|
||||
This does not work in Windows yet.
|
||||
mpv will not attempt to construct invalid UTF-8 with broken escape sequences.
|
||||
+25
-77
@@ -27,16 +27,16 @@ 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 at the modules ``mp``, ``mp.utils`` and ``mp.msg`` are available to
|
||||
JavaScript with identical APIs - including running commands, getting/setting
|
||||
properties, registering events/key-bindings/property-changes/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`` and ``mp.msg`` are preloaded, and
|
||||
you can use e.g. ``var cwd = mp.utils.getcwd();`` without prior setup.
|
||||
``mp.options`` is currently not implemented, but ``mp.get_opt(...)`` is.
|
||||
|
||||
Errors are slightly different. Where the Lua APIs return ``nil`` for error,
|
||||
the JavaScript ones return ``undefined``. Where Lua returns ``something, error``
|
||||
@@ -67,6 +67,10 @@ Unsupported Lua APIs and their JS alternatives
|
||||
|
||||
``mp.add_periodic_timer(seconds, fn)`` JS: ``id = setInterval(fn, ms)``
|
||||
|
||||
``mp.register_idle(fn)`` JS: ``id = setTimeout(fn)``
|
||||
|
||||
``mp.unregister_idle(fn)`` JS: ``clearTimeout(id)``
|
||||
|
||||
``utils.parse_json(str [, trail])`` JS: ``JSON.parse(str)``
|
||||
|
||||
``utils.format_json(v)`` JS: ``JSON.stringify(v)``
|
||||
@@ -83,12 +87,14 @@ Unsupported Lua APIs and their JS alternatives
|
||||
|
||||
``mp.dispatch_events([allow_wait])`` see event loop below.
|
||||
|
||||
``mp.options`` module is not implemented currently for JS.
|
||||
|
||||
Scripting APIs - identical to Lua
|
||||
---------------------------------
|
||||
|
||||
(LE) - Last-Error, indicates that ``mp.last_error()`` can be used after the
|
||||
call to test for success (empty string) or failure (non empty reason string).
|
||||
Where the Lua APIs use ``nil`` to indicate error, JS APIs use ``undefined``.
|
||||
Otherwise, where the Lua APIs return ``nil`` on error, JS returns ``undefined``.
|
||||
|
||||
``mp.command(string)`` (LE)
|
||||
|
||||
@@ -96,11 +102,6 @@ Where the Lua APIs use ``nil`` to indicate error, JS APIs use ``undefined``.
|
||||
|
||||
``mp.command_native(table [,def])`` (LE)
|
||||
|
||||
``id = mp.command_native_async(table [,fn])`` (LE) Notes: ``id`` is true-thy on
|
||||
success, ``fn`` is called always a-sync, ``error`` is empty string on success.
|
||||
|
||||
``mp.abort_async_command(id)``
|
||||
|
||||
``mp.get_property(name [,def])`` (LE)
|
||||
|
||||
``mp.get_property_osd(name [,def])`` (LE)
|
||||
@@ -139,26 +140,16 @@ success, ``fn`` is called always a-sync, ``error`` is empty string on success.
|
||||
|
||||
``mp.get_script_name()``
|
||||
|
||||
``mp.get_script_directory()``
|
||||
|
||||
``mp.osd_message(text [,duration])``
|
||||
|
||||
``mp.get_wakeup_pipe()``
|
||||
|
||||
``mp.register_idle(fn)``
|
||||
|
||||
``mp.unregister_idle(fn)``
|
||||
|
||||
``mp.enable_messages(level)``
|
||||
|
||||
``mp.register_script_message(name, fn)``
|
||||
|
||||
``mp.unregister_script_message(name)``
|
||||
|
||||
``mp.create_osd_overlay(format)``
|
||||
|
||||
``mp.get_osd_size()`` (returned object has properties: width, height, aspect)
|
||||
|
||||
``mp.msg.log(level, ...)``
|
||||
|
||||
``mp.msg.fatal(...)``
|
||||
@@ -173,14 +164,10 @@ success, ``fn`` is called always a-sync, ``error`` is empty string on success.
|
||||
|
||||
``mp.msg.debug(...)``
|
||||
|
||||
``mp.msg.trace(...)``
|
||||
|
||||
``mp.utils.getcwd()`` (LE)
|
||||
|
||||
``mp.utils.readdir(path [, filter])`` (LE)
|
||||
|
||||
``mp.utils.file_info(path)`` (LE)
|
||||
|
||||
``mp.utils.split_path(path)``
|
||||
|
||||
``mp.utils.join_path(p1, p2)``
|
||||
@@ -189,14 +176,7 @@ success, ``fn`` is called always a-sync, ``error`` is empty string on success.
|
||||
|
||||
``mp.utils.subprocess_detached(t)``
|
||||
|
||||
``mp.utils.get_env_list()``
|
||||
|
||||
``mp.utils.getpid()`` (LE)
|
||||
|
||||
``mp.add_hook(type, priority, fn(hook))``
|
||||
|
||||
``mp.options.read_options(obj [, identifier [, on_update]])`` (types:
|
||||
string/boolean/number)
|
||||
``mp.add_hook(type, priority, fn)``
|
||||
|
||||
Additional utilities
|
||||
--------------------
|
||||
@@ -242,16 +222,13 @@ Note: ``read_file`` and ``write_file`` throw on errors, allow text content only.
|
||||
|
||||
``exit()`` (global)
|
||||
Make the script exit at the end of the current event loop iteration.
|
||||
Note: please remove added key bindings before calling ``exit()``.
|
||||
Note: please reomve added key bindings before calling ``exit()``.
|
||||
|
||||
``mp.utils.compile_js(fname, content_str)``
|
||||
Compiles the JS code ``content_str`` as file name ``fname`` (without loading
|
||||
anything from the filesystem), and returns it as a function. Very similar
|
||||
to a ``Function`` constructor, but shows at stack traces as ``fname``.
|
||||
|
||||
``mp.module_paths``
|
||||
Global modules search paths array for the ``require`` function (see below).
|
||||
|
||||
Timers (global)
|
||||
---------------
|
||||
|
||||
@@ -284,17 +261,15 @@ event loop iteration or at a later event loop iteration. This is true also for
|
||||
intervals - which also never call back twice at the same event loop iteration.
|
||||
|
||||
Additionally, timers are processed after the event queue is empty, so it's valid
|
||||
to use ``setTimeout(fn)`` as a one-time idle observer.
|
||||
to use ``setTimeout(fn)`` instead of Lua's ``mp.register_idle(fn)``.
|
||||
|
||||
CommonJS modules and ``require(id)``
|
||||
------------------------------------
|
||||
|
||||
CommonJS Modules are a standard system where scripts can export common functions
|
||||
for use by other scripts. Specifically, a module is a script which adds
|
||||
properties (functions, etc) to its pre-existing ``exports`` object, which
|
||||
another script can access with ``require(module-id)``. This runs the module and
|
||||
returns its ``exports`` object. Further calls to ``require`` for the same module
|
||||
will return its cached ``exports`` object without running the module again.
|
||||
for use by other scripts. A module is a script which adds properties (functions,
|
||||
etc) to its invisible ``exports`` object, which another script can access by
|
||||
loading it with ``require(module-id)`` - which returns that ``exports`` object.
|
||||
|
||||
Modules and ``require`` are supported, standard compliant, and generally similar
|
||||
to node.js. However, most node.js modules won't run due to missing modules such
|
||||
@@ -304,20 +279,13 @@ do work. In general, this is for mpv modules and not a node.js replacement.
|
||||
A ``.js`` file extension is always added to ``id``, e.g. ``require("./foo")``
|
||||
will load the file ``./foo.js`` and return its ``exports`` object.
|
||||
|
||||
An id which starts with ``./`` or ``../`` is relative to the script or module
|
||||
which ``require`` it. Otherwise it's considered a top-level id (CommonJS term).
|
||||
An id is relative (to the script which ``require``'d it) if it starts with
|
||||
``./`` or ``../``. Otherwise, it's considered a "top-level id" (CommonJS term).
|
||||
|
||||
Top-level id is evaluated as absolute filesystem path if possible, e.g. ``/x/y``
|
||||
or ``~/x``. Otherwise it's considered a global module id and searched according
|
||||
to ``mp.module_paths`` in normal array order, e.g. ``require("x")`` tries to
|
||||
load ``x.js`` at one of the array paths, and id ``foo/x`` tries to load ``x.js``
|
||||
inside dir ``foo`` at one of the paths.
|
||||
|
||||
The ``mp.module_paths`` array is empty by default except for scripts which are
|
||||
loaded as a directory where it contains one item - ``<directory>/modules/`` .
|
||||
The array may be updated from a script (or using custom init - see below) which
|
||||
will affect future calls to ``require`` for global module id's which are not
|
||||
already loaded/cached.
|
||||
Top level id is evaluated as absolute filesystem path if possible (e.g. ``/x/y``
|
||||
or ``~/x``). Otherwise, it's searched at ``scripts/modules.js/`` in mpv config
|
||||
dirs - in normal config search order. E.g. ``require("x")`` is searched as file
|
||||
``x.js`` at those dirs, and id ``foo/x`` is searched as file ``foo/x.js``.
|
||||
|
||||
No ``global`` variable, but a module's ``this`` at its top lexical scope is the
|
||||
global object - also in strict mode. If you have a module which needs ``global``
|
||||
@@ -325,15 +293,6 @@ as the global object, you could do ``this.global = this;`` before ``require``.
|
||||
|
||||
Functions and variables declared at a module don't pollute the global object.
|
||||
|
||||
Custom initialization
|
||||
---------------------
|
||||
|
||||
After mpv initializes the JavaScript environment for a script but before it
|
||||
loads the script - it tries to run the file ``.init.js`` at the root of the mpv
|
||||
configuration directory. Code at this file can update the environment further
|
||||
for all scripts. E.g. if it contains ``mp.module_paths.push("/foo")`` then
|
||||
``require`` at all scripts will search global module id's also at ``/foo``.
|
||||
|
||||
The event loop
|
||||
--------------
|
||||
|
||||
@@ -355,10 +314,6 @@ also print every event which mpv sends to your script:
|
||||
wait = 0;
|
||||
} else {
|
||||
wait = mp.process_timers() / 1000;
|
||||
if (wait != 0) {
|
||||
mp.notify_idle_observers();
|
||||
wait = mp.peek_timers_wait() / 1000;
|
||||
}
|
||||
}
|
||||
} while (mp.keep_running);
|
||||
}
|
||||
@@ -378,12 +333,5 @@ if there are such (event handlers, property observers, script messages, etc).
|
||||
and returns the duration in ms till the next due timer (possibly 0), or -1 if
|
||||
there are no pending timers. Must not be called recursively.
|
||||
|
||||
``mp.notify_idle_observers()`` calls back the idle observers, which we do when
|
||||
we're about to sleep (wait != 0), but the observers may add timers or take
|
||||
non-negligible duration to complete, so we re-calculate ``wait`` afterwards.
|
||||
|
||||
``mp.peek_timers_wait()`` returns the same values as ``mp.process_timers()``
|
||||
but without doing anything. Invalid result if called from a timer callback.
|
||||
|
||||
Note: ``exit()`` is also registered for the ``shutdown`` event, and its
|
||||
implementation is a simple ``mp.keep_running = false``.
|
||||
+174
-259
@@ -1,7 +1,12 @@
|
||||
LUA SCRIPTING
|
||||
=============
|
||||
|
||||
mpv can load Lua scripts. (See `Script location`_.)
|
||||
mpv can load Lua scripts. Scripts passed to the ``--script`` option, or found in
|
||||
the ``scripts`` subdirectory of the mpv configuration directory (usually
|
||||
``~/.config/mpv/scripts/``) will be loaded on program start. mpv also appends the
|
||||
``scripts`` subdirectory to the end of Lua's path so you can import scripts from
|
||||
there too. Since it's added to the end, don't name scripts you want to import
|
||||
the same as Lua libraries because they will be overshadowed by them.
|
||||
|
||||
mpv provides the built-in module ``mp``, which contains functions to send
|
||||
commands to the mpv core and to retrieve information about playback state, user
|
||||
@@ -25,49 +30,6 @@ A script which leaves fullscreen mode when the player is paused:
|
||||
mp.observe_property("pause", "bool", on_pause_change)
|
||||
|
||||
|
||||
Script location
|
||||
---------------
|
||||
|
||||
Scripts can be passed to the ``--script`` option, and are automatically loaded
|
||||
from the ``scripts`` subdirectory of the mpv configuration directory (usually
|
||||
``~/.config/mpv/scripts/``).
|
||||
|
||||
A script can be a single file. The file extension is used to select the
|
||||
scripting backend to use for it. For Lua, it is ``.lua``. If the extension is
|
||||
not recognized, an error is printed. (If an error happens, the extension is
|
||||
either mistyped, or the backend was not compiled into your mpv binary.)
|
||||
|
||||
Entries with ``.disable`` extension are always ignored.
|
||||
|
||||
If a script is a directory (either if a directory is passed to ``--script``,
|
||||
or any sub-directories in the script directory, such as for example
|
||||
``~/.config/mpv/scripts/something/``), then the directory represents a single
|
||||
script. The player will try to load a file named ``main.x``, where ``x`` is
|
||||
replaced with the file extension. For example, if ``main.lua`` exists, it is
|
||||
loaded with the Lua scripting backend.
|
||||
|
||||
You must not put any other files or directories that start with ``main.`` into
|
||||
the script's top level directory. If the script directory contains for example
|
||||
both ``main.lua`` and ``main.js``, only one of them will be loaded (and which
|
||||
one depends on mpv internals that may change any time). Likewise, if there is
|
||||
for example ``main.foo``, your script will break as soon as mpv adds a backend
|
||||
that uses the ``.foo`` file extension.
|
||||
|
||||
mpv also appends the top level directory of the script to the start of Lua's
|
||||
package path so you can import scripts from there too. Be aware that this will
|
||||
shadow Lua libraries that use the same package path. (Single file scripts do not
|
||||
include mpv specific directory the Lua package path. This was silently changed
|
||||
in mpv 0.32.0.)
|
||||
|
||||
Using a script directory is the recommended way to package a script that
|
||||
consists of multiple source files, or requires other files (you can use
|
||||
``mp.get_script_directory()`` to get the location and e.g. load data files).
|
||||
|
||||
Making a script a git repository, basically a repository which contains a
|
||||
``main.lua``` file in the root directory, makes scripts easily updateable
|
||||
(without the dangers of auto-updates). Another suggestion is to use git
|
||||
submodules to share common files or libraries.
|
||||
|
||||
Details on the script initialization and lifecycle
|
||||
--------------------------------------------------
|
||||
|
||||
@@ -77,10 +39,7 @@ option. Some scripts are loaded internally (like ``--osc``). Each script runs in
|
||||
its own thread. Your script is first run "as is", and once that is done, the event loop
|
||||
is entered. This event loop will dispatch events received by mpv and call your
|
||||
own event handlers which you have registered with ``mp.register_event``, or
|
||||
timers added with ``mp.add_timeout`` or similar. Note that since the
|
||||
script starts execution concurrently with player initialization, some properties
|
||||
may not be populated with meaningful values until the relevant subsystems have
|
||||
initialized.
|
||||
timers added with ``mp.add_timeout`` or similar.
|
||||
|
||||
When the player quits, all scripts will be asked to terminate. This happens via
|
||||
a ``shutdown`` event, which by default will make the event loop return. If your
|
||||
@@ -101,10 +60,7 @@ the player will more or less hang until the script returns from the main chunk
|
||||
(and ``mp_event_loop`` is called), or the script calls ``mp_event_loop`` or
|
||||
``mp.dispatch_events`` directly. This is done to make it possible for a script
|
||||
to fully setup event handlers etc. before playback actually starts. In older
|
||||
mpv versions, this happened asynchronously. With mpv 0.29.0, this changes
|
||||
slightly, and it merely waits for scripts to be loaded in this manner before
|
||||
starting playback as part of the player initialization phase. Scripts run though
|
||||
initialization in parallel. This might change again.
|
||||
mpv versions, this happened asynchronously.
|
||||
|
||||
mp functions
|
||||
------------
|
||||
@@ -147,50 +103,12 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
``mp.command_native(table [,def])``
|
||||
Similar to ``mp.commandv``, but pass the argument list as table. This has
|
||||
the advantage that in at least some cases, arguments can be passed as
|
||||
native types. It also allows you to use named argument.
|
||||
|
||||
If the table is an array, each array item is like an argument in
|
||||
``mp.commandv()`` (but can be a native type instead of a string).
|
||||
|
||||
If the table contains string keys, it's interpreted as command with named
|
||||
arguments. This requires at least an entry with the key ``name`` to be
|
||||
present, which must be a string, and contains the command name. The special
|
||||
entry ``_flags`` is optional, and if present, must be an array of
|
||||
`Input Command Prefixes`_ to apply. All other entries are interpreted as
|
||||
arguments.
|
||||
native types.
|
||||
|
||||
Returns a result table on success (usually empty), or ``def, error`` on
|
||||
error. ``def`` is the second parameter provided to the function, and is
|
||||
nil if it's missing.
|
||||
|
||||
``mp.command_native_async(table [,fn])``
|
||||
Like ``mp.command_native()``, but the command is ran asynchronously (as far
|
||||
as possible), and upon completion, fn is called. fn has three arguments:
|
||||
``fn(success, result, error)``:
|
||||
|
||||
``success``
|
||||
Always a Boolean and is true if the command was successful,
|
||||
otherwise false.
|
||||
|
||||
``result``
|
||||
The result value (can be nil) in case of success, nil otherwise (as
|
||||
returned by ``mp.command_native()``).
|
||||
|
||||
``error``
|
||||
The error string in case of an error, nil otherwise.
|
||||
|
||||
Returns a table with undefined contents, which can be used as argument for
|
||||
``mp.abort_async_command``.
|
||||
|
||||
If starting the command failed for some reason, ``nil, error`` is returned,
|
||||
and ``fn`` is called indicating failure, using the same error value.
|
||||
|
||||
``mp.abort_async_command(t)``
|
||||
Abort a ``mp.command_native_async`` call. The argument is the return value
|
||||
of that command (which starts asynchronous execution of the command).
|
||||
Whether this works and how long it takes depends on the command and the
|
||||
situation. The abort call itself is asynchronous. Does not return anything.
|
||||
|
||||
``mp.get_property(name [,def])``
|
||||
Return the value of the given property as string. These are the same
|
||||
properties as used in input.conf. See `Properties`_ for a list of
|
||||
@@ -285,9 +203,7 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
an example). The name should be unique across other bindings in the same
|
||||
script - if not, the previous binding with the same name will be
|
||||
overwritten. You can omit the name, in which case a random name is generated
|
||||
internally. (Omitting works as follows: either pass ``nil`` for ``name``,
|
||||
or pass the ``fn`` argument in place of the name. The latter is not
|
||||
recommended and is handled for compatibility only.)
|
||||
internally.
|
||||
|
||||
The last argument is used for optional flags. This is a table, which can
|
||||
have the following entries:
|
||||
@@ -298,25 +214,11 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
``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):
|
||||
|
||||
``event``
|
||||
Set to one of the strings ``down``, ``repeat``, ``up`` or
|
||||
``press`` (the latter if key up/down can't be tracked).
|
||||
|
||||
``is_mouse``
|
||||
Boolean Whether the event was caused by a mouse button.
|
||||
|
||||
``key_name``
|
||||
The name of they key that triggered this, or ``nil`` if
|
||||
invoked artificially. If the key name is unknown, it's an
|
||||
empty string.
|
||||
|
||||
``key_text``
|
||||
Text if triggered by a text key, otherwise ``nil``. See
|
||||
description of ``script-binding`` command for details (this
|
||||
field is equivalent to the 5th argument).
|
||||
argument being a table. This table has an ``event`` entry, which
|
||||
is set to one of the strings ``down``, ``repeat``, ``up`` or
|
||||
``press`` (the latter if key up/down can't be tracked). It further
|
||||
has an ``is_mouse`` entry, which tells whether the event was caused
|
||||
by a mouse button.
|
||||
|
||||
Internally, key bindings are dispatched via the ``script-message-to`` or
|
||||
``script-binding`` input commands and ``mp.register_script_message``.
|
||||
@@ -404,21 +306,13 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
of times in a row, only the last change triggers the change function. (The
|
||||
exact behavior depends on timing and other things.)
|
||||
|
||||
If a property is unavailable, or on error, the value argument to ``fn`` is
|
||||
``nil``. (The ``observe_property()`` call always succeeds, even if a
|
||||
property does not exist.)
|
||||
|
||||
In some cases the function is not called even if the property changes.
|
||||
This depends on the property, and it's a valid feature request to ask for
|
||||
better update handling of a specific property.
|
||||
Whether this can happen depends on the 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.
|
||||
|
||||
You always get an initial change notification. This is meant to initialize
|
||||
the user's state to the current value of the property.
|
||||
|
||||
``mp.unobserve_property(fn)``
|
||||
Undo ``mp.observe_property(..., fn)``. This removes all property handlers
|
||||
that are equal to the ``fn`` parameter. This uses normal Lua ``==``
|
||||
@@ -473,7 +367,7 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
(``true``). This value is used when the timer expires (but before
|
||||
the timer callback function fn is run).
|
||||
|
||||
Note that these are methods, and you have to call them using ``:`` instead
|
||||
Note that these are method, and you have to call them using ``:`` instead
|
||||
of ``.`` (Refer to http://www.lua.org/manual/5.2/manual.html#3.4.9 .)
|
||||
|
||||
Example:
|
||||
@@ -506,11 +400,6 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
|
||||
The script ``/path/to/fooscript.lua`` becomes ``fooscript``.
|
||||
|
||||
``mp.get_script_directory()``
|
||||
Return the directory if this is a script packaged as directory (see
|
||||
`Script location`_ for a description). Return nothing if this is a single
|
||||
file script.
|
||||
|
||||
``mp.osd_message(text [,duration])``
|
||||
Show an OSD message on the screen. ``duration`` is in seconds, and is
|
||||
optional (uses ``--osd-duration`` by default).
|
||||
@@ -535,7 +424,7 @@ are useful only in special situations.
|
||||
|
||||
``mp.get_wakeup_pipe()``
|
||||
Calls ``mpv_get_wakeup_pipe()`` and returns the read end of the wakeup
|
||||
pipe. This is deprecated, but still works. (See ``client.h`` for details.)
|
||||
pipe. (See ``client.h`` for details.)
|
||||
|
||||
``mp.get_next_timeout()``
|
||||
Return the relative time in seconds when the next timer (``mp.add_timeout``
|
||||
@@ -587,42 +476,6 @@ are useful only in special situations.
|
||||
Undo a previous registration with ``mp.register_script_message``. Does
|
||||
nothing if the ``name`` wasn't registered.
|
||||
|
||||
``mp.create_osd_overlay(format)``
|
||||
Create an OSD overlay. This is a very thin wrapper around the ``osd-overlay``
|
||||
command. The function returns a table, which mostly contains fields that
|
||||
will be passed to ``osd-overlay``. The ``format`` parameter is used to
|
||||
initialize the ``format`` field. The ``data`` field contains the text to
|
||||
be used as overlay. For details, see the ``osd-overlay`` command.
|
||||
|
||||
In addition, it provides the following methods:
|
||||
|
||||
``update()``
|
||||
Commit the OSD overlay to the screen, or in other words, run the
|
||||
``osd-overlay`` command with the current fields of the overlay table.
|
||||
Returns the result of the ``osd-overlay`` command itself.
|
||||
|
||||
``remove()``
|
||||
Remove the overlay from the screen. A ``update()`` call will add it
|
||||
again.
|
||||
|
||||
Example:
|
||||
|
||||
::
|
||||
|
||||
ov = mp.create_osd_overlay("ass-events")
|
||||
ov.data = "{\\an5}{\\b1}hello world!"
|
||||
ov:update()
|
||||
|
||||
The advantage of using this wrapper (as opposed to running ``osd-overlay``
|
||||
directly) is that the ``id`` field is allocated automatically.
|
||||
|
||||
``mp.get_osd_size()``
|
||||
Returns a tuple of ``osd_width, osd_height, osd_par``. The first two give
|
||||
the size of the OSD in pixels (for video ouputs like ``--vo=xv``, this may
|
||||
be "scaled" pixels). The third is the display pixel aspect ratio.
|
||||
|
||||
May return invalid/nonsense values if OSD is not initialized yet.
|
||||
|
||||
mp.msg functions
|
||||
----------------
|
||||
|
||||
@@ -631,17 +484,16 @@ with ``require 'mp.msg'``.
|
||||
|
||||
``msg.log(level, ...)``
|
||||
The level parameter is the message priority. It's a string and one of
|
||||
``fatal``, ``error``, ``warn``, ``info``, ``v``, ``debug``, ``trace``. The
|
||||
user's settings will determine which of these messages will be
|
||||
visible. Normally, all messages are visible, except ``v``, ``debug`` and
|
||||
``trace``.
|
||||
``fatal``, ``error``, ``warn``, ``info``, ``v``, ``debug``. The user's
|
||||
settings will determine which of these messages will be visible. Normally,
|
||||
all messages are visible, except ``v`` and ``debug``.
|
||||
|
||||
The parameters after that are all converted to strings. Spaces are inserted
|
||||
to separate multiple parameters.
|
||||
|
||||
You don't need to add newlines.
|
||||
|
||||
``msg.fatal(...)``, ``msg.error(...)``, ``msg.warn(...)``, ``msg.info(...)``, ``msg.verbose(...)``, ``msg.debug(...)``, ``msg.trace(...)``
|
||||
``msg.fatal(...)``, ``msg.error(...)``, ``msg.warn(...)``, ``msg.info(...)``, ``msg.verbose(...)``, ``msg.debug(...)``
|
||||
All of these are shortcuts and equivalent to the corresponding
|
||||
``msg.log(level, ...)`` call.
|
||||
|
||||
@@ -653,27 +505,14 @@ command-line. All you have to do is to supply a table with default options to
|
||||
the read_options function. The function will overwrite the default values
|
||||
with values found in the config-file and the command-line (in that order).
|
||||
|
||||
``options.read_options(table [, identifier [, on_update]])``
|
||||
``options.read_options(table [, identifier])``
|
||||
A ``table`` with key-value pairs. The type of the default values is
|
||||
important for converting the values read from the config file or
|
||||
command-line back. Do not use ``nil`` as a default value!
|
||||
|
||||
The ``identifier`` is used to identify the config-file and the command-line
|
||||
options. These needs to unique to avoid collisions with other scripts.
|
||||
Defaults to ``mp.get_script_name()`` if the parameter is ``nil`` or missing.
|
||||
|
||||
The ``on_update`` parameter enables run-time updates of all matching option
|
||||
values via the ``script-opts`` option/property. If any of the matching
|
||||
options changes, the values in the ``table`` (which was originally passed to
|
||||
the function) are changed, and ``on_update(list)`` is called. ``list`` is
|
||||
a table where each updated option has a ``list[option_name] = true`` entry.
|
||||
There is no initial ``on_update()`` call. This never re-reads the config file.
|
||||
``script-opts`` is always applied on the original config file, ignoring
|
||||
previous ``script-opts`` values (for example, if an option is removed from
|
||||
``script-opts`` at runtime, the option will have the value in the config
|
||||
file). ``table`` entries are only written for option values whose values
|
||||
effectively change (this is important if the script changes ``table``
|
||||
entries independently).
|
||||
Defaults to ``mp.get_script_name()``.
|
||||
|
||||
|
||||
Example implementation::
|
||||
@@ -688,7 +527,7 @@ Example implementation::
|
||||
print(options.optionA)
|
||||
|
||||
|
||||
The config file will be stored in ``script-opts/identifier.conf`` in mpv's user
|
||||
The config file will be stored in ``lua-settings/identifier.conf`` in mpv's user
|
||||
folder. Comment lines can be started with # and stray spaces are not removed.
|
||||
Boolean values will be represented with yes/no.
|
||||
|
||||
@@ -749,34 +588,6 @@ strictly part of the guaranteed API.
|
||||
|
||||
On error, ``nil, error`` is returned.
|
||||
|
||||
``utils.file_info(path)``
|
||||
Stats the given path for information and returns a table with the
|
||||
following entries:
|
||||
|
||||
``mode``
|
||||
protection bits (on Windows, always 755 (octal) for directories
|
||||
and 644 (octal) for files)
|
||||
``size``
|
||||
size in bytes
|
||||
``atime``
|
||||
time of last access
|
||||
``mtime``
|
||||
time of last modification
|
||||
``ctime``
|
||||
time of last metadata change (Linux) / time of creation (Windows)
|
||||
``is_file``
|
||||
Whether ``path`` is a regular file (boolean)
|
||||
``is_dir``
|
||||
Whether ``path`` is a directory (boolean)
|
||||
|
||||
``mode`` and ``size`` are integers.
|
||||
Timestamps (``atime``, ``mtime`` and ``ctime``) are integer seconds since
|
||||
the Unix epoch (Unix time).
|
||||
The booleans ``is_file`` and ``is_dir`` are provided as a convenience;
|
||||
they can be and are derived from ``mode``.
|
||||
|
||||
On error (eg. path does not exist), ``nil, error`` is returned.
|
||||
|
||||
``utils.split_path(path)``
|
||||
Split a path into directory component and filename component, and return
|
||||
them. The first return value is always the directory. The second return
|
||||
@@ -784,29 +595,49 @@ strictly part of the guaranteed API.
|
||||
|
||||
``utils.join_path(p1, p2)``
|
||||
Return the concatenation of the 2 paths. Tries to be clever. For example,
|
||||
if ``p2`` is an absolute path, ``p2`` is returned without change.
|
||||
if ```p2`` is an absolute path, p2 is returned without change.
|
||||
|
||||
``utils.subprocess(t)``
|
||||
Runs an external process and waits until it exits. Returns process status
|
||||
and the captured output. This is a legacy wrapper around calling the
|
||||
``subprocess`` command with ``mp.command_native``. It does the following
|
||||
things:
|
||||
and the captured output.
|
||||
|
||||
- copy the table ``t``
|
||||
- rename ``cancellable`` field to ``playback_only``
|
||||
- rename ``max_size`` to ``capture_size``
|
||||
- set ``capture_stdout`` field to ``true`` if unset
|
||||
- set ``name`` field to ``subprocess``
|
||||
- call ``mp.command_native(copied_t)``
|
||||
- if the command failed, create a dummy result table
|
||||
- copy ``error_string`` to ``error`` field if the string is non-empty
|
||||
- return the result table
|
||||
The parameter ``t`` is a table. The function reads the following entries:
|
||||
|
||||
It is recommended to use ``mp.command_native`` or ``mp.command_native_async``
|
||||
directly, instead of calling this legacy wrapper. It is for compatibility
|
||||
only.
|
||||
``args``
|
||||
Array of strings. The first array entry is the executable. This
|
||||
can be either an absolute path, or a filename with no path
|
||||
components, in which case the ``PATH`` environment variable is
|
||||
used to resolve the executable. The other array elements are
|
||||
passed as command line arguments.
|
||||
|
||||
See the ``subprocess`` documentation for semantics and further parameters.
|
||||
``cancellable``
|
||||
Optional. If set to ``true`` (default), then if the user stops
|
||||
playback or goes to the next file while the process is running,
|
||||
the process will be killed.
|
||||
|
||||
``max_size``
|
||||
Optional. The maximum size in bytes of the data that can be captured
|
||||
from stdout. (Default: 16 MB.)
|
||||
|
||||
The function returns a table as result with the following entries:
|
||||
|
||||
``status``
|
||||
The raw exit status of the process. It will be negative on error.
|
||||
|
||||
``stdout``
|
||||
Captured output stream as string, limited to ``max_size``.
|
||||
|
||||
``error``
|
||||
``nil`` on success. The string ``killed`` if the process was
|
||||
terminated in an unusual way. The string ``init`` if the process
|
||||
could not be started.
|
||||
|
||||
On Windows, ``killed`` is only returned when the process has been
|
||||
killed by mpv as a result of ``cancellable`` being set to ``true``.
|
||||
|
||||
``killed_by_us``
|
||||
Set to ``true`` if the process has been killed by mpv as a result
|
||||
of ``cancellable`` being set to ``true``.
|
||||
|
||||
``utils.subprocess_detached(t)``
|
||||
Runs an external process and detaches it from mpv's control.
|
||||
@@ -819,17 +650,6 @@ strictly part of the guaranteed API.
|
||||
|
||||
The function returns ``nil``.
|
||||
|
||||
This is a legacy wrapper around calling the ``run`` command with
|
||||
``mp.commandv`` and other functions.
|
||||
|
||||
``utils.getpid()``
|
||||
Returns the process ID of the running mpv process. This can be used to identify
|
||||
the calling mpv when launching (detached) subprocesses.
|
||||
|
||||
``utils.get_env_list()``
|
||||
Returns the C environment as a list of strings. (Do not confuse this with
|
||||
the Lua "environment", which is an unrelated concept.)
|
||||
|
||||
``utils.parse_json(str [, trail])``
|
||||
Parses the given string argument as JSON, and returns it as a Lua table. On
|
||||
error, returns ``nil, error``. (Currently, ``error`` is just a string
|
||||
@@ -875,7 +695,116 @@ Example:
|
||||
|
||||
mp.register_event("file-loaded", my_fn)
|
||||
|
||||
For the existing event types, see `List of events`_.
|
||||
|
||||
|
||||
List of events
|
||||
--------------
|
||||
|
||||
``start-file``
|
||||
Happens right before a new file is loaded. When you receive this, the
|
||||
player is loading the file (or possibly already done with it).
|
||||
|
||||
``end-file``
|
||||
Happens after a file was unloaded. Typically, the player will load the
|
||||
next file right away, or quit if this was the last file.
|
||||
|
||||
The event has the ``reason`` field, which takes one of these values:
|
||||
|
||||
``eof``
|
||||
The file has ended. This can (but doesn't have to) include
|
||||
incomplete files or broken network connections under
|
||||
circumstances.
|
||||
|
||||
``stop``
|
||||
Playback was ended by a command.
|
||||
|
||||
``quit``
|
||||
Playback was ended by sending the quit command.
|
||||
|
||||
``error``
|
||||
An error happened. In this case, an ``error`` field is present with
|
||||
the error string.
|
||||
|
||||
``redirect``
|
||||
Happens with playlists and similar. Details see
|
||||
``MPV_END_FILE_REASON_REDIRECT`` in the C API.
|
||||
|
||||
``unknown``
|
||||
Unknown. Normally doesn't happen, unless the Lua API is out of sync
|
||||
with the C API. (Likewise, it could happen that your script gets
|
||||
reason strings that did not exist yet at the time your script was
|
||||
written.)
|
||||
|
||||
``file-loaded``
|
||||
Happens after a file was loaded and begins playback.
|
||||
|
||||
``seek``
|
||||
Happens on seeking. (This might include cases when the player seeks
|
||||
internally, even without user interaction. This includes e.g. segment
|
||||
changes when playing ordered chapters Matroska files.)
|
||||
|
||||
``playback-restart``
|
||||
Start of playback after seek or after file was loaded.
|
||||
|
||||
``idle``
|
||||
Idle mode is entered. This happens when playback ended, and the player was
|
||||
started with ``--idle`` or ``--force-window``. This mode is implicitly ended
|
||||
when the ``start-file`` or ``shutdown`` events happen.
|
||||
|
||||
``tick``
|
||||
Called after a video frame was displayed. This is a hack, and you should
|
||||
avoid using it. Use timers instead and maybe watch pausing/unpausing events
|
||||
to avoid wasting CPU when the player is paused.
|
||||
|
||||
``shutdown``
|
||||
Sent when the player quits, and the script should terminate. Normally
|
||||
handled automatically. See `Details on the script initialization and lifecycle`_.
|
||||
|
||||
``log-message``
|
||||
Receives messages enabled with ``mp.enable_messages``. The message data
|
||||
is contained in the table passed as first parameter to the event handler.
|
||||
The table contains, in addition to the default event fields, the following
|
||||
fields:
|
||||
|
||||
``prefix``
|
||||
The module prefix, identifies the sender of the message. This is what
|
||||
the terminal player puts in front of the message text when using the
|
||||
``--v`` option, and is also what is used for ``--msg-level``.
|
||||
|
||||
``level``
|
||||
The log level as string. See ``msg.log`` for possible log level names.
|
||||
Note that later versions of mpv might add new levels or remove
|
||||
(undocumented) existing ones.
|
||||
|
||||
``text``
|
||||
The log message. The text will end with a newline character. Sometimes
|
||||
it can contain multiple lines.
|
||||
|
||||
Keep in mind that these messages are meant to be hints for humans. You
|
||||
should not parse them, and prefix/level/text of messages might change
|
||||
any time.
|
||||
|
||||
``get-property-reply``
|
||||
Undocumented (not useful for Lua scripts).
|
||||
|
||||
``set-property-reply``
|
||||
Undocumented (not useful for Lua scripts).
|
||||
|
||||
``command-reply``
|
||||
Undocumented (not useful for Lua scripts).
|
||||
|
||||
``client-message``
|
||||
Undocumented (used internally).
|
||||
|
||||
``video-reconfig``
|
||||
Happens on video output or filter reconfig.
|
||||
|
||||
``audio-reconfig``
|
||||
Happens on audio output or filter reconfig.
|
||||
|
||||
The following events also happen, but are deprecated: ``tracks-changed``,
|
||||
``track-switched``, ``pause``, ``unpause``, ``metadata-update``,
|
||||
``chapter-change``. Use ``mp.observe_property()`` instead.
|
||||
|
||||
Extras
|
||||
------
|
||||
@@ -889,22 +818,8 @@ guarantee a stable interface.
|
||||
their result (normally, the Lua scripting interface is asynchronous from
|
||||
the point of view of the player core). ``priority`` is an arbitrary integer
|
||||
that allows ordering among hooks of the same kind. Using the value 50 is
|
||||
recommended as neutral default value.
|
||||
|
||||
``fn(hook)`` is the function that will be called during execution of the
|
||||
hook. The parameter passed to it (``hook``) is a Lua object that can control
|
||||
further aspects about the currently invoked hook. It provides the following
|
||||
methods:
|
||||
|
||||
``defer()``
|
||||
Returning from the hook function should not automatically continue
|
||||
the hook. Instead, the API user wants to call ``hook:cont()`` on its
|
||||
own at a later point in time (before or after the function has
|
||||
returned).
|
||||
|
||||
``cont()``
|
||||
Continue the hook. Doesn't need to be called unless ``defer()`` was
|
||||
called.
|
||||
recommended as neutral default value. ``fn`` is the function that will be
|
||||
called during execution of the hook.
|
||||
|
||||
See `Hooks`_ for currently existing hooks and what they do - only the hook
|
||||
list is interesting; handling hook execution is done by the Lua script
|
||||
|
||||
+142
-642
File diff suppressed because it is too large.
Load diff
+865
-2387
File diff suppressed because it is too large.
Load diff
+4
-94
@@ -128,7 +128,7 @@ Configuration
|
||||
-------------
|
||||
|
||||
The OSC offers limited configuration through a config file
|
||||
``script-opts/osc.conf`` placed in mpv's user dir and through the
|
||||
``lua-settings/osc.conf`` placed in mpv's user dir and through the
|
||||
``--script-opts`` command-line option. Options provided through the command-line
|
||||
will override those from the config file.
|
||||
|
||||
@@ -167,46 +167,9 @@ Configurable Options
|
||||
``seekbarstyle``
|
||||
Default: bar
|
||||
|
||||
Sets the style of the playback position marker and overall shape
|
||||
of the seekbar: ``bar``, ``diamond`` or ``knob``.
|
||||
|
||||
``seekbarhandlesize``
|
||||
Default: 0.6
|
||||
|
||||
Size ratio of the seek handle if ``seekbarstyle`` is set to ``dimaond``
|
||||
or ``knob``. This is relative to the full height of the seekbar.
|
||||
|
||||
``seekbarkeyframes``
|
||||
Default: yes
|
||||
|
||||
Controls the mode used to seek when dragging the seekbar (default: true). If
|
||||
set to true, default seeking mode is used (usually keyframes, but player
|
||||
defaults and heuristics can change it to exact). If set to false, exact
|
||||
seeking on mouse drags will be used instead. Keyframes are preferred, but
|
||||
exact seeks may be useful in cases where keyframes cannot be found. Note
|
||||
that using exact seeks can potentially make mouse dragging much slower.
|
||||
|
||||
``seekrangestyle``
|
||||
Default: inverted
|
||||
|
||||
Display seekable ranges on the seekbar. ``bar`` shows them on the full
|
||||
height of the bar, ``line`` as a thick line and ``inverted`` as a thin
|
||||
line that is inverted over playback position markers. ``none`` will hide
|
||||
them. Additionally, ``slider`` will show a permanent handle inside the seekbar
|
||||
with cached ranges marked inside. Note that these will look differently
|
||||
based on the seekbarstyle option. Also, ``slider`` does not work with
|
||||
``seekbarstyle`` set to ``bar``.
|
||||
|
||||
``seekrangeseparate``
|
||||
Default: yes
|
||||
|
||||
Controls whether to show line-style seekable ranges on top of the
|
||||
seekbar or separately if ``seekbarstyle`` is set to ``bar``.
|
||||
|
||||
``seekrangealpha``
|
||||
Default: 200
|
||||
|
||||
Alpha of the seekable ranges, 0 (opaque) to 255 (fully transparent).
|
||||
Sets the style of the seekbar, slider (diamond marker), knob (circle
|
||||
marker with guide), or bar (fill).
|
||||
Default pre-0.21.0 was 'slider'.
|
||||
|
||||
``deadzonesize``
|
||||
Default: 0.5
|
||||
@@ -322,59 +285,6 @@ Configurable Options
|
||||
default is conservative to allow wide fonts to be used without overflow.
|
||||
However, with many common fonts a bigger number can be used. YMMV.
|
||||
|
||||
``boxvideo``
|
||||
Default: no
|
||||
|
||||
Whether to overlay the osc over the video (``no``), or to box the video
|
||||
within the areas not covered by the osc (``yes``). If this option is set,
|
||||
the osc may overwrite the ``--video-margin-ratio-*`` options, even if the
|
||||
user has set them. (It will not overwrite them if all of them are set to
|
||||
default values.) Additionally, ``visibility`` must be set to ``always``.
|
||||
Otherwise, this option does nothing.
|
||||
|
||||
Currently, this is supported for the ``bottombar`` and ``topbar`` layout
|
||||
only. The other layouts do not change if this option is set. Separately,
|
||||
if window controls are present (see below), they will be affected
|
||||
regardless of which osc layout is in use.
|
||||
|
||||
The border is static and appears even if the OSC is configured to appear
|
||||
only on mouse interaction. If the OSC is invisible, the border is simply
|
||||
filled with the background color (black by default).
|
||||
|
||||
This currently still makes the OSC overlap with subtitles (if the
|
||||
``--sub-use-margins`` option is set to ``yes``, the default). This may be
|
||||
fixed later.
|
||||
|
||||
This does not work correctly with video outputs like ``--vo=xv``, which
|
||||
render OSD into the unscaled video.
|
||||
|
||||
``windowcontrols``
|
||||
Default: auto (Show window controls if there is no window border)
|
||||
|
||||
Whether to show window management controls over the video, and if so,
|
||||
which side of the window to place them. This may be desirable when the
|
||||
window has no decorations, either because they have been explicitly
|
||||
disabled (``border=no``) or because the current platform doesn't support
|
||||
them (eg: gnome-shell with wayland).
|
||||
|
||||
The set of window controls is fixed, offering ``minimize``, ``maximize``,
|
||||
and ``quit``. Not all platforms implement ``minimize`` and ``maximize``,
|
||||
but ``quit`` will always work.
|
||||
|
||||
``windowcontrols_alignment``
|
||||
Default: right
|
||||
|
||||
If window controls are shown, indicates which side should they be aligned
|
||||
to.
|
||||
|
||||
Supports ``left`` and ``right`` which will place the controls on those
|
||||
respective sides.
|
||||
|
||||
``greenandgrumpy``
|
||||
Default: no
|
||||
|
||||
Set to ``yes`` to reduce festivity (i.e. disable santa hat in December.)
|
||||
|
||||
Script Commands
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
@@ -1,221 +0,0 @@
|
||||
STATS
|
||||
=====
|
||||
|
||||
This builtin script displays information and statistics for the currently
|
||||
played file. It is enabled by default if mpv was compiled with Lua support.
|
||||
It can be disabled entirely using the ``--load-stats-overlay=no`` option.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
The following key bindings are active by default unless something else is
|
||||
already bound to them:
|
||||
|
||||
==== ==============================================
|
||||
i Show stats for a fixed duration
|
||||
I Toggle stats (shown until toggled again)
|
||||
==== ==============================================
|
||||
|
||||
While the stats are visible on screen the following key bindings are active,
|
||||
regardless of existing bindings. They allow you to switch between *pages* of
|
||||
stats:
|
||||
|
||||
==== ==================
|
||||
1 Show usual stats
|
||||
2 Show frame timings (scroll)
|
||||
3 Input cache stats
|
||||
4 Internal stuff (scroll)
|
||||
==== ==================
|
||||
|
||||
On pages which support scroll, these key bindings are also active:
|
||||
|
||||
==== ==================
|
||||
UP Scroll one line up
|
||||
DOWN Scroll one line down
|
||||
==== ==================
|
||||
|
||||
Font
|
||||
~~~~
|
||||
|
||||
For optimal visual experience, a font with support for many font weights and
|
||||
monospaced digits is recommended. By default, the open source font
|
||||
`Source Sans Pro <https://github.com/adobe-fonts/source-sans-pro>`_ is used.
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
This script can be customized through a config file ``script-opts/stats.conf``
|
||||
placed in mpv's user directory and through the ``--script-opts`` command-line
|
||||
option. The configuration syntax is described in `ON SCREEN CONTROLLER`_.
|
||||
|
||||
Configurable Options
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
``key_oneshot``
|
||||
Default: i
|
||||
``key_toggle``
|
||||
Default: I
|
||||
|
||||
Key bindings to display stats.
|
||||
|
||||
``key_page_1``
|
||||
Default: 1
|
||||
``key_page_2``
|
||||
Default: 2
|
||||
``key_page_3``
|
||||
Default: 3
|
||||
``key_page_4``
|
||||
Default: 4
|
||||
|
||||
Key bindings for page switching while stats are displayed.
|
||||
|
||||
``key_scroll_up``
|
||||
Default: UP
|
||||
``key_scroll_down``
|
||||
Default: DOWN
|
||||
``scroll_lines``
|
||||
Default: 1
|
||||
|
||||
Scroll key bindings and number of lines to scroll on pages which support it.
|
||||
|
||||
``duration``
|
||||
Default: 4
|
||||
|
||||
How long the stats are shown in seconds (oneshot).
|
||||
|
||||
``redraw_delay``
|
||||
Default: 1
|
||||
|
||||
How long it takes to refresh the displayed stats in seconds (toggling).
|
||||
|
||||
``persistent_overlay``
|
||||
Default: no
|
||||
|
||||
When `no`, other scripts printing text to the screen can overwrite the
|
||||
displayed stats. When `yes`, displayed stats are persistently shown for the
|
||||
respective duration. This can result in overlapping text when multiple
|
||||
scripts decide to print text at the same time.
|
||||
|
||||
``plot_perfdata``
|
||||
Default: yes
|
||||
|
||||
Show graphs for performance data (page 2).
|
||||
|
||||
``plot_vsync_ratio``
|
||||
Default: yes
|
||||
``plot_vsync_jitter``
|
||||
Default: yes
|
||||
|
||||
Show graphs for vsync and jitter values (page 1). Only when toggled.
|
||||
|
||||
``flush_graph_data``
|
||||
Default: yes
|
||||
|
||||
Clear data buffers used for drawing graphs when toggling.
|
||||
|
||||
``font``
|
||||
Default: Source Sans Pro
|
||||
|
||||
Font name. Should support as many font weights as possible for optimal
|
||||
visual experience.
|
||||
|
||||
``font_mono``
|
||||
Default: Source Sans Pro
|
||||
|
||||
Font name for parts where monospaced characters are necessary to align
|
||||
text. Currently, monospaced digits are sufficient.
|
||||
|
||||
``font_size``
|
||||
Default: 8
|
||||
|
||||
Font size used to render text.
|
||||
|
||||
``font_color``
|
||||
Default: FFFFFF
|
||||
|
||||
Font color.
|
||||
|
||||
``border_size``
|
||||
Default: 0.8
|
||||
|
||||
Size of border drawn around the font.
|
||||
|
||||
``border_color``
|
||||
Default: 262626
|
||||
|
||||
Color of drawn border.
|
||||
|
||||
``alpha``
|
||||
Default: 11
|
||||
|
||||
Transparency for drawn text.
|
||||
|
||||
``plot_bg_border_color``
|
||||
Default: 0000FF
|
||||
|
||||
Border color used for drawing graphs.
|
||||
|
||||
``plot_bg_color``
|
||||
Default: 262626
|
||||
|
||||
Background color used for drawing graphs.
|
||||
|
||||
``plot_color``
|
||||
Default: FFFFFF
|
||||
|
||||
Color used for drawing graphs.
|
||||
|
||||
Note: colors are given as hexadecimal values and use ASS tag order: BBGGRR
|
||||
(blue green red).
|
||||
|
||||
Different key bindings
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A different key binding can be defined with the aforementioned options
|
||||
``key_oneshot`` and ``key_toggle`` but also with commands in ``input.conf``,
|
||||
for example::
|
||||
|
||||
e script-binding stats/display-stats
|
||||
E script-binding stats/display-stats-toggle
|
||||
|
||||
Using ``input.conf``, it is also possible to directly display a certain page::
|
||||
|
||||
i script-binding stats/display-page-1
|
||||
e script-binding stats/display-page-2
|
||||
|
||||
Internal stuff page
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Most entries shown on this page have rather vague meaning. Likely none of this
|
||||
is useful for you. Don't attempt to use it. Forget its existence.
|
||||
|
||||
Selecting this for the first time will start collecting some internal
|
||||
performance data. That means performance will be slightly lower than normal for
|
||||
the rest of the time the player is running (even if the stats page is closed).
|
||||
Note that the stats page itself uses a lot of CPU and even GPU resources, and
|
||||
may have a heavy impact on performance.
|
||||
|
||||
The displayed information is accumulated over the redraw delay (shown as
|
||||
``poll-time`` field).
|
||||
|
||||
This adds entries for each Lua script. If there are too many scripts running,
|
||||
parts of the list will simply be out of the screen, but it can be scrolled.
|
||||
|
||||
If the underlying platform does not support pthread per thread times, the
|
||||
displayed times will be 0 or something random (I suspect that at time of this
|
||||
writing, only Linux provides the correct via pthread APIs for per thread times).
|
||||
|
||||
Most entries are added lazily and only during data collection, which is why
|
||||
entries may pop up randomly after some time. It's also why the memory usage
|
||||
entries for scripts that have been inactive since the start of data collection
|
||||
are missing.
|
||||
|
||||
Memory usage is approximate and does not reflect internal fragmentation.
|
||||
|
||||
JS scripts memory reporting is disabled by default because collecting the data
|
||||
at the JS side has an overhead. It can be enabled by exporting the env var
|
||||
``MPV_LEAK_REPORT=1`` before starting mpv, and will increase JS memory usage.
|
||||
|
||||
If entries have ``/time`` and ``/cpu`` variants, the former gives the real time
|
||||
(monotonic clock), while the latter the thread CPU time (only if the
|
||||
corresponding pthread API works and is supported).
|
||||
+407
-321
@@ -1,11 +1,8 @@
|
||||
VIDEO FILTERS
|
||||
=============
|
||||
|
||||
Video filters allow you to modify the video stream and its properties. All of
|
||||
the information described in this section applies to audio filters as well
|
||||
(generally using the prefix ``--af`` instead of ``--vf``).
|
||||
|
||||
The exact syntax is:
|
||||
Video filters allow you to modify the video stream and its properties. The
|
||||
syntax is:
|
||||
|
||||
``--vf=<filter1[=parameter1:parameter2:...],filter2,...>``
|
||||
Setup a chain of video filters. This consists on the filter name, and an
|
||||
@@ -16,7 +13,7 @@ The exact syntax is:
|
||||
name is an arbitrary user-given name, which identifies the filter. This
|
||||
is only needed if you want to toggle the filter at runtime.
|
||||
|
||||
A ``!`` before the filter name means the filter is disabled by default. It
|
||||
A ``!`` before the filter name means the filter is enabled by default. It
|
||||
will be skipped on filter creation. This is also useful for runtime filter
|
||||
toggling.
|
||||
|
||||
@@ -44,18 +41,11 @@ The exact syntax is:
|
||||
the ``lavfi`` filter, which uses a very similar syntax as mpv (MPlayer
|
||||
historically) to specify filters and their parameters.
|
||||
|
||||
Filters can be manipulated at run time. You can use ``@`` labels as described
|
||||
above in combination with the ``vf`` command (see `COMMAND INTERFACE`_) to get
|
||||
more control over this. Initially disabled filters with ``!`` are useful for
|
||||
this as well.
|
||||
|
||||
You can also set defaults for each filter. The defaults are applied before the
|
||||
normal filter parameters. This is deprecated and never worked for the
|
||||
libavfilter bridge.
|
||||
normal filter parameters.
|
||||
|
||||
``--vf-defaults=<filter1[=parameter1:parameter2:...],filter2,...>``
|
||||
Set defaults for each filter. (Deprecated. ``--af-defaults`` is deprecated
|
||||
as well.)
|
||||
Set defaults for each filter.
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -85,35 +75,15 @@ libavfilter bridge.
|
||||
Video filters are managed in lists. There are a few commands to manage the
|
||||
filter list.
|
||||
|
||||
``--vf-append=filter``
|
||||
Appends the filter given as arguments to the filter list.
|
||||
``--vf-add=<filter1[,filter2,...]>``
|
||||
Appends the filters given as arguments to the filter list.
|
||||
|
||||
``--vf-add=filter``
|
||||
Appends the filter given as arguments to the filter list. (Passing multiple
|
||||
filters is currently still possible, but deprecated.)
|
||||
``--vf-pre=<filter1[,filter2,...]>``
|
||||
Prepends the filters given as arguments to the filter list.
|
||||
|
||||
``--vf-pre=filter``
|
||||
Prepends the filters given as arguments to the filter list. (Passing
|
||||
multiple filters is currently still possible, but deprecated.)
|
||||
|
||||
``--vf-remove=filter``
|
||||
Deletes the filter from the list. The filter can be either given the way it
|
||||
was added (filter name and its full argument list), or by label (prefixed
|
||||
with ``@``). Matching of filters works as follows: if either of the compared
|
||||
filters has a label set, only the labels are compared. If none of the
|
||||
filters have a label, the filter name, arguments, and argument order are
|
||||
compared. (Passing multiple filters is currently still possible, but
|
||||
deprecated.)
|
||||
|
||||
``-vf-toggle=filter``
|
||||
Add the given filter to the list if it was not present yet, or remove it
|
||||
from the list if it was present. Matching of filters works as described in
|
||||
``--vf-remove``.
|
||||
|
||||
``--vf-del=filter``
|
||||
Sort of like ``--vf-remove``, but also accepts an index number. Index
|
||||
numbers start at 0, negative numbers address the end of the list (-1 is the
|
||||
last). Deprecated.
|
||||
``--vf-del=<index1[,index2,...]>``
|
||||
Deletes the filters at the given indexes. Index numbers start at 0,
|
||||
negative numbers address the end of the list (-1 is the last).
|
||||
|
||||
``--vf-clr``
|
||||
Completely empties the filter list.
|
||||
@@ -124,61 +94,178 @@ With filters that support it, you can access parameters by their name.
|
||||
Prints the parameter names and parameter value ranges for a particular
|
||||
filter.
|
||||
|
||||
``--vf=<filter=named_parameter1=value1[:named_parameter2=value2:...]>``
|
||||
Sets a named parameter to the given value. Use on and off or yes and no to
|
||||
set flag parameters.
|
||||
|
||||
Available mpv-only filters are:
|
||||
|
||||
``crop[=w:h:x:y]``
|
||||
Crops the given part of the image and discards the rest. Useful to remove
|
||||
black bands from widescreen videos.
|
||||
|
||||
``<w>,<h>``
|
||||
Cropped width and height, defaults to original width and height.
|
||||
``<x>,<y>``
|
||||
Position of the cropped picture, defaults to center.
|
||||
|
||||
``expand[=w:h:x:y:aspect:round]``
|
||||
Expands (not scales) video resolution to the given value and places the
|
||||
unscaled original at coordinates x, y.
|
||||
|
||||
``<w>,<h>``
|
||||
Expanded width,height (default: original width,height). Negative
|
||||
values for w and h are treated as offsets to the original size.
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
``expand=0:-50:0:0``
|
||||
Adds a 50 pixel border to the bottom of the picture.
|
||||
|
||||
``<x>,<y>``
|
||||
position of original image on the expanded image (default: center)
|
||||
|
||||
``<aspect>``
|
||||
Expands to fit an aspect instead of a resolution (default: 0).
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
``expand=800::::4/3``
|
||||
Expands to 800x600, unless the source is higher resolution, in
|
||||
which case it expands to fill a 4/3 aspect.
|
||||
|
||||
``<round>``
|
||||
Rounds up to make both width and height divisible by <r> (default: 1).
|
||||
|
||||
``flip``
|
||||
Flips the image upside down.
|
||||
|
||||
``mirror``
|
||||
Mirrors the image on the Y axis.
|
||||
|
||||
``rotate[=0|90|180|270]``
|
||||
Rotates the image by a multiple of 90 degrees clock-wise.
|
||||
|
||||
``scale[=w:h:param:param2:chr-drop:noup:arnd``
|
||||
Scales the image with the software scaler (slow) and performs a YUV<->RGB
|
||||
color space conversion (see also ``--sws``).
|
||||
|
||||
All parameters are optional.
|
||||
|
||||
``<w>:<h>``
|
||||
scaled width/height (default: original width/height)
|
||||
|
||||
:0: scaled d_width/d_height
|
||||
:-1: original width/height
|
||||
:-2: Calculate w/h using the other dimension and the prescaled
|
||||
aspect ratio.
|
||||
:-3: Calculate w/h using the other dimension and the original
|
||||
aspect ratio.
|
||||
:-(n+8): Like -n above, but rounding the dimension to the closest
|
||||
multiple of 16.
|
||||
|
||||
``<param>[:<param2>]`` (see also ``--sws``)
|
||||
Set some scaling parameters depending on the type of scaler selected
|
||||
with ``--sws``::
|
||||
|
||||
--sws=2 (bicubic): B (blurring) and C (ringing)
|
||||
0.00:0.60 default
|
||||
0.00:0.75 VirtualDub's "precise bicubic"
|
||||
0.00:0.50 Catmull-Rom spline
|
||||
0.33:0.33 Mitchell-Netravali spline
|
||||
1.00:0.00 cubic B-spline
|
||||
|
||||
--sws=7 (Gaussian): sharpness (0 (soft) - 100 (sharp))
|
||||
|
||||
--sws=9 (Lanczos): filter length (1-10)
|
||||
|
||||
``<chr-drop>``
|
||||
chroma skipping
|
||||
|
||||
:0: Use all available input lines for chroma (default).
|
||||
:1: Use only every 2. input line for chroma.
|
||||
:2: Use only every 4. input line for chroma.
|
||||
:3: Use only every 8. input line for chroma.
|
||||
|
||||
``<noup>``
|
||||
Disallow upscaling past the original dimensions.
|
||||
|
||||
:0: Allow upscaling (default).
|
||||
:1: Disallow upscaling if one dimension exceeds its original value.
|
||||
:2: Disallow upscaling if both dimensions exceed their original values.
|
||||
|
||||
``<arnd>``
|
||||
Accurate rounding for the vertical scaler, which may be faster or
|
||||
slower than the default rounding.
|
||||
|
||||
:no: Disable accurate rounding (default).
|
||||
:yes: Enable accurate rounding.
|
||||
|
||||
``dsize[=w:h:aspect-method:r:aspect]``
|
||||
Changes the intended display aspect at an arbitrary point in the
|
||||
filter chain. Aspect can be given as a fraction (4/3) or floating point
|
||||
number (1.33). Note that this filter does *not* do any scaling itself; it
|
||||
just affects what later scalers (software or hardware) will do when
|
||||
auto-scaling to the correct aspect.
|
||||
|
||||
``<w>,<h>``
|
||||
New aspect ratio given by a display width and height. Unlike older mpv
|
||||
versions or MPlayer, this does not set the display size.
|
||||
|
||||
Can also be these special values:
|
||||
|
||||
:0: original display width and height
|
||||
:-1: original video width and height (default)
|
||||
:-2: Calculate w/h using the other dimension and the original display
|
||||
aspect ratio.
|
||||
:-3: Calculate w/h using the other dimension and the original video
|
||||
aspect ratio.
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
``dsize=800:-2``
|
||||
Specifies a display resolution of 800x600 for a 4/3 aspect
|
||||
video, or 800x450 for a 16/9 aspect video.
|
||||
|
||||
``<aspect-method>``
|
||||
Modifies width and height according to original aspect ratios.
|
||||
|
||||
:-1: Ignore original aspect ratio (default).
|
||||
:0: Keep display aspect ratio by using ``<w>`` and ``<h>`` as maximum
|
||||
resolution.
|
||||
:1: Keep display aspect ratio by using ``<w>`` and ``<h>`` as minimum
|
||||
resolution.
|
||||
:2: Keep video aspect ratio by using ``<w>`` and ``<h>`` as maximum
|
||||
resolution.
|
||||
:3: Keep video aspect ratio by using ``<w>`` and ``<h>`` as minimum
|
||||
resolution.
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
``dsize=800:600:0``
|
||||
Specifies a display resolution of at most 800x600, or smaller,
|
||||
in order to keep aspect.
|
||||
|
||||
``<r>``
|
||||
Rounds up to make both width and height divisible by ``<r>``
|
||||
(default: 1).
|
||||
|
||||
``<aspect>``
|
||||
Force an aspect ratio.
|
||||
|
||||
``format=fmt=<value>:colormatrix=<value>:...``
|
||||
Applies video parameter overrides, with optional conversion. By default,
|
||||
this overrides the video's parameters without conversion (except for the
|
||||
``fmt`` parameter), but can be made to perform an appropriate conversion
|
||||
with ``convert=yes`` for parameters for which conversion is supported.
|
||||
Restricts the color space for the next filter without doing any conversion.
|
||||
Use together with the scale filter for a real conversion.
|
||||
|
||||
.. note::
|
||||
|
||||
For a list of available formats, see ``format=fmt=help``.
|
||||
|
||||
``<fmt>``
|
||||
Image format name, e.g. rgb15, bgr24, 420p, etc. (default: don't change).
|
||||
|
||||
This filter always performs conversion to the given format.
|
||||
|
||||
.. note::
|
||||
|
||||
For a list of available formats, use ``--vf=format=fmt=help``.
|
||||
|
||||
``<convert=yes|no>``
|
||||
Force conversion of color parameters (default: no).
|
||||
|
||||
If this is disabled (the default), the only conversion that is possibly
|
||||
performed is format conversion if ``<fmt>`` is set. All other parameters
|
||||
(like ``<colormatrix>``) are forced without conversion. This mode is
|
||||
typically useful when files have been incorrectly tagged.
|
||||
|
||||
If this is enabled, libswscale or zimg is used if any of the parameters
|
||||
mismatch. zimg is used of the input/output image formats are supported
|
||||
by mpv's zimg wrapper, and if ``--sws-allow-zimg=yes`` is used. Both
|
||||
libraries may not support all kinds of conversions. This typically
|
||||
results in silent incorrect conversion. zimg has in many cases a better
|
||||
chance of performing the conversion correctly.
|
||||
|
||||
In both cases, the color parameters are set on the output stage of the
|
||||
image format conversion (if ``fmt`` was set). The difference is that
|
||||
with ``convert=no``, the color parameters are not passed on to the
|
||||
converter.
|
||||
|
||||
If input and output video parameters are the same, conversion is always
|
||||
skipped.
|
||||
|
||||
.. admonition:: Examples
|
||||
|
||||
``mpv test.mkv --vf=format:colormatrix=ycgco``
|
||||
Results in incorrect colors (if test.mkv was tagged correctly).
|
||||
|
||||
``mpv test.mkv --vf=format:colormatrix=ycgco:convert=yes --sws-allow-zimg``
|
||||
Results in true conversion to ``ycgco``, assuming the renderer
|
||||
supports it (``--vo=gpu`` normally does). You can add ``--vo=xv``
|
||||
to force a VO which definitely does not support it, which should
|
||||
show incorrect colors as confirmation.
|
||||
|
||||
Using ``--sws-allow-zimg=no`` (or disabling zimg at build time)
|
||||
will use libswscale, which cannot perform this conversion as
|
||||
of this writing.
|
||||
|
||||
Format name, e.g. rgb15, bgr24, 420p, etc. (default: don't change).
|
||||
``<outfmt>``
|
||||
Format name that should be substituted for the output. If they do not
|
||||
have the same bytes per pixel and chroma subsampling, it will fail.
|
||||
``<colormatrix>``
|
||||
Controls the YUV to RGB color space conversion when playing video. There
|
||||
are various standards. Normally, BT.601 should be used for SD video, and
|
||||
@@ -186,7 +273,7 @@ Available mpv-only filters are:
|
||||
results in slightly under or over saturated and shifted colors.
|
||||
|
||||
These options are not always supported. Different video outputs provide
|
||||
varying degrees of support. The ``gpu`` and ``vdpau`` video output
|
||||
varying degrees of support. The ``opengl`` and ``vdpau`` video output
|
||||
drivers usually offer full support. The ``xv`` output can set the color
|
||||
space if the system video driver supports it, but not input and output
|
||||
levels. The ``scale`` video filter can configure color space and input
|
||||
@@ -229,7 +316,7 @@ Available mpv-only filters are:
|
||||
used to override the setting.
|
||||
|
||||
This option only affects video output drivers that perform color
|
||||
management, for example ``gpu`` with the ``target-prim`` or
|
||||
management, for example ``opengl`` with the ``target-prim`` or
|
||||
``icc-profile`` suboptions set.
|
||||
|
||||
If this option is set to ``auto`` (which is the default), the video's
|
||||
@@ -273,10 +360,7 @@ Available mpv-only filters are:
|
||||
:srgb: IEC 61966-2-4 (sRGB)
|
||||
:linear: Linear light
|
||||
:gamma1.8: Pure power curve (gamma 1.8)
|
||||
:gamma2.0: Pure power curve (gamma 2.0)
|
||||
:gamma2.2: Pure power curve (gamma 2.2)
|
||||
:gamma2.4: Pure power curve (gamma 2.4)
|
||||
:gamma2.6: Pure power curve (gamma 2.6)
|
||||
:gamma2.8: Pure power curve (gamma 2.8)
|
||||
:prophoto: ProPhoto RGB (ROMM) curve
|
||||
:pq: ITU-R BT.2100 PQ (Perceptual quantizer) curve
|
||||
@@ -309,22 +393,17 @@ Available mpv-only filters are:
|
||||
:gamma1.2: Scene-referred using a pure power OOTF (gamma=1.2)
|
||||
|
||||
``<stereo-in>``
|
||||
Set the stereo mode the video is assumed to be encoded in. Use
|
||||
``--vf format:stereo-in=help`` to list all available modes. Check with
|
||||
the ``stereo3d`` filter documentation to see what the names mean.
|
||||
Set the stereo mode the video is assumed to be encoded in. Takes the
|
||||
same values as the ``--video-stereo-mode`` option.
|
||||
|
||||
``<stereo-out>``
|
||||
Set the stereo mode the video should be displayed as. Takes the
|
||||
same values as the ``stereo-in`` option.
|
||||
same values as the ``--video-stereo-mode`` option.
|
||||
|
||||
``<rotate>``
|
||||
Set the rotation the video is assumed to be encoded with in degrees.
|
||||
The special value ``-1`` uses the input format.
|
||||
|
||||
``<w>``, ``<h>``
|
||||
If not 0, perform conversion to the given size. Ignored if
|
||||
``convert=yes`` is not set.
|
||||
|
||||
``<dw>``, ``<dh>``
|
||||
Set the display size. Note that setting the display size such that
|
||||
the video is scaled in both directions instead of just changing the
|
||||
@@ -335,17 +414,26 @@ Available mpv-only filters are:
|
||||
but values such as ``[16:9]`` can be passed too (``[...]`` for quoting
|
||||
to prevent the option parser from interpreting the ``:`` character).
|
||||
|
||||
``<force-scaler=auto|zimg|sws>``
|
||||
Force a specific scaler backend, if applicable. This is a debug option
|
||||
and could go away any time.
|
||||
``<spherical-type>``
|
||||
Type of the spherical projection:
|
||||
|
||||
``<alpha=auto|straight|premul>``
|
||||
Set the kind of alpha the video uses. Undefined effect if the image
|
||||
format has no alpha channel (could be ignored or cause an error,
|
||||
depending on how mpv internals evolve). Setting this may or may not
|
||||
cause downstream image processing to treat alpha differently, depending
|
||||
on support. With ``convert`` and zimg used, this will convert the alpha.
|
||||
libswscale and other FFmpeg components completely ignore this.
|
||||
:auto: As indicated by the file (default)
|
||||
:none: Normal video
|
||||
:equirect: Equirectangular
|
||||
:unknown: Unknown projection
|
||||
|
||||
``<spherical-yaw>``, ``<spherical-pitch>``, ``<spherical-roll>``
|
||||
Reference angle in degree, if spherical video is used.
|
||||
|
||||
``noformat[=fmt]``
|
||||
Restricts the color space for the next filter without doing any conversion.
|
||||
Unlike the format filter, this will allow any color space except the one
|
||||
you specify.
|
||||
|
||||
.. note:: For a list of available formats, see ``noformat=fmt=help``.
|
||||
|
||||
``<fmt>``
|
||||
Format name, e.g. rgb15, bgr24, 420p, etc. (default: 420p).
|
||||
|
||||
``lavfi=graph[:sws-flags[:o=opts]]``
|
||||
Filter video using FFmpeg's libavfilter.
|
||||
@@ -361,14 +449,11 @@ Available mpv-only filters are:
|
||||
|
||||
If you want to use the full filter syntax with this option, you have
|
||||
to quote the filter graph in order to prevent mpv's syntax and the
|
||||
filter graph syntax from clashing. To prevent a quoting and escaping
|
||||
mess, consider using ``--lavfi-complex`` if you know which video
|
||||
track you want to use from the input file. (There is only one video
|
||||
track for nearly all video files anyway.)
|
||||
filter graph syntax from clashing.
|
||||
|
||||
.. admonition:: Examples
|
||||
|
||||
``--vf=lavfi=[gradfun=20:30,vflip]``
|
||||
``-vf lavfi=[gradfun=20:30,vflip]``
|
||||
``gradfun`` filter with nonsense parameters, followed by a
|
||||
``vflip`` filter. (This demonstrates how libavfilter takes a
|
||||
graph and not just a single filter.) The filter graph string is
|
||||
@@ -399,6 +484,62 @@ Available mpv-only filters are:
|
||||
``'--vf=lavfi=yadif:o="threads=2,thread_type=slice"'``
|
||||
forces a specific threading configuration.
|
||||
|
||||
``pullup[=jl:jr:jt:jb:sb:mp]``
|
||||
Pulldown reversal (inverse telecine) filter, capable of handling mixed
|
||||
hard-telecine, 24000/1001 fps progressive, and 30000/1001 fps progressive
|
||||
content. The ``pullup`` filter makes use of future context in making its
|
||||
decisions. It is stateless in the sense that it does not lock onto a pattern
|
||||
to follow, but it instead looks forward to the following fields in order to
|
||||
identify matches and rebuild progressive frames.
|
||||
|
||||
``jl``, ``jr``, ``jt``, and ``jb``
|
||||
These options set the amount of "junk" to ignore at the left, right,
|
||||
top, and bottom of the image, respectively. Left/right are in units of
|
||||
8 pixels, while top/bottom are in units of 2 lines. The default is 8
|
||||
pixels on each side.
|
||||
|
||||
``sb`` (strict breaks)
|
||||
Setting this option to 1 will reduce the chances of ``pullup``
|
||||
generating an occasional mismatched frame, but it may also cause an
|
||||
excessive number of frames to be dropped during high motion sequences.
|
||||
Conversely, setting it to -1 will make ``pullup`` match fields more
|
||||
easily. This may help process video with slight blurring between the
|
||||
fields, but may also cause interlaced frames in the output.
|
||||
|
||||
``mp`` (metric plane)
|
||||
This option may be set to ``u`` or ``v`` to use a chroma plane instead of the
|
||||
luma plane for doing ``pullup``'s computations. This may improve accuracy
|
||||
on very clean source material, but more likely will decrease accuracy,
|
||||
especially if there is chroma noise (rainbow effect) or any grayscale
|
||||
video. The main purpose of setting ``mp`` to a chroma plane is to reduce
|
||||
CPU load and make pullup usable in realtime on slow machines.
|
||||
|
||||
``yadif=[mode:interlaced-only]``
|
||||
Yet another deinterlacing filter
|
||||
|
||||
``<mode>``
|
||||
:frame: Output 1 frame for each frame.
|
||||
:field: Output 1 frame for each field (default).
|
||||
:frame-nospatial: Like ``frame`` but skips spatial interlacing check.
|
||||
:field-nospatial: Like ``field`` but skips spatial interlacing check.
|
||||
|
||||
``<interlaced-only>``
|
||||
:no: Deinterlace all frames.
|
||||
:yes: Only deinterlace frames marked as interlaced (default).
|
||||
|
||||
This filter is automatically inserted when using the ``d`` key (or any
|
||||
other key that toggles the ``deinterlace`` property or when using the
|
||||
``--deinterlace`` switch), assuming the video output does not have native
|
||||
deinterlacing support.
|
||||
|
||||
If you just want to set the default mode, put this filter and its options
|
||||
into ``--vf-defaults`` instead, and enable deinterlacing with ``d`` or
|
||||
``--deinterlace``.
|
||||
|
||||
Also, note that the ``d`` key is stupid enough to insert a deinterlacer twice
|
||||
when inserting yadif with ``--vf``, so using the above methods is
|
||||
recommended.
|
||||
|
||||
``sub=[=bottom-margin:top-margin]``
|
||||
Moves subtitle rendering to an arbitrary point in the filter chain, or force
|
||||
subtitle rendering in the video filter as opposed to using video output OSD
|
||||
@@ -417,6 +558,91 @@ Available mpv-only filters are:
|
||||
subtitle colors and video under the influence of the video equalizer
|
||||
settings.
|
||||
|
||||
``stereo3d[=in:out]``
|
||||
Stereo3d converts between different stereoscopic image formats.
|
||||
|
||||
``<in>``
|
||||
Stereoscopic image format of input. Possible values:
|
||||
|
||||
``sbsl`` or ``side_by_side_left_first``
|
||||
side by side parallel (left eye left, right eye right)
|
||||
``sbsr`` or ``side_by_side_right_first``
|
||||
side by side crosseye (right eye left, left eye right)
|
||||
``abl`` or ``above_below_left_first``
|
||||
above-below (left eye above, right eye below)
|
||||
``abr`` or ``above_below_right_first``
|
||||
above-below (right eye above, left eye below)
|
||||
``ab2l`` or ``above_below_half_height_left_first``
|
||||
above-below with half height resolution (left eye above, right eye
|
||||
below)
|
||||
``ab2r`` or ``above_below_half_height_right_first``
|
||||
above-below with half height resolution (right eye above, left eye
|
||||
below)
|
||||
|
||||
``<out>``
|
||||
Stereoscopic image format of output. Possible values are all the input
|
||||
formats as well as:
|
||||
|
||||
``arcg`` or ``anaglyph_red_cyan_gray``
|
||||
anaglyph red/cyan gray (red filter on left eye, cyan filter on
|
||||
right eye)
|
||||
``arch`` or ``anaglyph_red_cyan_half_color``
|
||||
anaglyph red/cyan half colored (red filter on left eye, cyan filter
|
||||
on right eye)
|
||||
``arcc`` or ``anaglyph_red_cyan_color``
|
||||
anaglyph red/cyan color (red filter on left eye, cyan filter on
|
||||
right eye)
|
||||
``arcd`` or ``anaglyph_red_cyan_dubois``
|
||||
anaglyph red/cyan color optimized with the least-squares
|
||||
projection of Dubois (red filter on left eye, cyan filter on right
|
||||
eye)
|
||||
``agmg`` or ``anaglyph_green_magenta_gray``
|
||||
anaglyph green/magenta gray (green filter on left eye, magenta
|
||||
filter on right eye)
|
||||
``agmh`` or ``anaglyph_green_magenta_half_color``
|
||||
anaglyph green/magenta half colored (green filter on left eye,
|
||||
magenta filter on right eye)
|
||||
``agmc`` or ``anaglyph_green_magenta_color``
|
||||
anaglyph green/magenta colored (green filter on left eye, magenta
|
||||
filter on right eye)
|
||||
``aybg`` or ``anaglyph_yellow_blue_gray``
|
||||
anaglyph yellow/blue gray (yellow filter on left eye, blue filter
|
||||
on right eye)
|
||||
``aybh`` or ``anaglyph_yellow_blue_half_color``
|
||||
anaglyph yellow/blue half colored (yellow filter on left eye, blue
|
||||
filter on right eye)
|
||||
``aybc`` or ``anaglyph_yellow_blue_color``
|
||||
anaglyph yellow/blue colored (yellow filter on left eye, blue
|
||||
filter on right eye)
|
||||
``irl`` or ``interleave_rows_left_first``
|
||||
Interleaved rows (left eye has top row, right eye starts on next
|
||||
row)
|
||||
``irr`` or ``interleave_rows_right_first``
|
||||
Interleaved rows (right eye has top row, left eye starts on next
|
||||
row)
|
||||
``ml`` or ``mono_left``
|
||||
mono output (left eye only)
|
||||
``mr`` or ``mono_right``
|
||||
mono output (right eye only)
|
||||
|
||||
``gradfun[=strength[:radius|:size=<size>]]``
|
||||
Fix the banding artifacts that are sometimes introduced into nearly flat
|
||||
regions by truncation to 8-bit color depth. Interpolates the gradients that
|
||||
should go where the bands are, and dithers them.
|
||||
|
||||
``<strength>``
|
||||
Maximum amount by which the filter will change any one pixel. Also the
|
||||
threshold for detecting nearly flat regions (default: 1.5).
|
||||
|
||||
``<radius>``
|
||||
Neighborhood to fit the gradient to. Larger radius makes for smoother
|
||||
gradients, but also prevents the filter from modifying pixels near
|
||||
detailed regions (default: disabled).
|
||||
|
||||
``<size>``
|
||||
size of the filter in percent of the image diagonal size. This is
|
||||
used to calculate the final radius size (default: 1).
|
||||
|
||||
``vapoursynth=file:buffered-frames:concurrent-frames``
|
||||
Loads a VapourSynth filter script. This is intended for streamed
|
||||
processing: mpv actually provides a source filter, instead of using a
|
||||
@@ -426,31 +652,22 @@ Available mpv-only filters are:
|
||||
of that will return errors. As such, you can't use the full power of
|
||||
VapourSynth, but you can use certain filters.
|
||||
|
||||
.. warning::
|
||||
|
||||
Do not use this filter, unless you have expert knowledge in VapourSynth,
|
||||
and know how to fix bugs in the mpv VapourSynth wrapper code.
|
||||
|
||||
If you just want to play video generated by VapourSynth (i.e. using
|
||||
If you just want to play video generated by a VapourSynth (i.e. using
|
||||
a native VapourSynth video source), it's better to use ``vspipe`` and a
|
||||
pipe or FIFO to feed the video to mpv. The same applies if the filter script
|
||||
FIFO to feed the video to mpv. The same applies if the filter script
|
||||
requires random frame access (see ``buffered-frames`` parameter).
|
||||
|
||||
This filter is experimental. If it turns out that it works well and is
|
||||
used, it will be ported to libavfilter. Otherwise, it will be just removed.
|
||||
|
||||
``file``
|
||||
Filename of the script source. Currently, this is always a python
|
||||
script (``.vpy`` in VapourSynth convention).
|
||||
|
||||
The variable ``video_in`` is set to the mpv video source, and it is
|
||||
expected that the script reads video from it. (Otherwise, mpv will
|
||||
decode no video, and the video packet queue will overflow, eventually
|
||||
leading to only audio playing, or worse.)
|
||||
|
||||
The filter graph created by the script is also expected to pass through
|
||||
timestamps using the ``_DurationNum`` and ``_DurationDen`` frame
|
||||
properties.
|
||||
|
||||
See the end of the option list for a full list of script variables
|
||||
defined by mpv.
|
||||
script. The variable ``video_in`` is set to the mpv video source,
|
||||
and it is expected that the script reads video from it. (Otherwise,
|
||||
mpv will decode no video, and the video packet queue will overflow,
|
||||
eventually leading to audio being stopped.) The script is also
|
||||
expected to pass through timestamps using the ``_DurationNum`` and
|
||||
``_DurationDen`` frame properties.
|
||||
|
||||
.. admonition:: Example:
|
||||
|
||||
@@ -468,61 +685,21 @@ Available mpv-only filters are:
|
||||
``buffered-frames``
|
||||
Maximum number of decoded video frames that should be buffered before
|
||||
the filter (default: 4). This specifies the maximum number of frames
|
||||
the script can request in backward direction.
|
||||
|
||||
the script can request in reverse direction.
|
||||
E.g. if ``buffered-frames=5``, and the script just requested frame 15,
|
||||
it can still request frame 10, but frame 9 is not available anymore.
|
||||
If it requests frame 30, mpv will decode 15 more frames, and keep only
|
||||
frames 25-30.
|
||||
|
||||
The only reason why this buffer exists is to serve the random access
|
||||
requests the VapourSynth filter can make.
|
||||
|
||||
The VapourSynth API has a ``getFrameAsync`` function, which takes an
|
||||
absolute frame number. Source filters must respond to all requests. For
|
||||
example, a source filter can request frame 2432, and then frame 3.
|
||||
Source filters typically implement this by pre-indexing the entire
|
||||
file.
|
||||
|
||||
mpv on the other hand is stream oriented, and does not allow filters to
|
||||
seek. (And it would not make sense to allow it, because it would ruin
|
||||
performance.) Filters get frames sequentially in playback direction, and
|
||||
cannot request them out of order.
|
||||
|
||||
To compensate for this mismatch, mpv allows the filter to access frames
|
||||
within a certain window. ``buffered-frames`` controls the size of this
|
||||
window. Most VapourSynth filters happen to work with this, because mpv
|
||||
requests frames sequentially increasing from it, and most filters only
|
||||
require frames "close" to the requested frame.
|
||||
|
||||
If the filter requests a frame that has a higher frame number than the
|
||||
highest buffered frame, new frames will be decoded until the requested
|
||||
frame number is reached. Excessive frames will be flushed out in a FIFO
|
||||
manner (there are only at most ``buffered-frames`` in this buffer).
|
||||
|
||||
If the filter requests a frame that has a lower frame number than the
|
||||
lowest buffered frame, the request cannot be satisfied, and an error
|
||||
is returned to the filter. This kind of error is not supposed to happen
|
||||
in a "proper" VapourSynth environment. What exactly happens depends on
|
||||
the filters involved.
|
||||
|
||||
Increasing this buffer will not improve performance. Rather, it will
|
||||
waste memory, and slow down seeks (when enough frames to fill the buffer
|
||||
need to be decoded at once). It is only needed to prevent the error
|
||||
described in the previous paragraph.
|
||||
|
||||
How many frames a filter requires depends on filter implementation
|
||||
details, and mpv has no way of knowing. A scale filter might need only
|
||||
1 frame, an interpolation filter may require a small number of frames,
|
||||
and the ``Reverse`` filter will require an infinite number of frames.
|
||||
|
||||
If you want reliable operation to the full extend VapourSynth is
|
||||
capable, use ``vspipe``.
|
||||
|
||||
The actual number of buffered frames also depends on the value of the
|
||||
``concurrent-frames`` option. Currently, both option values are
|
||||
multiplied to get the final buffer size.
|
||||
|
||||
(Normally, VapourSynth source filters must provide random access, but
|
||||
mpv was made for playback, and does not provide frame-exact random
|
||||
access. The way this video filter works is a compromise to make simple
|
||||
filters work anyway.)
|
||||
|
||||
``concurrent-frames``
|
||||
Number of frames that should be requested in parallel. The
|
||||
level of concurrency depends on the filter and how quickly mpv can
|
||||
@@ -531,25 +708,15 @@ Available mpv-only filters are:
|
||||
making it higher than the number of cores can actually make it
|
||||
slower.
|
||||
|
||||
Technically, mpv will call the VapourSynth ``getFrameAsync`` function
|
||||
in a loop, until there are ``concurrent-frames`` frames that have not
|
||||
been returned by the filter yet. This also assumes that the rest of the
|
||||
mpv filter chain reads the output of the ``vapoursynth`` filter quickly
|
||||
enough. (For example, if you pause the player, filtering will stop very
|
||||
soon, because the filtered frames are waiting in a queue.)
|
||||
|
||||
Actual concurrency depends on many other factors.
|
||||
|
||||
By default, this uses the special value ``auto``, which sets the option
|
||||
to the number of detected logical CPU cores.
|
||||
|
||||
The following ``.vpy`` script variables are defined by mpv:
|
||||
The following variables are defined by mpv:
|
||||
|
||||
``video_in``
|
||||
The mpv video source as vapoursynth clip. Note that this has an
|
||||
incorrect (very high) length set, which confuses many filters. This is
|
||||
necessary, because the true number of frames is unknown. You can use the
|
||||
``Trim`` filter on the clip to reduce the length.
|
||||
The mpv video source as vapoursynth clip. Note that this has no length
|
||||
set, which confuses many filters. Using ``Trim`` on the clip with a
|
||||
high dummy length can turn it into a finite clip.
|
||||
|
||||
``video_in_dw``, ``video_in_dh``
|
||||
Display size of the video. Can be different from video size if the
|
||||
@@ -559,7 +726,7 @@ Available mpv-only filters are:
|
||||
FPS value as reported by file headers. This value can be wrong or
|
||||
completely broken (e.g. 0 or NaN). Even if the value is correct,
|
||||
if another filter changes the real FPS (by dropping or inserting
|
||||
frames), the value of this variable will not be useful. Note that
|
||||
frames), the value of this variable might not be useful. Note that
|
||||
the ``--fps`` command line option overrides this value.
|
||||
|
||||
Useful for some filters which insist on having a FPS.
|
||||
@@ -567,10 +734,34 @@ Available mpv-only filters are:
|
||||
``display_fps``
|
||||
Refresh rate of the current display. Note that this value can be 0.
|
||||
|
||||
``vapoursynth-lazy``
|
||||
The same as ``vapoursynth``, but doesn't load Python scripts. Instead, a
|
||||
custom backend using Lua and the raw VapourSynth API is used. The syntax
|
||||
is completely different, and absolutely no convenience features are
|
||||
provided. There's no type checking either, and you can trigger crashes.
|
||||
|
||||
.. admonition:: Example:
|
||||
|
||||
::
|
||||
|
||||
video_out = invoke("morpho", "Open", {clip = video_in})
|
||||
|
||||
The special variable ``video_in`` is the mpv video source, while the
|
||||
special variable ``video_out`` is used to read video from. The 1st argument
|
||||
is the plugin (queried with ``getPluginByNs``), the 2nd is the filter name,
|
||||
and the 3rd argument is a table with the arguments. Positional arguments
|
||||
are not supported. The types must match exactly. Since Lua is terrible and
|
||||
can't distinguish integers and floats, integer arguments must be prefixed
|
||||
with ``i_``, in which case the prefix is removed and the argument is cast
|
||||
to an integer. Should the argument's name start with ``i_``, you're out of
|
||||
luck.
|
||||
|
||||
Clips (VSNodeRef) are passed as light userdata, so trying to pass any
|
||||
other userdata type will result in hard crashes.
|
||||
|
||||
``vavpp``
|
||||
VA-API video post processing. Requires the system to support VA-API,
|
||||
i.e. Linux/BSD only. Works with ``--vo=vaapi`` and ``--vo=gpu`` only.
|
||||
Currently deinterlaces. This filter is automatically inserted if
|
||||
VA-AP-API video post processing. Works with ``--vo=vaapi`` and ``--vo=opengl``
|
||||
only. Currently deinterlaces. This filter is automatically inserted if
|
||||
deinterlacing is requested (either using the ``d`` key, by default mapped to
|
||||
the command ``cycle deinterlace``, or the ``--deinterlace`` option).
|
||||
|
||||
@@ -579,22 +770,18 @@ Available mpv-only filters are:
|
||||
|
||||
no
|
||||
Don't perform deinterlacing.
|
||||
auto
|
||||
Select the best quality deinterlacing algorithm (default). This
|
||||
goes by the order of the options as documented, with
|
||||
``motion-compensated`` being considered best quality.
|
||||
first-field
|
||||
Show only first field.
|
||||
bob
|
||||
bob deinterlacing.
|
||||
bob deinterlacing (default).
|
||||
weave, motion-adaptive, motion-compensated
|
||||
Advanced deinterlacing algorithms. Whether these actually work
|
||||
depends on the GPU hardware, the GPU drivers, driver bugs, and
|
||||
mpv bugs.
|
||||
|
||||
``<interlaced-only>``
|
||||
:no: Deinterlace all frames (default).
|
||||
:yes: Only deinterlace frames marked as interlaced.
|
||||
:no: Deinterlace all frames.
|
||||
:yes: Only deinterlace frames marked as interlaced (default).
|
||||
|
||||
``reversal-bug=<yes|no>``
|
||||
:no: Use the API as it was interpreted by older Mesa drivers. While
|
||||
@@ -606,12 +793,12 @@ Available mpv-only filters are:
|
||||
algorithms.
|
||||
|
||||
``vdpaupp``
|
||||
VDPAU video post processing. Works with ``--vo=vdpau`` and ``--vo=gpu``
|
||||
VDPAU video post processing. Works with ``--vo=vdpau`` and ``--vo=opengl``
|
||||
only. This filter is automatically inserted if deinterlacing is requested
|
||||
(either using the ``d`` key, by default mapped to the command
|
||||
``cycle deinterlace``, or the ``--deinterlace`` option). When enabling
|
||||
deinterlacing, it is always preferred over software deinterlacer filters
|
||||
if the ``vdpau`` VO is used, and also if ``gpu`` is used and hardware
|
||||
if the ``vdpau`` VO is used, and also if ``opengl`` is used and hardware
|
||||
decoding was activated at least once (i.e. vdpau was loaded).
|
||||
|
||||
``sharpen=<-1-1>``
|
||||
@@ -649,7 +836,7 @@ Available mpv-only filters are:
|
||||
Try to apply inverse telecine, needs motion adaptive temporal
|
||||
deinterlacing.
|
||||
``interlaced-only=<yes|no>``
|
||||
If ``yes``, only deinterlace frames marked as interlaced (default: no).
|
||||
If ``yes`` (default), only deinterlace frames marked as interlaced.
|
||||
``hqscaling=<0-9>``
|
||||
0
|
||||
Use default VDPAU scaling (default).
|
||||
@@ -663,7 +850,7 @@ Available mpv-only filters are:
|
||||
``deint=<yes|no>``
|
||||
Whether deinterlacing is enabled (default: no).
|
||||
``interlaced-only=<yes|no>``
|
||||
If ``yes``, only deinterlace frames marked as interlaced (default: no).
|
||||
If ``yes`` (default), only deinterlace frames marked as interlaced.
|
||||
``mode=<blend|bob|adaptive|mocomp|ivctc|none>``
|
||||
Tries to select a video processor with the given processing capability.
|
||||
If a video processor supports multiple capabilities, it is not clear
|
||||
@@ -671,109 +858,8 @@ Available mpv-only filters are:
|
||||
most if not all hardware, this option will probably do nothing, because
|
||||
a video processor usually supports all modes or none.
|
||||
|
||||
``fingerprint=...``
|
||||
Compute video frame fingerprints and provide them as metadata. Actually, it
|
||||
currently barely deserved to be called ``fingerprint``, because it does not
|
||||
compute "proper" fingerprints, only tiny downscaled images (but which can be
|
||||
used to compute image hashes or for similarity matching).
|
||||
|
||||
The main purpose of this filter is to support the ``skip-logo.lua`` script.
|
||||
If this script is dropped, or mpv ever gains a way to load user-defined
|
||||
filters (other than VapourSynth), this filter will be removed. Due to the
|
||||
"special" nature of this filter, it will be removed without warning.
|
||||
|
||||
The intended way to read from the filter is using ``vf-metadata`` (also
|
||||
see ``clear-on-query`` filter parameter). The property will return a list
|
||||
of key/value pairs as follows:
|
||||
|
||||
::
|
||||
|
||||
fp0.pts = 1.2345
|
||||
fp0.hex = 1234abcdef...bcde
|
||||
fp1.pts = 1.4567
|
||||
fp1.hex = abcdef1234...6789
|
||||
...
|
||||
fpN.pts = ...
|
||||
fpN.hex = ...
|
||||
type = gray-hex-16x16
|
||||
|
||||
Each ``fp<N>`` entry is for a frame. The ``pts`` entry specifies the
|
||||
timestamp of the frame (within the filter chain; in simple cases this is
|
||||
the same as the display timestamp). The ``hex`` field is the hex encoded
|
||||
fingerprint, whose size and meaning depend on the ``type`` filter option.
|
||||
The ``type`` field has the same value as the option the filter was created
|
||||
with.
|
||||
|
||||
This returns the frames that were filtered since the last query of the
|
||||
property. If ``clear-on-query=no`` was set, a query doesn't reset the list
|
||||
of frames. In both cases, a maximum of 10 frames is returned. If there are
|
||||
more frames, the oldest frames are discarded. Frames are returned in filter
|
||||
order.
|
||||
|
||||
(This doesn't return a structured list for the per-frame details because the
|
||||
internals of the ``vf-metadata`` mechanism suck. The returned format may
|
||||
change in the future.)
|
||||
|
||||
This filter uses zimg for speed and profit. However, it will fallback to
|
||||
libswscale in a number of situations: lesser pixel formats, unaligned data
|
||||
pointers or strides, or if zimg fails to initialize for unknown reasons. In
|
||||
these cases, the filter will use more CPU. Also, it will output different
|
||||
fingerprints, because libswscale cannot perform the full range expansion we
|
||||
normally request from zimg. As a consequence, the filter may be slower and
|
||||
not work correctly in random situations.
|
||||
|
||||
``type=...``
|
||||
What fingerprint to compute. Available types are:
|
||||
|
||||
:gray-hex-8x8: grayscale, 8 bit, 8x8 size
|
||||
:gray-hex-16x16: grayscale, 8 bit, 16x16 size (default)
|
||||
|
||||
Both types simply remove all colors, downscale the image, concatenate
|
||||
all pixel values to a byte array, and convert the array to a hex string.
|
||||
|
||||
``clear-on-query=yes|no``
|
||||
Clear the list of frame fingerprints if the ``vf-metadata`` property for
|
||||
this filter is queried (default: yes). This requires some care by the
|
||||
user. Some types of accesses might query the filter multiple times,
|
||||
which leads to lost frames.
|
||||
|
||||
``print=yes|no``
|
||||
Print computed fingerprints the the terminal (default: no). This is
|
||||
mostly for testing and such. Scripts should use ``vf-metadata`` to
|
||||
read information from this filter instead.
|
||||
|
||||
``gpu=...``
|
||||
Convert video to RGB using the OpenGL renderer normally used with
|
||||
``--vo=gpu``. This requires that the EGL implementation supports off-screen
|
||||
rendering on the default display. (This is the case with Mesa.)
|
||||
|
||||
Sub-options:
|
||||
|
||||
``w=<pixels>``, ``h=<pixels>``
|
||||
Size of the output in pixels (default: 0). If not positive, this will
|
||||
use the size of the first filtered input frame.
|
||||
|
||||
.. warning::
|
||||
|
||||
This is highly experimental. Performance is bad, and it will not work
|
||||
everywhere in the first place. Some features are not supported.
|
||||
|
||||
.. warning::
|
||||
|
||||
This does not do OSD rendering. If you see OSD, then it has been
|
||||
rendered by the VO backend. (Subtitles are rendered by the ``gpu``
|
||||
filter, if possible.)
|
||||
|
||||
.. warning::
|
||||
|
||||
If you use this with encoding mode, keep in mind that encoding mode will
|
||||
convert the RGB filter's output back to yuv420p in software, using the
|
||||
configured software scaler. Using ``zimg`` might improve this, but in
|
||||
any case it might go against your goals when using this filter.
|
||||
|
||||
.. warning::
|
||||
|
||||
Do not use this with ``--vo=gpu``. It will apply filtering twice, since
|
||||
most ``--vo=gpu`` options are unconditionally applied to the ``gpu``
|
||||
filter. There is no mechanism in mpv to prevent this.
|
||||
|
||||
``buffer=<num>``
|
||||
Buffer ``<num>`` frames in the filter chain. This filter is probably pretty
|
||||
useless, except for debugging. (Note that this won't help to smooth out
|
||||
latencies with decoding, because the filter will never output a frame if
|
||||
the buffer isn't full, except on EOF.)
|
||||
+100
-242
@@ -14,7 +14,7 @@ in the list.
|
||||
|
||||
See ``--vo=help`` for a list of compiled-in video output drivers.
|
||||
|
||||
The recommended output driver is ``--vo=gpu``, which is the default. All
|
||||
The recommended output driver is ``--vo=opengl``, which is the default. All
|
||||
other drivers are for compatibility or special purposes. If the default
|
||||
does not work, it will fallback to other drivers (in the same order as
|
||||
listed by ``--vo=help``).
|
||||
@@ -71,9 +71,6 @@ Available video output drivers are:
|
||||
Shared memory video output driver without hardware acceleration that works
|
||||
whenever X11 is present.
|
||||
|
||||
Since mpv 0.30.0, you may need to use ``--profile=sw-fast`` to get decent
|
||||
performance.
|
||||
|
||||
.. note:: This is a fallback only, and should not be normally used.
|
||||
|
||||
``vdpau`` (X11 only)
|
||||
@@ -99,6 +96,29 @@ Available video output drivers are:
|
||||
|
||||
Apply a noise reduction algorithm to the video (default: 0; no noise
|
||||
reduction).
|
||||
``--vo-vdpau-deint=<-4-4>``
|
||||
(Deprecated. See note about ``vdpaupp``.)
|
||||
|
||||
Select deinterlacing mode (default: 0). In older versions (as well as
|
||||
MPlayer/mplayer2) you could use this option to enable deinterlacing.
|
||||
This doesn't work anymore, and deinterlacing is enabled with either
|
||||
the ``d`` key (by default mapped to the command ``cycle deinterlace``),
|
||||
or the ``--deinterlace`` option. Also, to select the default deint mode,
|
||||
you should use something like ``--vf-defaults=vdpaupp:deint-mode=temporal``
|
||||
instead of this sub-option.
|
||||
|
||||
0
|
||||
Pick the ``vdpaupp`` video filter default, which corresponds to 3.
|
||||
1
|
||||
Show only first field.
|
||||
2
|
||||
Bob deinterlacing.
|
||||
3
|
||||
Motion-adaptive temporal deinterlacing. May lead to A/V desync
|
||||
with slow video hardware and/or high resolution.
|
||||
4
|
||||
Motion-adaptive temporal deinterlacing with edge-guided spatial
|
||||
interpolation. Needs fast video hardware.
|
||||
``--vo-vdpau-chroma-deint``
|
||||
(Deprecated. See note about ``vdpaupp``.)
|
||||
|
||||
@@ -181,8 +201,33 @@ Available video output drivers are:
|
||||
.. note:: This driver is for compatibility with systems that don't provide
|
||||
proper OpenGL drivers, and where ANGLE does not perform well.
|
||||
|
||||
.. note:: Before to 0.21.0, ``direct3d_shaders`` and ``direct3d`` were
|
||||
different, with ``direct3d`` not using shader by default. Now
|
||||
both use shaders by default, and ``direct3d_shaders`` is a
|
||||
deprecated alias. Use the ``--vo-direct3d-prefer-stretchrect``
|
||||
or the ``--vo-direct3d-disable-shaders`` options to get the old
|
||||
behavior of ``direct3d``.
|
||||
|
||||
The following global options are supported by this video output:
|
||||
|
||||
``--vo-direct3d-prefer-stretchrect``
|
||||
Use ``IDirect3DDevice9::StretchRect`` over other methods if possible.
|
||||
|
||||
``--vo-direct3d-disable-stretchrect``
|
||||
Never render the video using ``IDirect3DDevice9::StretchRect``.
|
||||
|
||||
``--vo-direct3d-disable-textures``
|
||||
Never render the video using D3D texture rendering. Rendering with
|
||||
textures + shader will still be allowed. Add ``disable-shaders`` to
|
||||
completely disable video rendering with textures.
|
||||
|
||||
``--vo-direct3d-disable-shaders``
|
||||
Never use shaders when rendering video.
|
||||
|
||||
``--vo-direct3d-only-8bit``
|
||||
Never render YUV video with more than 8 bits per component.
|
||||
Using this flag will force software conversion to 8-bit.
|
||||
|
||||
``--vo-direct3d-disable-texture-align``
|
||||
Normally texture sizes are always aligned to 16. With this option
|
||||
enabled, the video texture will always have exactly the same size as
|
||||
@@ -228,34 +273,37 @@ Available video output drivers are:
|
||||
``--vo-direct3d-exact-backbuffer``
|
||||
Always resize the backbuffer to window size.
|
||||
|
||||
``gpu``
|
||||
General purpose, customizable, GPU-accelerated video output driver. It
|
||||
supports extended scaling methods, dithering, color management, custom
|
||||
shaders, HDR, and more.
|
||||
``opengl``
|
||||
OpenGL video output driver. It supports extended scaling methods, dithering
|
||||
and color management.
|
||||
|
||||
See `GPU renderer options`_ for options specific to this VO.
|
||||
See `OpenGL renderer options`_ for options specific to this VO.
|
||||
|
||||
By default, it tries to use fast and fail-safe settings. Use the
|
||||
``gpu-hq`` profile to use this driver with defaults set to high quality
|
||||
rendering. The profile can be applied with ``--profile=gpu-hq`` and its
|
||||
contents can be viewed with ``--show-profile=gpu-hq``.
|
||||
``opengl-hq`` profile to use this driver with defaults set to high
|
||||
quality rendering. (This profile is also the replacement for
|
||||
``--vo=opengl-hq``.) The profile can be applied with ``--profile=opengl-hq``
|
||||
and its contents can be viewed with ``--show-profile=opengl-hq``.
|
||||
|
||||
This VO abstracts over several possible graphics APIs and windowing
|
||||
contexts, which can be influenced using the ``--gpu-api`` and
|
||||
``--gpu-context`` options.
|
||||
Requires at least OpenGL 2.1.
|
||||
|
||||
Some features are available with OpenGL 3 capable graphics drivers only
|
||||
(or if the necessary extensions are available).
|
||||
|
||||
OpenGL ES 2.0 and 3.0 are supported as well.
|
||||
|
||||
Hardware decoding over OpenGL-interop is supported to some degree. Note
|
||||
that in this mode, some corner case might not be gracefully handled, and
|
||||
color space conversion and chroma upsampling is generally in the hand of
|
||||
the hardware decoder APIs.
|
||||
|
||||
``gpu`` makes use of FBOs by default. Sometimes you can achieve better
|
||||
quality or performance by changing the ``--fbo-format`` option to
|
||||
``opengl`` makes use of FBOs by default. Sometimes you can achieve better
|
||||
quality or performance by changing the ``--opengl-fbo-format`` option to
|
||||
``rgb16f``, ``rgb32f`` or ``rgb``. Known problems include Mesa/Intel not
|
||||
accepting ``rgb16``, Mesa sometimes not being compiled with float texture
|
||||
support, and some OS X setups being very slow with ``rgb16`` but fast
|
||||
with ``rgb32f``. If you have problems, you can also try enabling the
|
||||
``--gpu-dumb-mode=yes`` option.
|
||||
``--opengl-dumb-mode=yes`` option.
|
||||
|
||||
``sdl``
|
||||
SDL 2.0+ Render video output driver, depending on system with or without
|
||||
@@ -263,7 +311,7 @@ Available video output drivers are:
|
||||
For tuning, refer to your copy of the file ``SDL_hints.h``.
|
||||
|
||||
.. note:: This driver is for compatibility with systems that don't provide
|
||||
proper graphics drivers.
|
||||
proper graphics drivers, or which support GLES only.
|
||||
|
||||
The following global options are supported by this video output:
|
||||
|
||||
@@ -279,7 +327,7 @@ Available video output drivers are:
|
||||
This is low quality, and has issues with OSD.
|
||||
|
||||
.. note:: This driver is for compatibility with crappy systems. You can
|
||||
use vaapi hardware decoding with ``--vo=gpu`` too.
|
||||
use vaapi hardware decoding with ``--vo=opengl`` too.
|
||||
|
||||
The following global options are supported by this video output:
|
||||
|
||||
@@ -335,16 +383,8 @@ Available video output drivers are:
|
||||
|
||||
``tct``
|
||||
Color Unicode art video output driver that works on a text console.
|
||||
By default depends on support of true color by modern terminals to display
|
||||
the images at full color range, but 256-colors outout is also supported (see
|
||||
below). On Windows it requires an ansi terminal such as mintty.
|
||||
|
||||
Since mpv 0.30.0, you may need to use ``--profile=sw-fast`` to get decent
|
||||
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
|
||||
``--really-quiet`` can help with that.
|
||||
Depends on support of true color by modern terminals to display the images
|
||||
at full color range. On Windows it requires an ansi terminal such as mintty.
|
||||
|
||||
``--vo-tct-algo=<algo>``
|
||||
Select how to write the pixels to the terminal.
|
||||
@@ -363,102 +403,6 @@ Available video output drivers are:
|
||||
``--vo-tct-256=<yes|no>`` (default: no)
|
||||
Use 256 colors - for terminals which don't support true color.
|
||||
|
||||
``sixel``
|
||||
Graphical output for the terminal, using sixels. Tested with ``mlterm`` and
|
||||
``xterm``.
|
||||
|
||||
Note: the Sixel image output is not synchronized with other terminal output
|
||||
from mpv, which can lead to broken images. The option ``--really-quiet``
|
||||
can help with that, and is recommended.
|
||||
|
||||
You may need to use ``--profile=sw-fast`` to get decent performance.
|
||||
|
||||
Note: at the time of writing, ``xterm`` does not enable sixel by default -
|
||||
launching it as ``xterm -ti 340`` is one way to enable it. Also, ``xterm``
|
||||
does not display images bigger than 1000x1000 pixels by default.
|
||||
|
||||
To render and align sixel images correctly, mpv needs to know the terminal
|
||||
size both in cells and in pixels. By default it tries to use values which
|
||||
the terminal reports, however, due to differences between terminals this is
|
||||
an error-prone process which cannot be automated with certainty - some
|
||||
terminals report the size in pixels including the padding - e.g. ``xterm``,
|
||||
while others report the actual usable number of pixels - like ``mlterm``.
|
||||
Additionally, they may behave differently when maximized or in fullscreen,
|
||||
and mpv cannot detect this state using standard methods.
|
||||
|
||||
Sixel size and alignment options:
|
||||
|
||||
``--vo-sixel-cols=<columns>``, ``--vo-sixel-rows=<rows>`` (default: 0)
|
||||
Specify the terminal size in character cells, otherwise (0) read it
|
||||
from the terminal, or fall back to 80x25. Note that mpv doesn't use the
|
||||
the last row with sixel because this seems to result in scrolling.
|
||||
|
||||
``--vo-sixel-width=<width>``, ``--vo-sixel-height=<height>`` (default: 0)
|
||||
Specify the available size in pixels, otherwise (0) read it from the
|
||||
terminal, or fall back to 320x240. Other than excluding the last line,
|
||||
the height is also further rounded down to a multiple of 6 (sixel unit
|
||||
height) to avoid overflowing below the designated size.
|
||||
|
||||
``--vo-sixel-left=<col>``, ``--vo-sixel-top=<row>`` (default: 0)
|
||||
Specify the position in character cells where the image starts (1 is
|
||||
the first column or row). If 0 (default) then try to automatically
|
||||
determine it according to the other values and the image aspect ratio
|
||||
and zoom.
|
||||
|
||||
``--vo-sixel-pad-x=<pad_x>``, ``--vo-sixel-pad-y=<pad_y>`` (default: -1)
|
||||
Used only when mpv reads the size in pixels from the terminal.
|
||||
Specify the number of padding pixels (on one side) which are included
|
||||
at the size which the terminal reports. If -1 (default) then the number
|
||||
of pixels is rounded down to a multiple of number of cells (per axis),
|
||||
to take into account padding at the report - this only works correctly
|
||||
when the overall padding per axis is smaller than the number of cells.
|
||||
|
||||
Sixel image quality options:
|
||||
|
||||
``--vo-sixel-dither=<algo>``
|
||||
Selects the dither algorithm which libsixel should apply.
|
||||
Can be one of the below list as per libsixel's documentation.
|
||||
|
||||
auto
|
||||
Choose diffuse type automatically
|
||||
none
|
||||
Don't diffuse
|
||||
atkinson
|
||||
Diffuse with Bill Atkinson's method. (Default)
|
||||
fs
|
||||
Diffuse with Floyd-Steinberg method
|
||||
jajuni
|
||||
Diffuse with Jarvis, Judice & Ninke method
|
||||
stucki
|
||||
Diffuse with Stucki's method
|
||||
burkes
|
||||
Diffuse with Burkes' method
|
||||
arithmetic
|
||||
Positionally stable arithmetic dither
|
||||
xor
|
||||
Positionally stable arithmetic xor based dither
|
||||
|
||||
``--vo-sixel-fixedpalette=<yes|no>`` (default: yes)
|
||||
Use libsixel's built-in static palette using the XTERM256 profile
|
||||
for dither. Fixed palette uses 256 colors for dithering. Note that
|
||||
using ``no`` (at the time of writing) will slow down ``xterm``.
|
||||
|
||||
``--vo-sixel-reqcolors=<colors>`` (default: 256)
|
||||
Set up libsixel to use required number of colors for dynamic palette.
|
||||
This value depends on the terminal emulator as well. Xterm supports
|
||||
256 colors. Can set this to a lower value for faster performance.
|
||||
This option has no effect if fixed palette is used.
|
||||
|
||||
``--vo-sixel-threshold=<threshold>`` (default: -1)
|
||||
When using a dynamic palette, defines the threshold to change the
|
||||
palette - as percentage of the number of colors, e.g. 20 will change
|
||||
the palette when the number of colors changed by 20%. It's a simple
|
||||
measure to reduce the number of palette changes, because it can be slow
|
||||
in some terminals (``xterm``), however, it seems that in ``mlterm`` it
|
||||
causes image corruption. The default (-1) will change the palette
|
||||
on every frame and will have better quality, and no corruption in
|
||||
``mlterm``.
|
||||
|
||||
``image``
|
||||
Output each frame into an image file in the current directory. Each file
|
||||
takes the frame number padded with leading zeros as name.
|
||||
@@ -474,8 +418,6 @@ Available video output drivers are:
|
||||
JPEG files, extension .jpeg.
|
||||
png
|
||||
PNG files.
|
||||
webp
|
||||
WebP files.
|
||||
|
||||
``--vo-image-png-compression=<0-9>``
|
||||
PNG compression factor (speed vs. file size tradeoff) (default: 7)
|
||||
@@ -486,30 +428,40 @@ Available video output drivers are:
|
||||
JPEG quality factor (default: 90)
|
||||
``--vo-image-jpeg-optimize=<0-100>``
|
||||
JPEG optimization factor (default: 100)
|
||||
``--vo-image-webp-lossless=<yes|no>``
|
||||
Enable writing lossless WebP files (default: no)
|
||||
``--vo-image-webp-quality=<0-100>``
|
||||
WebP quality (default: 75)
|
||||
``--vo-image-webp-compression=<0-6>``
|
||||
WebP compression factor (default: 4)
|
||||
``--vo-image-outdir=<dirname>``
|
||||
Specify the directory to save the image files to (default: ``./``).
|
||||
|
||||
``libmpv``
|
||||
For use with libmpv direct embedding. As a special case, on OS X it
|
||||
is used like a normal VO within mpv (cocoa-cb). Otherwise useless in any
|
||||
other contexts.
|
||||
(See ``<mpv/render.h>``.)
|
||||
``wayland`` (Wayland only)
|
||||
Wayland shared memory video output as fallback for ``opengl``.
|
||||
|
||||
This also supports many of the options the ``gpu`` VO has, depending on the
|
||||
backend.
|
||||
.. note:: This driver is for compatibility with systems that don't provide
|
||||
working OpenGL drivers.
|
||||
|
||||
The following global options are supported by this video output:
|
||||
|
||||
``--vo-wayland-alpha``
|
||||
Use a buffer format that supports videos and images with alpha
|
||||
information
|
||||
``--vo-wayland-rgb565``
|
||||
Use RGB565 as buffer format. This format is implemented on most
|
||||
platforms, especially on embedded where it is far more efficient then
|
||||
RGB8888.
|
||||
``--vo-wayland-triple-buffering``
|
||||
Use 3 buffers instead of 2. This can lead to more fluid playback, but
|
||||
uses more memory.
|
||||
|
||||
``opengl-cb``
|
||||
For use with libmpv direct OpenGL embedding; useless in any other contexts.
|
||||
(See ``<mpv/opengl_cb.h>``.)
|
||||
|
||||
This also supports many of the options the ``opengl`` VO has.
|
||||
|
||||
``rpi`` (Raspberry Pi)
|
||||
Native video output on the Raspberry Pi using the MMAL API.
|
||||
|
||||
This is deprecated. Use ``--vo=gpu`` instead, which is the default and
|
||||
This is deprecated. Use ``--vo=opengl`` instead, which is the default and
|
||||
provides the same functionality. The ``rpi`` VO will be removed in
|
||||
mpv 0.23.0. Its functionality was folded into --vo=gpu, which now uses
|
||||
mpv 0.23.0. Its functionality was folded into --vo=opengl, which now uses
|
||||
RPI hardware decoding by treating it as a hardware overlay (without applying
|
||||
GL filtering). Also to be changed in 0.23.0: the --fs flag will be reset to
|
||||
"no" by default (like on the other platforms).
|
||||
@@ -539,113 +491,19 @@ Available video output drivers are:
|
||||
Video output driver using Kernel Mode Setting / Direct Rendering Manager.
|
||||
Should be used when one doesn't want to install full-blown graphical
|
||||
environment (e.g. no X). Does not support hardware acceleration (if you
|
||||
need this, check the ``drm`` backend for ``gpu`` VO).
|
||||
|
||||
Since mpv 0.30.0, you may need to use ``--profile=sw-fast`` to get decent
|
||||
performance.
|
||||
need this, check the ``drm`` backend for ``opengl`` VO).
|
||||
|
||||
The following global options are supported by this video output:
|
||||
|
||||
``--drm-connector=[<gpu_number>.]<name>``
|
||||
Select the connector to use (usually this is a monitor.) If ``<name>``
|
||||
is empty or ``auto``, mpv renders the output on the first available
|
||||
connector. Use ``--drm-connector=help`` to get a list of available
|
||||
connector. Use ``--drm-connector=help`` to get list of available
|
||||
connectors. When using multiple graphic cards, use the ``<gpu_number>``
|
||||
argument to disambiguate.
|
||||
(default: empty)
|
||||
|
||||
``--drm-mode=<preferred|highest|N|WxH[@R]>``
|
||||
Mode to use (resolution and frame rate).
|
||||
Possible values:
|
||||
``--drm-mode=<number>``
|
||||
Mode ID to use (resolution, bit depth and frame rate).
|
||||
(default: 0)
|
||||
|
||||
:preferred: Use the preferred mode for the screen on the selected
|
||||
connector. (default)
|
||||
:highest: Use the mode with the highest resolution available on the
|
||||
selected connector.
|
||||
:N: Select mode by index.
|
||||
:WxH[@R]: Specify mode by width, height, and optionally refresh rate.
|
||||
In case several modes match, selects the mode that comes
|
||||
first in the EDID list of modes.
|
||||
|
||||
Use ``--drm-mode=help`` to get a list of available modes for all active
|
||||
connectors.
|
||||
|
||||
``--drm-atomic=<no|auto>``
|
||||
Toggle use of atomic modesetting. Mostly useful for debugging.
|
||||
|
||||
:no: Use legacy modesetting.
|
||||
:auto: Use atomic modesetting, falling back to legacy modesetting if
|
||||
not available. (default)
|
||||
|
||||
Note: Only affects ``gpu-context=drm``. ``vo=drm`` supports legacy
|
||||
modesetting only.
|
||||
|
||||
``--drm-draw-plane=<primary|overlay|N>``
|
||||
Select the DRM plane to which video and OSD is drawn to, under normal
|
||||
circumstances. The plane can be specified as ``primary``, which will
|
||||
pick the first applicable primary plane; ``overlay``, which will pick
|
||||
the first applicable overlay plane; or by index. The index is zero
|
||||
based, and related to the CRTC.
|
||||
(default: primary)
|
||||
|
||||
When using this option with the drmprime-drm hwdec interop, only the OSD
|
||||
is rendered to this plane.
|
||||
|
||||
``--drm-drmprime-video-plane=<primary|overlay|N>``
|
||||
Select the DRM plane to use for video with the drmprime-drm hwdec
|
||||
interop (used by e.g. the rkmpp hwdec on RockChip SoCs, and v4l2 hwdec:s
|
||||
on various other SoC:s). The plane is unused otherwise. This option
|
||||
accepts the same values as ``--drm-draw-plane``. (default: overlay)
|
||||
|
||||
To be able to successfully play 4K video on various SoCs you might need
|
||||
to set ``--drm-draw-plane=overlay --drm-drmprime-video-plane=primary``
|
||||
and setting ``--drm-draw-surface-size=1920x1080``, to render the OSD at a
|
||||
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>``
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
``--drm-draw-surface-size=<[WxH]>``
|
||||
Sets the size of the surface used on the draw plane. The surface will
|
||||
then be upscaled to the current screen resolution. This option can be
|
||||
useful when used together with the drmprime-drm hwdec interop at high
|
||||
resolutions, as it allows scaling the draw plane (which in this case
|
||||
only handles the OSD) down to a size the GPU can handle.
|
||||
|
||||
When used without the drmprime-drm hwdec interop this option will just
|
||||
cause the video to get rendered at a different resolution and then
|
||||
scaled to screen size.
|
||||
|
||||
Note: this option is only available with DRM atomic support.
|
||||
(default: display resolution)
|
||||
|
||||
``mediacodec_embed`` (Android)
|
||||
Renders ``IMGFMT_MEDIACODEC`` frames directly to an ``android.view.Surface``.
|
||||
Requires ``--hwdec=mediacodec`` for hardware decoding, along with
|
||||
``--vo=mediacodec_embed`` and ``--wid=(intptr_t)(*android.view.Surface)``.
|
||||
|
||||
Since this video output driver uses native decoding and rendering routines,
|
||||
many of mpv's features (subtitle rendering, OSD/OSC, video filters, etc)
|
||||
are not available with this driver.
|
||||
|
||||
To use hardware decoding with ``--vo=gpu`` instead, use
|
||||
``--hwdec=mediacodec-copy`` along with ``--gpu-context=android``.
|
||||
|
||||
``wlshm`` (Wayland only)
|
||||
Shared memory video output driver without hardware acceleration that works
|
||||
whenever Wayland is present.
|
||||
|
||||
Since mpv 0.30.0, you may need to use ``--profile=sw-fast`` to get decent
|
||||
performance.
|
||||
|
||||
.. note:: This is a fallback only, and should not be normally used.
|
||||
@@ -76,8 +76,8 @@ Video
|
||||
|
||||
* Wayland support.
|
||||
* Native support for VAAPI and VDA. Improved VDPAU video output.
|
||||
* Improved GPU-accelerated video output (see the ``gpu-hq`` preset).
|
||||
* Make hardware decoding work with the ``gpu`` video output.
|
||||
* Improved OpenGL output (see the ``opengl-hq`` video output).
|
||||
* Make hardware decoding work with the ``opengl`` video output.
|
||||
* Support for libavfilter (for video->video and audio->audio). This allows
|
||||
using most of FFmpeg's filters, which improve greatly on the old MPlayer
|
||||
filters in features, performance, and correctness.
|
||||
@@ -85,7 +85,7 @@ Video
|
||||
for BT.2020 (Ultra HD). linear XYZ (Digital Cinema) and SMPTE ST2084 (HDR)
|
||||
inputs.
|
||||
* Support for color managed displays, via ICC profiles.
|
||||
* High-quality image resamplers (see the ``--scale`` suboption).
|
||||
* High-quality image resamplers (see the ``opengl`` ``scale`` suboption).
|
||||
* Support for scaling in (sigmoidized) linear light.
|
||||
* Better subtitle rendering using libass by default.
|
||||
* Improvements when playing multiple files (``-fixed-vo`` is default, do not
|
||||
@@ -144,6 +144,7 @@ Mac OS X
|
||||
* Native OpenGL backend.
|
||||
* Cocoa event loop is independent from MPlayer's event loop, so user
|
||||
actions like accessing menus and live resizing do not block the playback.
|
||||
* Apple Remote support.
|
||||
* Media Keys support.
|
||||
* VDA support using libavcodec hwaccel API instead of FFmpeg's decoder with up
|
||||
to 2-2.5x reduction in CPU usage.
|
||||
@@ -247,7 +248,7 @@ Command Line Switches
|
||||
``-msglevel`` ``--msg-level`` (changed semantics)
|
||||
``-msgmodule`` ``--msg-module``
|
||||
``-name`` ``--x11-name``
|
||||
``-noar`` ``(removed; replaced by MediaPlayer framework)``
|
||||
``-noar`` ``--no-input-appleremote``
|
||||
``-noautosub`` ``--no-sub-auto``
|
||||
``-noconsolecontrols`` ``--no-input-terminal``
|
||||
``-nosound`` ``--no-audio``
|
||||
|
||||
@@ -20,28 +20,24 @@ Release procedure
|
||||
|
||||
While on master:
|
||||
|
||||
- Update the `RELEASE_NOTES` file, replacing the previous release notes.
|
||||
- Update the `RELEASE_NOTES` file.
|
||||
|
||||
- Update the `VERSION` file.
|
||||
|
||||
- Update `DOCS/client-api-changes.rst` and `DOCS/interface-changes.rst`
|
||||
(in particular, update the last version numbers if necessary)
|
||||
|
||||
- Create signed commit with changes.
|
||||
- Commit changes.
|
||||
|
||||
- Create signed tag v0.X.Y.
|
||||
|
||||
- Add -UNKNOWN suffix to version in `VERSION` file.
|
||||
|
||||
- Commit changes, push release branch (`release/0.X`) and tag to GitHub.
|
||||
- Commit changes, push branch and tag to GitHub.
|
||||
|
||||
- Create a new GitHub release using the content of `RELEASE_NOTES` related to
|
||||
the new version.
|
||||
|
||||
If necessary (to e.g. exclude commits already on master), the release can
|
||||
be done on a branch with different commit history. The release branch **must**
|
||||
then be merged to master so `git describe` will pick up the tag.
|
||||
|
||||
Release notes template
|
||||
----------------------
|
||||
|
||||
|
||||
+12
-427
@@ -15,7 +15,7 @@ player/*.c:
|
||||
* 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
|
||||
* call mp_play_files() function that works down the playlist:
|
||||
* call play_files() function that works down the playlist:
|
||||
* run idle loop (idle_loop()), until there are files in the
|
||||
playlist or an exit command was given (only if --idle it set)
|
||||
* actually load and play a file in play_current_file():
|
||||
@@ -30,7 +30,7 @@ player/*.c:
|
||||
* determine next entry on the playlist to play
|
||||
* loop, or exit if no next file or quit is requested
|
||||
(see enum stop_play_reason)
|
||||
* call mp_destroy()
|
||||
* call exit_player_with_rc()
|
||||
* run_playloop():
|
||||
* calls fill_audio_out_buffers()
|
||||
This checks whether new audio needs to be decoded, and pushes it
|
||||
@@ -44,9 +44,9 @@ 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)
|
||||
subsystems
|
||||
- the currently played tracks are in mpctx->current_tracks, and decoder
|
||||
state in track.dec/d_sub
|
||||
state in track.d_video/d_audio/d_sub
|
||||
- the other subsystems rarely call back into the frontend, and the frontend
|
||||
polls them instead (probably a good thing)
|
||||
- one exceptions are wakeup callbacks, which notify a "higher" component
|
||||
@@ -68,7 +68,6 @@ ta.h & ta.c:
|
||||
as in it doesn't require valgrind.) You can enable it by setting the
|
||||
MPV_LEAK_REPORT environment variable to "1":
|
||||
export MPV_LEAK_REPORT=1
|
||||
Or permanently by building with --enable-ta-leak-report.
|
||||
This will list all unfree'd allocations on exit.
|
||||
|
||||
Documentation can be found here:
|
||||
@@ -104,45 +103,13 @@ options/options.h, options/options.c
|
||||
options.c. Most default values for options and MPOpts are in
|
||||
mp_default_opts at the end of options.c.
|
||||
|
||||
MPOpts is unfortunately quite monolithic, but is being incrementally broken
|
||||
up into sub-structs. Many components have their own sub-option structs
|
||||
separate from MPOpts. New options should be bound to the component that uses
|
||||
them. Add a new option table/struct if needed.
|
||||
|
||||
The global MPOpts still contains the sub-structs as fields, which serves to
|
||||
link them to the option parser. For example, an entry like this may be
|
||||
typical:
|
||||
|
||||
{"", OPT_SUBSTRUCT(demux_opts, demux_conf)},
|
||||
|
||||
This directs the option access code to include all options in demux_conf
|
||||
into the global option list, with no prefix (""), and as part of the
|
||||
MPOpts.demux_opts field. The MPOpts.demux_opts field is actually not
|
||||
accessed anywhere, and instead demux.c does this:
|
||||
|
||||
struct m_config_cache *opts_cache =
|
||||
m_config_cache_alloc(demuxer, global, &demux_conf);
|
||||
struct demux_opts *opts = opts_cache->opts;
|
||||
|
||||
... to get a copy of its options.
|
||||
|
||||
See m_config.h (below) how to access options.
|
||||
MPOpts is unfortunately quite monolithic, and virtually accessed by
|
||||
everything.But some components (like video outputs and video filters) have
|
||||
their own sub-option tables separate from MPOpts.
|
||||
|
||||
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.
|
||||
|
||||
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().
|
||||
|
||||
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
|
||||
type that can be passed "down" the component hierarchy. For safety reasons,
|
||||
you should not pass down any pointers to option structs (like MPOpts), but
|
||||
instead pass down mpv_global, and use m_config_cache_alloc() (or similar)
|
||||
to get a synchronized copy of the options.
|
||||
|
||||
input/input.c:
|
||||
This translates keyboard input coming from VOs and other sources (such
|
||||
as remote control devices like Apple IR or client API commands) to the
|
||||
@@ -172,6 +139,9 @@ stream/*:
|
||||
Some stream inputs are just there to invoke special demuxers, like
|
||||
stream_mf.c. (Basically to make the prefix "mf://" do something special.)
|
||||
|
||||
cache.c is a caching wrapper around streams implementations, needed for
|
||||
smooth network playback.
|
||||
|
||||
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
|
||||
@@ -185,10 +155,6 @@ demux/:
|
||||
There is a stream header for each audio/video/sub stream, and each of them
|
||||
holds codec information about the stream and other information.
|
||||
|
||||
demux.c is a bit big, the main reason being that it contains the demuxer
|
||||
cache, which is implemented as a list of packets. The cache is complex
|
||||
because it support seeking, multiple ranges, prefetching, and so on.
|
||||
|
||||
video/:
|
||||
This contains several things related to audio/video decoding, as well as
|
||||
video filters.
|
||||
@@ -211,13 +177,13 @@ 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
|
||||
vo_opengl can pick a windowing system at runtime, e.g. the same binary can
|
||||
provide both X11 and Cocoa support on OSX.
|
||||
|
||||
VOs can be reconfigured at runtime. A vo_reconfig() call can change the video
|
||||
resolution and format, without destroying the window.
|
||||
|
||||
vo_gpu should be taken as reference.
|
||||
vo_opengl should be taken as reference.
|
||||
|
||||
audio/:
|
||||
format.h/format.c define the uncompressed audio formats. (As well as some
|
||||
@@ -274,384 +240,3 @@ sub/:
|
||||
etc/:
|
||||
The file input.conf is actually integrated into the mpv binary by the
|
||||
build system. It contains the default keybindings.
|
||||
|
||||
Best practices and Concepts within mpv
|
||||
======================================
|
||||
|
||||
General contribution etc.
|
||||
-------------------------
|
||||
|
||||
See: DOCS/contribute.md
|
||||
|
||||
Error checking
|
||||
--------------
|
||||
|
||||
If an error is relevant, it should be handled. If it's interesting, log the
|
||||
error. However, mpv often keeps errors silent and reports failures somewhat
|
||||
coarsely by propagating them upwards the caller chain. This is OK, as long as
|
||||
the errors are not very interesting, or would require a developer to debug it
|
||||
anyway (in which case using a debugger would be more convenient, and the
|
||||
developer would need to add temporary debug printfs to get extremely detailed
|
||||
information which would not be appropriate during normal operation).
|
||||
|
||||
Basically, keep a balance on error reporting. But always check them, unless you
|
||||
have a good argument not to.
|
||||
|
||||
Memory allocation errors (OOM) are a special class of errors. Normally such
|
||||
allocation failures are not handled "properly". Instead, abort() is called.
|
||||
(New code should use MP_HANDLE_OOM() for this.) This is done out of laziness and
|
||||
for convenience, and due to the fact that MPlayer/mplayer2 never handled it
|
||||
correctly. (MPlayer varied between handling it correctly, trying to do so but
|
||||
failing, and just not caring, while mplayer2 started using abort() for it.)
|
||||
|
||||
This is justifiable in a number of ways. Error handling paths are notoriously
|
||||
untested and buggy, so merely having them won't make your program more reliable.
|
||||
Having these error handling paths also complicates non-error code, due to the
|
||||
need to roll back state at any point after a memory allocation.
|
||||
|
||||
Take any larger body of code, that is supposed to handle OOM, and test whether
|
||||
the error paths actually work, for example by overriding malloc with a version
|
||||
that randomly fails. You will find bugs quickly, and often they will be very
|
||||
annoying to fix (if you can even reproduce them).
|
||||
|
||||
In addition, a clear indication that something went wrong may be missing. On
|
||||
error your program may exhibit "degraded" behavior by design. Consider a video
|
||||
encoder dropping frames somewhere in the middle of a video due to temporary
|
||||
allocation failures, instead of just exiting with an errors. In other cases, it
|
||||
may open conceptual security holes. Failing fast may be better.
|
||||
|
||||
mpv uses GPU APIs, which may be break on allocation errors (because driver
|
||||
authors will have the same issues as described here), or don't even have a real
|
||||
concept for dealing with OOM (OpenGL).
|
||||
|
||||
libmpv is often used by GUIs, which I predict always break if OOM happens.
|
||||
|
||||
Last but not least, OSes like Linux use "overcommit", which basically means that
|
||||
your program may crash any time OOM happens, even if it doesn't use malloc() at
|
||||
all!
|
||||
|
||||
But still, don't just assume malloc() always succeeds. Use MP_HANDLE_OOM(). The
|
||||
ta* APIs do this for you. The reason for this is that dereferencing a NULL
|
||||
pointer can have security relevant consequences if large offsets are involved.
|
||||
Also, a clear error message is better than a random segfault.
|
||||
|
||||
Some big memory allocations are checked anyway. For example, all code must
|
||||
assume that allocating video frames or packets can fail. (The above example
|
||||
of dropping video frames during encoding is entirely possible in mpv.)
|
||||
|
||||
Undefined behavior
|
||||
------------------
|
||||
|
||||
Undefined behavior (UB) is a concept in the C language. C is famous for being a
|
||||
language that makes it almost impossible to write working code, because
|
||||
undefined behavior is so easily triggered, compilers will happily abuse it to
|
||||
generate "faster" code, debugging tools will shout at you, and sometimes it
|
||||
even means your code doesn't work.
|
||||
|
||||
There is a lot of literature on this topic. Read it.
|
||||
|
||||
(In C's defense, UB exists in other languages too, but since they're not used
|
||||
for low level infrastructure, and/or these languages are at times not rigorously
|
||||
defined, simply nobody cares. However, the C standard committee is still guilty
|
||||
for not addressing this. I'll admit that I can't even tell from the standard's
|
||||
gibberish whether some specific behavior is UB or not. It's written like tax
|
||||
law.)
|
||||
|
||||
In mpv, we generally try to avoid undefined behavior. For one, we want portable
|
||||
and reliable operation. But more importantly, we want clean output from
|
||||
debugging tools, in order to find real bugs more quickly and effectively.
|
||||
|
||||
Avoid the "works in practice" argument. Once debugging tools come into play, or
|
||||
simply when "in practice" stops being true, this will all get back to you in a
|
||||
bad way.
|
||||
|
||||
Global state, library safety
|
||||
----------------------------
|
||||
|
||||
Mutable global state is when code uses global variables that are not read-only.
|
||||
This must be avoided in mpv. Always use context structs that the caller of
|
||||
your code needs to allocate, and whose pointers are passed to your functions.
|
||||
|
||||
Library safety means that your code (or library) can be used by a library
|
||||
without causing conflicts with other library users in the same process. To any
|
||||
piece of code, a "safe" library's API can simply be used, without having to
|
||||
worry about other API users that may be around somewhere.
|
||||
|
||||
Libraries are often not library safe, because they they use global mutable state
|
||||
or other "global" resources. Typical examples include use of signals, simple
|
||||
global variables (like hsearch() in libc), or internal caches not protected by
|
||||
locks.
|
||||
|
||||
A surprisingly high number of libraries are not library safe because they need
|
||||
global initialization. Typically they provide an API function, which
|
||||
"initializes" the library, and which must be called before calling any other
|
||||
API functions. Often, you are to provide global configuration parameters, which
|
||||
can change the behavior of the library. If two libraries A and B use library C,
|
||||
but A and B initialize C with different parameters, something "bad" may happen.
|
||||
In addition, these global initialization functions are often not thread-safe. So
|
||||
if A and B try to initialize C at the same time (from different threads and
|
||||
without knowing about each other), it may cause undefined behavior. (libcurl is
|
||||
a good example of both of these issues. FFmpeg and some TLS libraries used to be
|
||||
affected, but improved.)
|
||||
|
||||
This is so bad because library A and B from the previous example most likely
|
||||
have no way to cooperate, because they're from different authors and have no
|
||||
business knowing each others. They'd need a library D, which wraps library C
|
||||
in a safe way. Unfortunately, typically something worse happens: libraries get
|
||||
"infected" by the unsafeness of its sub-libraries, and export a global init API
|
||||
just to initialize the sub-libraries. In the previous example, libraries A and B
|
||||
would export global init APIs just to init library C, even though the rest of
|
||||
A/B are clean and library safe. (Again, libcurl is an example of this, if you
|
||||
subtract other historic anti-features.)
|
||||
|
||||
The main problem with library safety is that its lack propagates to all
|
||||
libraries using the library.
|
||||
|
||||
We require libmpv to be library safe. This is not really possible, because some
|
||||
libraries are not library safe (FFmpeg, Xlib, partially ALSA). However, for
|
||||
ideological reasons, there is no global init API, and best effort is made to try
|
||||
to avoid problems.
|
||||
|
||||
libmpv has some features that are not library safe, but which are disabled by
|
||||
default (such as terminal usage aka stdout, or JSON IPC blocking SIGPIPE for
|
||||
internal convenience).
|
||||
|
||||
A notable, very disgustingly library unsafe behavior of libmpv is calling
|
||||
abort() on some memory allocation failure. See error checking section.
|
||||
|
||||
Logging
|
||||
-------
|
||||
|
||||
All logging and terminal output in mpv goes through the functions and macros
|
||||
provided in common/msg.h. This is in part for library safety, and in part to
|
||||
make sure users can silence all output, or to redirect the output elsewhere,
|
||||
like a log file or the internal console.lua script.
|
||||
|
||||
Locking
|
||||
-------
|
||||
|
||||
See generally available literature. In mpv, we use pthread for this.
|
||||
|
||||
Always keep locking clean. Don't skip locking just because it will work "in
|
||||
practice". (See undefined behavior section.) If your use case is simple, you may
|
||||
use C11 atomics (osdep/atomic.h for partial C99 support), but most likely you
|
||||
will only hurt yourself and others.
|
||||
|
||||
Always make clear which fields in a struct are protected by which lock. If a
|
||||
field is immutable, or simply not thread-safe (e.g. state for a single worker
|
||||
thread), document it as well.
|
||||
|
||||
Internal mpv APIs are assumed to be not thread-safe by default. If they have
|
||||
special guarantees (such as being usable by more than one thread at a time),
|
||||
these should be explicitly documented.
|
||||
|
||||
All internal mpv APIs must be free of global state. Even if a component is not
|
||||
thread-safe, multiple threads can use _different_ instances of it without any
|
||||
locking.
|
||||
|
||||
On a side note, recursive locks may seem convenient at first, but introduce
|
||||
additional problems with condition variables and locking hierarchies. They
|
||||
should be avoided.
|
||||
|
||||
Locking hierarchy
|
||||
-----------------
|
||||
|
||||
A simple way to avoid deadlocks with classic locking is to define a locking
|
||||
hierarchy or lock order. If all threads acquire locks in the same order, no
|
||||
deadlocks will happen.
|
||||
|
||||
For example, a "leaf" lock is a lock that is below all other locks in the
|
||||
hierarchy. You can acquire it any time, as long as you don't acquire other
|
||||
locks while holding it.
|
||||
|
||||
Unfortunately, C has no way to declare or check the lock order, so you should at
|
||||
least document it.
|
||||
|
||||
In addition, try to avoid exposing locks to the outside. Making the declaration
|
||||
of a lock private to a specific .c file (and _not_ exporting accessors or
|
||||
lock/unlock functions that manipulate the lock) is a good idea. Your component's
|
||||
API may acquire internal locks, but should release them when returning. Keeping
|
||||
the entire locking in a single file makes it easy to check it.
|
||||
|
||||
Avoiding callback hell
|
||||
----------------------
|
||||
|
||||
mpv code is separated in components, like the "frontend" (i.e. MPContext mpctx),
|
||||
VOs, AOs, demuxers, and more. The frontend usually calls "down" the usage
|
||||
hierarchy: mpctx almost on top, then things like vo/ao, and utility code on the
|
||||
very bottom.
|
||||
|
||||
"Callback hell" is when when components call both up and down the hierarchy,
|
||||
which for example leads to accidentally recursion, reentrancy problems, or
|
||||
locking nightmares. This is avoided by (mostly) calling only down the hierarchy.
|
||||
Basically the call graph forms a DAG. The other direction is handled by event
|
||||
queues, wakeup callbacks, and similar mechanisms.
|
||||
|
||||
Typically, a component provides an API, and does not know anything about its
|
||||
user. The API user (component higher in the hierarchy) polls the state of the
|
||||
lower component when needed.
|
||||
|
||||
This also enforces some level of modularization, and with some luck the locking
|
||||
hierarchy. (Basically, locks of lower components automatically become leaf
|
||||
locks.) Another positive effect is simpler memory management.
|
||||
|
||||
(Also see e.g.: http://250bpm.com/blog:24)
|
||||
|
||||
Wakeup callbacks
|
||||
----------------
|
||||
|
||||
This is a common concept in mpv. Even the public API uses it. It's used when an
|
||||
API has internal threads (or otherwise triggers asynchronous events), but the
|
||||
component call hierarchy needs to be kept. The wakeup callback is the only
|
||||
exception to the call hierarchy, and always calls up.
|
||||
|
||||
For example, vo spawns a thread that the API user (the mpv frontend) does not
|
||||
need to know about. vo simply provides a single-threaded API (or that looks like
|
||||
one). This API needs a way to notify the API user of new events. But the vo
|
||||
event producer is on the vo thread - it can't simply invoke a callback back into
|
||||
the API user, because then the API user has to deal with locking, despite not
|
||||
using threads. In addition, this will probably cause problems like mentioned in
|
||||
the "callback hell" section, especially lock order issues.
|
||||
|
||||
The solution is the wakeup callback. It merely unblocks the API user from
|
||||
waiting, and the API user then uses the normal vo API to examine whether or
|
||||
which state changed. As a concept, it documents what a wakeup callback is
|
||||
allowed to do and what not, to avoid the aforementioned problems.
|
||||
|
||||
Generally, you are not allowed to call any API from the wakeup callback. You
|
||||
just do whatever is needed to unblock your thread. For example, if it's waiting
|
||||
on a mutex/condition variable, acquire the mutex, set a change flag, signal
|
||||
the condition variable, unlock, return. (This mutex must not be held when
|
||||
calling the API. It must be a leaf lock.)
|
||||
|
||||
Restricting the wakeup callback like this sidesteps any reentrancy issues and
|
||||
other complexities. The API implementation can simply hold internal (and
|
||||
non-recursive) locks while invoking the wakeup callback.
|
||||
|
||||
The API user still needs to deal with locking (probably), but there's only the
|
||||
need to implement a single "receiver", that can handle the entire API of the
|
||||
used component. (Or multiple APIs - MPContext for example has only 1 wakeup
|
||||
callback that handles all AOs, VOs, input, demuxers, and more. It simple re-runs
|
||||
the playloop.)
|
||||
|
||||
You could get something more advanced by turning this into a message queue. The
|
||||
API would append a message to the queue, and the API user can read it. But then
|
||||
you still need a way to "wakeup" the API user (unless you force the API user
|
||||
to block on your API, which will make things inconvenient for the API user). You
|
||||
also need to worry about what happens if the message queue overruns (you either
|
||||
lose messages or have unbounded memory usage). In the mpv public API, the
|
||||
distinction between message queue and wakeup callback is sort of blurry, because
|
||||
it does provide a message queue, but an additional wakeup callback, so API
|
||||
users are not required to call mpv_wait_event() with a high timeout.
|
||||
|
||||
mpv itself prefers using wakeup callbacks over a generic event queue, because
|
||||
most times an event queue is not needed (or complicates things), and it is
|
||||
better to do it manually.
|
||||
|
||||
(You could still abstract the API user side of wakeup callback handling, and
|
||||
avoid reimplementing it all the time. Although mp_dispatch_queue already
|
||||
provides mechanisms for this.)
|
||||
|
||||
Condition variables
|
||||
-------------------
|
||||
|
||||
They're used whenever a thread needs to wait for something, without nonsense
|
||||
like sleep calls or busy waiting. mpv uses the standard pthread API for this.
|
||||
There's a lot of literature on it. Read it.
|
||||
|
||||
For initial understanding, it may be helpful to know that condition variables
|
||||
are not variables that signal a condition. pthread_cond_t does not have any
|
||||
state per-se. Maybe pthread_cond_t would better be named pthread_interrupt_t,
|
||||
because its sole purpose is to interrupt a thread waiting via pthread_cond_wait()
|
||||
(or similar). The "something" in "waiting for something" can be called
|
||||
predicate (to avoid confusing it with "condition"). Consult literature for the
|
||||
proper terms.
|
||||
|
||||
The very short version is...
|
||||
|
||||
Shared declarations:
|
||||
|
||||
pthread_mutex_t lock;
|
||||
pthread_cond_t cond_var;
|
||||
struct something state_var; // protected by lock, changes signaled by cond_var
|
||||
|
||||
Waiter thread:
|
||||
|
||||
pthread_mutex_lock(&lock);
|
||||
|
||||
// Wait for a change in state_var. We want to wait until predicate_fulfilled()
|
||||
// returns true.
|
||||
// Must be a loop for 2 reasons:
|
||||
// 1. cond_var may be associated with other conditions too
|
||||
// 2. pthread_cond_wait() can have sporadic wakeups
|
||||
while (!predicate_fulfilled(&state_var)) {
|
||||
// This unlocks, waits for cond_var to be signaled, and then locks again.
|
||||
// The _whole_ point of cond_var is that unlocking and waiting for the
|
||||
// signal happens atomically.
|
||||
pthread_cond_wait(&cond_var, &lock);
|
||||
}
|
||||
|
||||
// Here you may react to the state change. The state cannot change
|
||||
// asynchronously as long as you still hold the lock (and didn't release
|
||||
// and reacquire it).
|
||||
// ...
|
||||
|
||||
pthread_mutex_unlock(&lock);
|
||||
|
||||
Signaler thread:
|
||||
|
||||
pthread_mutex_lock(&lock);
|
||||
|
||||
// Something changed. Update the shared variable with the new state.
|
||||
update_state(&state_var);
|
||||
|
||||
// Notify that something changed. This will wake up the waiter thread if
|
||||
// it's blocked in pthread_cond_wait(). If not, nothing happens.
|
||||
pthread_cond_broadcast(&cond_var);
|
||||
|
||||
// Fun fact: good implementations wake up the waiter only when the lock is
|
||||
// released, to reduce kernel scheduling overhead.
|
||||
pthread_mutex_unlock(&lock);
|
||||
|
||||
Some basic rules:
|
||||
1. Always access your state under proper locking
|
||||
2. Always check your predicate before every call to pthread_cond_wait()
|
||||
(And don't call pthread_cond_wait() if the predicate is fulfilled.)
|
||||
3. Always call pthread_cond_wait() in a loop
|
||||
(And only if your predicate failed without releasing the lock..)
|
||||
4. Always call pthread_cond_broadcast()/_signal() inside of its associated
|
||||
lock
|
||||
|
||||
mpv sometimes violates rule 3, and leaves "retrying" (i.e. looping) to the
|
||||
caller.
|
||||
|
||||
Common pitfalls:
|
||||
- Thinking that pthread_cond_t is some kind of semaphore, or holds any
|
||||
application state or the user predicate (it _only_ wakes up threads
|
||||
that are at the same time blocking on pthread_cond_wait() and friends,
|
||||
nothing else)
|
||||
- Changing the predicate, but not updating all pthread_cond_broadcast()/
|
||||
_signal() calls correctly
|
||||
- Forgetting that pthread_cond_wait() unlocks the lock (other threads can
|
||||
and must acquire the lock)
|
||||
- Holding multiple nested locks while trying to wait (=> deadlock, violates
|
||||
the lock order anyway)
|
||||
- Waiting for a predicate correctly, but unlocking/relocking before acting
|
||||
on it (unlocking allows arbitrary state changes)
|
||||
- Confusing which lock/condition var. is used to manage a bit of state
|
||||
|
||||
Generally available literature probably has better examples and explanations.
|
||||
|
||||
Using condition variables the proper way is generally preferred over using more
|
||||
messy variants of them. (Just saying because on win32, "SetEvent" exists, and
|
||||
it's inferior to condition variables. Try to avoid the win32 primitives, even if
|
||||
you're dealing with Windows-only code.)
|
||||
|
||||
Threads
|
||||
-------
|
||||
|
||||
Threading should be conservatively used. Normally, mpv code pretends to be
|
||||
single-threaded, and provides thread-unsafe APIs. Threads are used coarsely,
|
||||
and if you can avoid messing with threads, you should. For example, VOs and AOs
|
||||
do not need to deal with threads normally, even though they run on separate
|
||||
threads. The glue code "isolates" them from any threading issues.
|
||||
File renamed without changes.
-502
@@ -1,502 +0,0 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 2.1, February 1999
|
||||
|
||||
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
[This is the first released version of the Lesser GPL. It also counts
|
||||
as the successor of the GNU Library Public License, version 2, hence
|
||||
the version number 2.1.]
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
Licenses are intended to guarantee your freedom to share and change
|
||||
free software--to make sure the software is free for all its users.
|
||||
|
||||
This license, the Lesser General Public License, applies to some
|
||||
specially designated software packages--typically libraries--of the
|
||||
Free Software Foundation and other authors who decide to use it. You
|
||||
can use it too, but we suggest you first think carefully about whether
|
||||
this license or the ordinary General Public License is the better
|
||||
strategy to use in any particular case, based on the explanations below.
|
||||
|
||||
When we speak of free software, we are referring to freedom of use,
|
||||
not price. Our General Public Licenses are designed to make sure that
|
||||
you have the freedom to distribute copies of free software (and charge
|
||||
for this service if you wish); that you receive source code or can get
|
||||
it if you want it; that you can change the software and use pieces of
|
||||
it in new free programs; and that you are informed that you can do
|
||||
these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
distributors to deny you these rights or to ask you to surrender these
|
||||
rights. These restrictions translate to certain responsibilities for
|
||||
you if you distribute copies of the library or if you modify it.
|
||||
|
||||
For example, if you distribute copies of the library, whether gratis
|
||||
or for a fee, you must give the recipients all the rights that we gave
|
||||
you. You must make sure that they, too, receive or can get the source
|
||||
code. If you link other code with the library, you must provide
|
||||
complete object files to the recipients, so that they can relink them
|
||||
with the library after making changes to the library and recompiling
|
||||
it. And you must show them these terms so they know their rights.
|
||||
|
||||
We protect your rights with a two-step method: (1) we copyright the
|
||||
library, and (2) we offer you this license, which gives you legal
|
||||
permission to copy, distribute and/or modify the library.
|
||||
|
||||
To protect each distributor, we want to make it very clear that
|
||||
there is no warranty for the free library. Also, if the library is
|
||||
modified by someone else and passed on, the recipients should know
|
||||
that what they have is not the original version, so that the original
|
||||
author's reputation will not be affected by problems that might be
|
||||
introduced by others.
|
||||
|
||||
Finally, software patents pose a constant threat to the existence of
|
||||
any free program. We wish to make sure that a company cannot
|
||||
effectively restrict the users of a free program by obtaining a
|
||||
restrictive license from a patent holder. Therefore, we insist that
|
||||
any patent license obtained for a version of the library must be
|
||||
consistent with the full freedom of use specified in this license.
|
||||
|
||||
Most GNU software, including some libraries, is covered by the
|
||||
ordinary GNU General Public License. This license, the GNU Lesser
|
||||
General Public License, applies to certain designated libraries, and
|
||||
is quite different from the ordinary General Public License. We use
|
||||
this license for certain libraries in order to permit linking those
|
||||
libraries into non-free programs.
|
||||
|
||||
When a program is linked with a library, whether statically or using
|
||||
a shared library, the combination of the two is legally speaking a
|
||||
combined work, a derivative of the original library. The ordinary
|
||||
General Public License therefore permits such linking only if the
|
||||
entire combination fits its criteria of freedom. The Lesser General
|
||||
Public License permits more lax criteria for linking other code with
|
||||
the library.
|
||||
|
||||
We call this license the "Lesser" General Public License because it
|
||||
does Less to protect the user's freedom than the ordinary General
|
||||
Public License. It also provides other free software developers Less
|
||||
of an advantage over competing non-free programs. These disadvantages
|
||||
are the reason we use the ordinary General Public License for many
|
||||
libraries. However, the Lesser license provides advantages in certain
|
||||
special circumstances.
|
||||
|
||||
For example, on rare occasions, there may be a special need to
|
||||
encourage the widest possible use of a certain library, so that it becomes
|
||||
a de-facto standard. To achieve this, non-free programs must be
|
||||
allowed to use the library. A more frequent case is that a free
|
||||
library does the same job as widely used non-free libraries. In this
|
||||
case, there is little to gain by limiting the free library to free
|
||||
software only, so we use the Lesser General Public License.
|
||||
|
||||
In other cases, permission to use a particular library in non-free
|
||||
programs enables a greater number of people to use a large body of
|
||||
free software. For example, permission to use the GNU C Library in
|
||||
non-free programs enables many more people to use the whole GNU
|
||||
operating system, as well as its variant, the GNU/Linux operating
|
||||
system.
|
||||
|
||||
Although the Lesser General Public License is Less protective of the
|
||||
users' freedom, it does ensure that the user of a program that is
|
||||
linked with the Library has the freedom and the wherewithal to run
|
||||
that program using a modified version of the Library.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow. Pay close attention to the difference between a
|
||||
"work based on the library" and a "work that uses the library". The
|
||||
former contains code derived from the library, whereas the latter must
|
||||
be combined with the library in order to run.
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License Agreement applies to any software library or other
|
||||
program which contains a notice placed by the copyright holder or
|
||||
other authorized party saying it may be distributed under the terms of
|
||||
this Lesser General Public License (also called "this License").
|
||||
Each licensee is addressed as "you".
|
||||
|
||||
A "library" means a collection of software functions and/or data
|
||||
prepared so as to be conveniently linked with application programs
|
||||
(which use some of those functions and data) to form executables.
|
||||
|
||||
The "Library", below, refers to any such software library or work
|
||||
which has been distributed under these terms. A "work based on the
|
||||
Library" means either the Library or any derivative work under
|
||||
copyright law: that is to say, a work containing the Library or a
|
||||
portion of it, either verbatim or with modifications and/or translated
|
||||
straightforwardly into another language. (Hereinafter, translation is
|
||||
included without limitation in the term "modification".)
|
||||
|
||||
"Source code" for a work means the preferred form of the work for
|
||||
making modifications to it. For a library, complete source code means
|
||||
all the source code for all modules it contains, plus any associated
|
||||
interface definition files, plus the scripts used to control compilation
|
||||
and installation of the library.
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running a program using the Library is not restricted, and output from
|
||||
such a program is covered only if its contents constitute a work based
|
||||
on the Library (independent of the use of the Library in a tool for
|
||||
writing it). Whether that is true depends on what the Library does
|
||||
and what the program that uses the Library does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Library's
|
||||
complete source code as you receive it, in any medium, provided that
|
||||
you conspicuously and appropriately publish on each copy an
|
||||
appropriate copyright notice and disclaimer of warranty; keep intact
|
||||
all the notices that refer to this License and to the absence of any
|
||||
warranty; and distribute a copy of this License along with the
|
||||
Library.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy,
|
||||
and you may at your option offer warranty protection in exchange for a
|
||||
fee.
|
||||
|
||||
2. You may modify your copy or copies of the Library or any portion
|
||||
of it, thus forming a work based on the Library, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) The modified work must itself be a software library.
|
||||
|
||||
b) You must cause the files modified to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
c) You must cause the whole of the work to be licensed at no
|
||||
charge to all third parties under the terms of this License.
|
||||
|
||||
d) If a facility in the modified Library refers to a function or a
|
||||
table of data to be supplied by an application program that uses
|
||||
the facility, other than as an argument passed when the facility
|
||||
is invoked, then you must make a good faith effort to ensure that,
|
||||
in the event an application does not supply such function or
|
||||
table, the facility still operates, and performs whatever part of
|
||||
its purpose remains meaningful.
|
||||
|
||||
(For example, a function in a library to compute square roots has
|
||||
a purpose that is entirely well-defined independent of the
|
||||
application. Therefore, Subsection 2d requires that any
|
||||
application-supplied function or table used by this function must
|
||||
be optional: if the application does not supply it, the square
|
||||
root function must still compute square roots.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Library,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Library, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote
|
||||
it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Library.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Library
|
||||
with the Library (or with a work based on the Library) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may opt to apply the terms of the ordinary GNU General Public
|
||||
License instead of this License to a given copy of the Library. To do
|
||||
this, you must alter all the notices that refer to this License, so
|
||||
that they refer to the ordinary GNU General Public License, version 2,
|
||||
instead of to this License. (If a newer version than version 2 of the
|
||||
ordinary GNU General Public License has appeared, then you can specify
|
||||
that version instead if you wish.) Do not make any other change in
|
||||
these notices.
|
||||
|
||||
Once this change is made in a given copy, it is irreversible for
|
||||
that copy, so the ordinary GNU General Public License applies to all
|
||||
subsequent copies and derivative works made from that copy.
|
||||
|
||||
This option is useful when you wish to copy part of the code of
|
||||
the Library into a program that is not a library.
|
||||
|
||||
4. You may copy and distribute the Library (or a portion or
|
||||
derivative of it, under Section 2) in object code or executable form
|
||||
under the terms of Sections 1 and 2 above provided that you accompany
|
||||
it with the complete corresponding machine-readable source code, which
|
||||
must be distributed under the terms of Sections 1 and 2 above on a
|
||||
medium customarily used for software interchange.
|
||||
|
||||
If distribution of object code is made by offering access to copy
|
||||
from a designated place, then offering equivalent access to copy the
|
||||
source code from the same place satisfies the requirement to
|
||||
distribute the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
5. A program that contains no derivative of any portion of the
|
||||
Library, but is designed to work with the Library by being compiled or
|
||||
linked with it, is called a "work that uses the Library". Such a
|
||||
work, in isolation, is not a derivative work of the Library, and
|
||||
therefore falls outside the scope of this License.
|
||||
|
||||
However, linking a "work that uses the Library" with the Library
|
||||
creates an executable that is a derivative of the Library (because it
|
||||
contains portions of the Library), rather than a "work that uses the
|
||||
library". The executable is therefore covered by this License.
|
||||
Section 6 states terms for distribution of such executables.
|
||||
|
||||
When a "work that uses the Library" uses material from a header file
|
||||
that is part of the Library, the object code for the work may be a
|
||||
derivative work of the Library even though the source code is not.
|
||||
Whether this is true is especially significant if the work can be
|
||||
linked without the Library, or if the work is itself a library. The
|
||||
threshold for this to be true is not precisely defined by law.
|
||||
|
||||
If such an object file uses only numerical parameters, data
|
||||
structure layouts and accessors, and small macros and small inline
|
||||
functions (ten lines or less in length), then the use of the object
|
||||
file is unrestricted, regardless of whether it is legally a derivative
|
||||
work. (Executables containing this object code plus portions of the
|
||||
Library will still fall under Section 6.)
|
||||
|
||||
Otherwise, if the work is a derivative of the Library, you may
|
||||
distribute the object code for the work under the terms of Section 6.
|
||||
Any executables containing that work also fall under Section 6,
|
||||
whether or not they are linked directly with the Library itself.
|
||||
|
||||
6. As an exception to the Sections above, you may also combine or
|
||||
link a "work that uses the Library" with the Library to produce a
|
||||
work containing portions of the Library, and distribute that work
|
||||
under terms of your choice, provided that the terms permit
|
||||
modification of the work for the customer's own use and reverse
|
||||
engineering for debugging such modifications.
|
||||
|
||||
You must give prominent notice with each copy of the work that the
|
||||
Library is used in it and that the Library and its use are covered by
|
||||
this License. You must supply a copy of this License. If the work
|
||||
during execution displays copyright notices, you must include the
|
||||
copyright notice for the Library among them, as well as a reference
|
||||
directing the user to the copy of this License. Also, you must do one
|
||||
of these things:
|
||||
|
||||
a) Accompany the work with the complete corresponding
|
||||
machine-readable source code for the Library including whatever
|
||||
changes were used in the work (which must be distributed under
|
||||
Sections 1 and 2 above); and, if the work is an executable linked
|
||||
with the Library, with the complete machine-readable "work that
|
||||
uses the Library", as object code and/or source code, so that the
|
||||
user can modify the Library and then relink to produce a modified
|
||||
executable containing the modified Library. (It is understood
|
||||
that the user who changes the contents of definitions files in the
|
||||
Library will not necessarily be able to recompile the application
|
||||
to use the modified definitions.)
|
||||
|
||||
b) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (1) uses at run time a
|
||||
copy of the library already present on the user's computer system,
|
||||
rather than copying library functions into the executable, and (2)
|
||||
will operate properly with a modified version of the library, if
|
||||
the user installs one, as long as the modified version is
|
||||
interface-compatible with the version that the work was made with.
|
||||
|
||||
c) Accompany the work with a written offer, valid for at
|
||||
least three years, to give the same user the materials
|
||||
specified in Subsection 6a, above, for a charge no more
|
||||
than the cost of performing this distribution.
|
||||
|
||||
d) If distribution of the work is made by offering access to copy
|
||||
from a designated place, offer equivalent access to copy the above
|
||||
specified materials from the same place.
|
||||
|
||||
e) Verify that the user has already received a copy of these
|
||||
materials or that you have already sent this user a copy.
|
||||
|
||||
For an executable, the required form of the "work that uses the
|
||||
Library" must include any data and utility programs needed for
|
||||
reproducing the executable from it. However, as a special exception,
|
||||
the materials to be distributed need not include anything that is
|
||||
normally distributed (in either source or binary form) with the major
|
||||
components (compiler, kernel, and so on) of the operating system on
|
||||
which the executable runs, unless that component itself accompanies
|
||||
the executable.
|
||||
|
||||
It may happen that this requirement contradicts the license
|
||||
restrictions of other proprietary libraries that do not normally
|
||||
accompany the operating system. Such a contradiction means you cannot
|
||||
use both them and the Library together in an executable that you
|
||||
distribute.
|
||||
|
||||
7. You may place library facilities that are a work based on the
|
||||
Library side-by-side in a single library together with other library
|
||||
facilities not covered by this License, and distribute such a combined
|
||||
library, provided that the separate distribution of the work based on
|
||||
the Library and of the other library facilities is otherwise
|
||||
permitted, and provided that you do these two things:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work
|
||||
based on the Library, uncombined with any other library
|
||||
facilities. This must be distributed under the terms of the
|
||||
Sections above.
|
||||
|
||||
b) Give prominent notice with the combined library of the fact
|
||||
that part of it is a work based on the Library, and explaining
|
||||
where to find the accompanying uncombined form of the same work.
|
||||
|
||||
8. You may not copy, modify, sublicense, link with, or distribute
|
||||
the Library except as expressly provided under this License. Any
|
||||
attempt otherwise to copy, modify, sublicense, link with, or
|
||||
distribute the Library is void, and will automatically terminate your
|
||||
rights under this License. However, parties who have received copies,
|
||||
or rights, from you under this License will not have their licenses
|
||||
terminated so long as such parties remain in full compliance.
|
||||
|
||||
9. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Library or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Library (or any work based on the
|
||||
Library), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Library or works based on it.
|
||||
|
||||
10. Each time you redistribute the Library (or any work based on the
|
||||
Library), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute, link with or modify the Library
|
||||
subject to these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties with
|
||||
this License.
|
||||
|
||||
11. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Library at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Library by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Library.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under any
|
||||
particular circumstance, the balance of the section is intended to apply,
|
||||
and the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
12. If the distribution and/or use of the Library is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Library under this License may add
|
||||
an explicit geographical distribution limitation excluding those countries,
|
||||
so that distribution is permitted only in or among countries not thus
|
||||
excluded. In such case, this License incorporates the limitation as if
|
||||
written in the body of this License.
|
||||
|
||||
13. The Free Software Foundation may publish revised and/or new
|
||||
versions of the Lesser General Public License from time to time.
|
||||
Such new versions will be similar in spirit to the present version,
|
||||
but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Library
|
||||
specifies a version number of this License which applies to it and
|
||||
"any later version", you have the option of following the terms and
|
||||
conditions either of that version or of any later version published by
|
||||
the Free Software Foundation. If the Library does not specify a
|
||||
license version number, you may choose any version ever published by
|
||||
the Free Software Foundation.
|
||||
|
||||
14. If you wish to incorporate parts of the Library into other free
|
||||
programs whose distribution conditions are incompatible with these,
|
||||
write to the author to ask for permission. For software which is
|
||||
copyrighted by the Free Software Foundation, write to the Free
|
||||
Software Foundation; we sometimes make exceptions for this. Our
|
||||
decision will be guided by the two goals of preserving the free status
|
||||
of all derivatives of our free software and of promoting the sharing
|
||||
and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
|
||||
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
|
||||
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
||||
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
|
||||
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
|
||||
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
||||
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
|
||||
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
||||
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Libraries
|
||||
|
||||
If you develop a new library, and you want it to be of the greatest
|
||||
possible use to the public, we recommend making it free software that
|
||||
everyone can redistribute and change. You can do so by permitting
|
||||
redistribution under these terms (or, alternatively, under the terms of the
|
||||
ordinary General Public License).
|
||||
|
||||
To apply these terms, attach the following notices to the library. It is
|
||||
safest to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least the
|
||||
"copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the library's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This library 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.
|
||||
|
||||
This library 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 this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the library, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the
|
||||
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1990
|
||||
Ty Coon, President of Vice
|
||||
|
||||
That's all there is to it!
|
||||
@@ -1,59 +1,51 @@
|
||||

|
||||

|
||||
|
||||
# mpv
|
||||
## mpv
|
||||
|
||||
--------------
|
||||
|
||||
|
||||
* [External links](#external-links)
|
||||
* [Overview](#overview)
|
||||
* [System requirements](#system-requirements)
|
||||
* [Downloads](#downloads)
|
||||
* [Changelog](#changelog)
|
||||
* [Compilation](#compilation)
|
||||
* [FFmpeg vs. Libav](#ffmpeg-vs-libav)
|
||||
* [Release cycle](#release-cycle)
|
||||
* [Bug reports](#bug-reports)
|
||||
* [Contributing](#contributing)
|
||||
* [License](#license)
|
||||
* [Contact](#contact)
|
||||
|
||||
|
||||
## External links
|
||||
|
||||
|
||||
* [Relation to MPlayer and mplayer2](#relation-to-mplayer-and-mplayer2)
|
||||
* [Wiki](https://github.com/mpv-player/mpv/wiki)
|
||||
* [FAQ][FAQ]
|
||||
* [Manual](http://mpv.io/manual/master/)
|
||||
|
||||
* [FAQ](https://github.com/mpv-player/mpv/wiki/FAQ)
|
||||
* [Man pages](http://mpv.io/manual/master/)
|
||||
* [Contact](#contact)
|
||||
* [License](#license)
|
||||
|
||||
## Overview
|
||||
|
||||
|
||||
**mpv** is a free (as in freedom) media player for the command line. It supports
|
||||
a wide variety of media file formats, audio and video codecs, and subtitle types.
|
||||
|
||||
There is a [FAQ][FAQ].
|
||||
**mpv** is a media player based on MPlayer and mplayer2. It supports a wide
|
||||
variety of video file formats, audio and video codecs, and subtitle types.
|
||||
|
||||
Releases can be found on the [release list][releases].
|
||||
|
||||
## System requirements
|
||||
|
||||
- A not too ancient Linux, Windows 7 or later, or OSX 10.8 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,
|
||||
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
|
||||
recommended or supported.
|
||||
- A not too ancient Linux, or Windows Vista or later, or OSX 10.8 or later.
|
||||
- A somewhat capable CPU. Hardware decoding might sometimes help if the CPU
|
||||
is too slow to decode video realtime, but must be explicitly enabled with
|
||||
the `--hwdec` option.
|
||||
- A not too crappy GPU. mpv is not intended to be used with bad GPUs. There are
|
||||
many caveats with drivers or system compositors causing tearing, stutter,
|
||||
etc. 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 recommended or supported.
|
||||
|
||||
|
||||
## Downloads
|
||||
|
||||
|
||||
For semi-official builds and third-party packages please see
|
||||
[mpv.io/installation](http://mpv.io/installation/).
|
||||
[mpv.io](http://mpv.io/installation/).
|
||||
|
||||
## Changelog
|
||||
|
||||
@@ -75,9 +67,9 @@ Changes to the default key bindings are indicated in
|
||||
Compiling with full features requires development files for several
|
||||
external libraries. Below is a list of some important requirements.
|
||||
|
||||
The mpv build system uses [waf](https://waf.io/), but we don't store it in the
|
||||
repository. The `./bootstrap.py` script will download the latest version
|
||||
of waf that was tested with the build system.
|
||||
The mpv build system uses *waf*, but we don't store it in your source tree. The
|
||||
script './bootstrap.py' will download the latest version of waf that was tested
|
||||
with the build system.
|
||||
|
||||
For a list of the available build options use `./waf configure --help`. If
|
||||
you think you have support for some feature installed but configure fails to
|
||||
@@ -109,15 +101,19 @@ Essential dependencies (incomplete list):
|
||||
- Audio output development headers (libasound/ALSA, pulseaudio)
|
||||
- FFmpeg libraries (libavutil libavcodec libavformat libswscale libavfilter
|
||||
and either libswresample or libavresample)
|
||||
At least FFmpeg 3.2.2 or Libav 12 is required.
|
||||
For hardware decoding with vaapi and vdpau, FFmpeg 3.3 or Libav git is
|
||||
required.
|
||||
- zlib
|
||||
- iconv (normally provided by the system libc)
|
||||
- libass (OSD, OSC, text subtitles)
|
||||
- Lua (optional, required for the OSC pseudo-GUI and youtube-dl integration)
|
||||
- libjpeg (optional, used for screenshots only)
|
||||
- uchardet (optional, for subtitle charset detection)
|
||||
- nvdec and vaapi libraries for hardware decoding on Linux (optional)
|
||||
- vdpau and vaapi libraries for hardware decoding on Linux (optional)
|
||||
(FFmpeg 3.3 or Libav git is also required.)
|
||||
|
||||
Libass dependencies (when building libass):
|
||||
Libass dependencies:
|
||||
|
||||
- gcc or clang, yasm on x86 and x86_64
|
||||
- fribidi, freetype, fontconfig development headers (for libass)
|
||||
@@ -125,29 +121,62 @@ Libass dependencies (when building libass):
|
||||
particularly for correct rendering of non-English text on OSX, and
|
||||
Arabic/Indic scripts on any platform)
|
||||
|
||||
FFmpeg dependencies (when building FFmpeg):
|
||||
FFmpeg dependencies:
|
||||
|
||||
- gcc or clang, yasm on x86 and x86_64
|
||||
- OpenSSL or GnuTLS (have to be explicitly enabled when compiling FFmpeg)
|
||||
- libx264/libmp3lame/libfdk-aac if you want to use encoding (have to be
|
||||
explicitly enabled when compiling FFmpeg)
|
||||
- For native DASH playback, FFmpeg needs to be built with --enable-libxml2
|
||||
(although there are security implications, and DASH support has lots of bugs).
|
||||
- AV1 decoding support requires dav1d.
|
||||
- For good nvidia support on Linux, make sure nv-codec-headers is installed
|
||||
and can be found by configure.
|
||||
- Libav also works, but some features will not work. (See section below.)
|
||||
|
||||
Most of the above libraries are available in suitable versions on normal
|
||||
Linux distributions. For ease of compiling the latest git master of everything,
|
||||
you may wish to use the separately available build wrapper ([mpv-build][mpv-build])
|
||||
which first compiles FFmpeg libraries and libass, and then compiles the player
|
||||
statically linked against those.
|
||||
Linux distributions. However, FFmpeg is an exception (distro versions may be
|
||||
too old to work well or at all). For that reason you may want to use
|
||||
the separately available build wrapper ([mpv-build][mpv-build]) that first
|
||||
compiles FFmpeg libraries and libass, and then compiles the player statically
|
||||
linked against those.
|
||||
|
||||
If you want to build a Windows binary, you either have to use MSYS2 and MinGW,
|
||||
or cross-compile from Linux with MinGW. See
|
||||
[Windows compilation][windows_compilation].
|
||||
|
||||
|
||||
## FFmpeg vs. Libav
|
||||
|
||||
|
||||
Generally, mpv should work with the latest release as well as the git version
|
||||
of both FFmpeg and Libav. But FFmpeg is preferred, and some mpv features work
|
||||
with FFmpeg only (subtitle formats in particular).
|
||||
|
||||
|
||||
## Preferred FFmpeg version
|
||||
|
||||
|
||||
Using the latest FFmpeg release (or FFmpeg git master) is strongly recommended.
|
||||
Older versions are unsupported, even if the build system still happens to
|
||||
accept them. The main reason mpv still builds with older FFmpeg versions is to
|
||||
evade arguing with people (users, distros) who insist on using older FFmpeg
|
||||
versions for no rational reason.
|
||||
|
||||
If you want to use a stable FFmpeg release, use the latest release, which has
|
||||
most likely the best maintenance out of all stable releases. Older releases
|
||||
are for distros, and at best receive basic changes, like fixing critical security
|
||||
issues or build fixes, and at worst are completely abandoned.
|
||||
|
||||
## FFmpeg ABI compatibility
|
||||
|
||||
mpv does not support linking against FFmpeg versions it was not built with, even
|
||||
if the linked version is supposedly ABI-compatible with the version it was
|
||||
compiled against. Expect malfunctions, crashes, and security issues if you
|
||||
do it anyway.
|
||||
|
||||
The reason for not supporting this is because it creates far too much complexity
|
||||
with little to no benefit, coupled with absurd and unusable FFmpeg API
|
||||
artifacts.
|
||||
|
||||
Newer mpv versions will refuse to start if runtime and compile time FFmpeg
|
||||
library versions mismatch.
|
||||
|
||||
## Release cycle
|
||||
|
||||
Every other month, an arbitrary git snapshot is made, and is assigned
|
||||
@@ -174,8 +203,6 @@ recommended (see [Contact](#Contact) below).
|
||||
## Contributing
|
||||
|
||||
|
||||
Please read [contribute.md][contribute.md].
|
||||
|
||||
For small changes you can just send us pull requests through GitHub. For bigger
|
||||
changes come and talk to us on IRC before you start working on them. It will
|
||||
make code review easier for both parties later on.
|
||||
@@ -184,33 +211,41 @@ You can check [the wiki](https://github.com/mpv-player/mpv/wiki/Stuff-to-do)
|
||||
or the [issue tracker](https://github.com/mpv-player/mpv/issues?q=is%3Aopen+is%3Aissue+label%3A%22feature+request%22)
|
||||
for ideas on what you could contribute with.
|
||||
|
||||
## License
|
||||
## Relation to MPlayer and mplayer2
|
||||
|
||||
GPLv2 "or later" by default, LGPLv2.1 "or later" with `--enable-lgpl`.
|
||||
See [details.](https://github.com/mpv-player/mpv/blob/master/Copyright)
|
||||
mpv is a fork of MPlayer. Much has changed, and in general, mpv should be
|
||||
considered a completely new program, rather than a MPlayer drop-in replacement.
|
||||
|
||||
## History
|
||||
For details see [FAQ entry](https://github.com/mpv-player/mpv/wiki/FAQ#How_is_mpv_related_to_MPlayer).
|
||||
|
||||
This software is based on the MPlayer project. Before mpv existed as a project,
|
||||
the code base was briefly developed under the mplayer2 project. For details,
|
||||
see the [FAQ][FAQ].
|
||||
If you are wondering what's different from mplayer2 and MPlayer, an incomplete
|
||||
and largely unmaintained list of changes is located [here][mplayer-changes].
|
||||
|
||||
## Contact
|
||||
|
||||
|
||||
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.freenode.net`
|
||||
- **Developer IRC Channel**: `#mpv-devel` on `irc.freenode.net`
|
||||
- **GitHub issue tracker**: [issue tracker][issue-tracker] (report bugs here)
|
||||
- **User IRC Channel**: `#mpv` on `irc.freenode.net`
|
||||
- **Developer IRC Channel**: `#mpv-devel` on `irc.freenode.net`
|
||||
|
||||
To contact the `mpv` team in private write to `mpv-team@googlegroups.com`. Use
|
||||
only if discretion is required.
|
||||
|
||||
[FAQ]: https://github.com/mpv-player/mpv/wiki/FAQ
|
||||
[releases]: https://github.com/mpv-player/mpv/releases
|
||||
[mpv-build]: https://github.com/mpv-player/mpv-build
|
||||
[homebrew-mpv]: https://github.com/mpv-player/homebrew-mpv
|
||||
[issue-tracker]: https://github.com/mpv-player/mpv/issues
|
||||
[ffmpeg_vs_libav]: https://github.com/mpv-player/mpv/wiki/FFmpeg-versus-Libav
|
||||
[release-policy]: https://github.com/mpv-player/mpv/blob/master/DOCS/release-policy.md
|
||||
[windows_compilation]: https://github.com/mpv-player/mpv/blob/master/DOCS/compile-windows.md
|
||||
[mplayer-changes]: https://github.com/mpv-player/mpv/blob/master/DOCS/mplayer-changes.rst
|
||||
[interface-changes]: https://github.com/mpv-player/mpv/blob/master/DOCS/interface-changes.rst
|
||||
[api-changes]: https://github.com/mpv-player/mpv/blob/master/DOCS/client-api-changes.rst
|
||||
[restore-old-bindings]: https://github.com/mpv-player/mpv/blob/master/etc/restore-old-bindings.conf
|
||||
[contribute.md]: https://github.com/mpv-player/mpv/blob/master/DOCS/contribute.md
|
||||
|
||||
## License
|
||||
|
||||
|
||||
Mostly GPLv2 or later. See [details.](https://github.com/mpv-player/mpv/blob/master/Copyright)
|
||||
+2808
-112
File diff suppressed because it is too large.
Load diff
@@ -10,15 +10,13 @@ export PYTHON=/usr/bin/python3
|
||||
"$PYTHON" bootstrap.py
|
||||
"$PYTHON" waf configure \
|
||||
--check-c-compiler=gcc \
|
||||
--enable-spirv-cross \
|
||||
--enable-d3d-hwaccel \
|
||||
--enable-d3d11 \
|
||||
--disable-cdda \
|
||||
--enable-egl-angle \
|
||||
--enable-jpeg \
|
||||
--enable-lcms2 \
|
||||
--enable-libarchive \
|
||||
--enable-libass \
|
||||
--enable-lua \
|
||||
--enable-rubberband \
|
||||
--enable-shaderc \
|
||||
--enable-uchardet \
|
||||
--enable-vulkan
|
||||
--enable-uchardet
|
||||
"$PYTHON" waf build
|
||||
+12
-49
@@ -1,7 +1,14 @@
|
||||
#!/usr/bin/bash
|
||||
set -e
|
||||
|
||||
export PYTHON=/usr/bin/python3
|
||||
case $MSYSTEM in
|
||||
MINGW32)
|
||||
export MINGW_PACKAGE_PREFIX=mingw-w64-i686
|
||||
;;
|
||||
MINGW64)
|
||||
export MINGW_PACKAGE_PREFIX=mingw-w64-x86_64
|
||||
;;
|
||||
esac
|
||||
|
||||
# Write an empty fonts.conf to speed up fc-cache
|
||||
export FONTCONFIG_FILE=/dummy-fonts.conf
|
||||
@@ -13,60 +20,16 @@ EOF
|
||||
|
||||
# Install build dependencies for mpv
|
||||
pacman -S --noconfirm --needed \
|
||||
$MINGW_PACKAGE_PREFIX-toolchain \
|
||||
$MINGW_PACKAGE_PREFIX-cmake \
|
||||
$MINGW_PACKAGE_PREFIX-gcc \
|
||||
$MINGW_PACKAGE_PREFIX-angleproject-git \
|
||||
$MINGW_PACKAGE_PREFIX-ffmpeg \
|
||||
$MINGW_PACKAGE_PREFIX-lcms2 \
|
||||
$MINGW_PACKAGE_PREFIX-libarchive \
|
||||
$MINGW_PACKAGE_PREFIX-libass \
|
||||
$MINGW_PACKAGE_PREFIX-libjpeg-turbo \
|
||||
$MINGW_PACKAGE_PREFIX-libplacebo \
|
||||
$MINGW_PACKAGE_PREFIX-lua51 \
|
||||
$MINGW_PACKAGE_PREFIX-ninja \
|
||||
$MINGW_PACKAGE_PREFIX-rubberband \
|
||||
$MINGW_PACKAGE_PREFIX-uchardet \
|
||||
$MINGW_PACKAGE_PREFIX-vulkan
|
||||
$MINGW_PACKAGE_PREFIX-uchardet
|
||||
|
||||
# Delete unused packages to reduce space used in the Appveyor cache
|
||||
pacman -Sc --noconfirm
|
||||
|
||||
# Compile ffmpeg
|
||||
(
|
||||
git clone --depth=1 https://github.com/FFmpeg/ffmpeg.git && cd ffmpeg
|
||||
|
||||
mkdir build && cd build
|
||||
../configure \
|
||||
--prefix=$MINGW_PREFIX \
|
||||
--target-os=mingw32 \
|
||||
--arch=$MSYSTEM_CARCH \
|
||||
--disable-static \
|
||||
--disable-doc \
|
||||
--disable-asm \
|
||||
--enable-gpl \
|
||||
--enable-version3 \
|
||||
--enable-shared \
|
||||
--enable-pic \
|
||||
--enable-d3d11va \
|
||||
--enable-dxva2 \
|
||||
--enable-schannel
|
||||
make -j4 install
|
||||
)
|
||||
|
||||
# Compile shaderc
|
||||
(
|
||||
git clone --depth=1 https://github.com/google/shaderc && cd shaderc
|
||||
"$PYTHON" utils/git-sync-deps
|
||||
|
||||
mkdir build && cd build
|
||||
cmake -GNinja -DCMAKE_BUILD_TYPE=Release -DSHADERC_SKIP_TESTS=ON \
|
||||
-DCMAKE_INSTALL_PREFIX=$MINGW_PREFIX ..
|
||||
ninja install
|
||||
)
|
||||
|
||||
# Compile SPIRV-Cross
|
||||
(
|
||||
git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Cross && cd SPIRV-Cross
|
||||
|
||||
mkdir build && cd build
|
||||
cmake -GNinja -DSPIRV_CROSS_SHARED=ON -DCMAKE_INSTALL_PREFIX=$MINGW_PREFIX ..
|
||||
ninja install
|
||||
)
|
||||
+24
-60
@@ -20,24 +20,12 @@ def is_user_lib(objfile, libname):
|
||||
not "libc." in libname and \
|
||||
not "libgcc." in libname and \
|
||||
not os.path.basename(libname) == 'Python' and \
|
||||
not os.path.basename(objfile) in libname and \
|
||||
not "libswift" in libname
|
||||
not os.path.basename(objfile) in libname
|
||||
|
||||
def otool(objfile):
|
||||
command = "otool -L '%s' | grep -e '\t' | awk '{ print $1 }'" % objfile
|
||||
output = subprocess.check_output(command, shell = True, universal_newlines=True)
|
||||
return set(filter(partial(is_user_lib, objfile), output.split()))
|
||||
|
||||
def get_rpaths_dev_tools(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*\)$")
|
||||
output = []
|
||||
|
||||
for line in result.splitlines():
|
||||
output.append(pathRe.search(line).group(1).strip())
|
||||
|
||||
return output
|
||||
command = "otool -L %s | grep -e '\t' | awk '{ print $1 }'" % objfile
|
||||
output = subprocess.check_output(command, shell = True)
|
||||
return filter(partial(is_user_lib, objfile), output.split())
|
||||
|
||||
def install_name_tool_change(old, new, objfile):
|
||||
subprocess.call(["install_name_tool", "-change", old, new, objfile])
|
||||
@@ -45,15 +33,9 @@ def install_name_tool_change(old, new, objfile):
|
||||
def install_name_tool_id(name, objfile):
|
||||
subprocess.call(["install_name_tool", "-id", name, objfile])
|
||||
|
||||
def install_name_tool_add_rpath(rpath, binary):
|
||||
subprocess.call(["install_name_tool", "-add_rpath", rpath, binary])
|
||||
|
||||
def install_name_tool_delete_rpath(rpath, binary):
|
||||
subprocess.call(["install_name_tool", "-delete_rpath", rpath, binary])
|
||||
|
||||
def libraries(objfile, result = dict()):
|
||||
libs_list = otool(objfile)
|
||||
result[objfile] = libs_list
|
||||
result[objfile] = set(libs_list)
|
||||
|
||||
for lib in libs_list:
|
||||
if lib not in result:
|
||||
@@ -61,19 +43,29 @@ def libraries(objfile, result = dict()):
|
||||
|
||||
return result
|
||||
|
||||
def leafs(libs_dict, processed = []):
|
||||
result = []
|
||||
processed = set(processed)
|
||||
|
||||
for objfile, libs in libs_dict.items():
|
||||
if libs <= processed:
|
||||
result.append(objfile)
|
||||
|
||||
return result
|
||||
|
||||
def lib_path(binary):
|
||||
return os.path.join(os.path.dirname(binary), 'lib')
|
||||
|
||||
def lib_name(lib):
|
||||
return os.path.join("@executable_path", "lib", os.path.basename(lib))
|
||||
|
||||
def process_libraries(libs_dict, binary):
|
||||
libs_set = set(libs_dict)
|
||||
# Remove binary from libs_set to prevent a duplicate of the binary being
|
||||
# added to the libs directory.
|
||||
libs_set.remove(binary)
|
||||
def process_libraries(libs_dict, binary, processed = []):
|
||||
ls = leafs(libs_dict, processed)
|
||||
diff = set(ls) - set(processed)
|
||||
if diff == set([binary]):
|
||||
return
|
||||
|
||||
for src in libs_set:
|
||||
for src in diff:
|
||||
name = lib_name(src)
|
||||
dst = os.path.join(lib_path(binary), os.path.basename(src))
|
||||
|
||||
@@ -84,47 +76,19 @@ def process_libraries(libs_dict, binary):
|
||||
if src in libs_dict[binary]:
|
||||
install_name_tool_change(src, name, binary)
|
||||
|
||||
for p in libs_set:
|
||||
for p in processed:
|
||||
if p in libs_dict[src]:
|
||||
install_name_tool_change(p, lib_name(p), dst)
|
||||
|
||||
def process_swift_libraries(binary):
|
||||
command = ['xcrun', '--find', 'swift-stdlib-tool']
|
||||
swiftStdlibTool = subprocess.check_output(command, universal_newlines=True).strip()
|
||||
# from xcode11 on the dynamic swift libs reside in a separate directory from
|
||||
# the std one, might need versioned paths for future swift versions
|
||||
swiftLibPath = os.path.join(swiftStdlibTool, '../../lib/swift-5.0/macosx')
|
||||
swiftLibPath = os.path.abspath(swiftLibPath)
|
||||
|
||||
command = [swiftStdlibTool, '--copy', '--platform', 'macosx', '--scan-executable', binary, '--destination', lib_path(binary)]
|
||||
|
||||
if os.path.exists(swiftLibPath):
|
||||
command.extend(['--source-libraries', swiftLibPath])
|
||||
|
||||
subprocess.check_output(command, universal_newlines=True)
|
||||
|
||||
print(">> setting additional rpath for swift libraries")
|
||||
install_name_tool_add_rpath("@executable_path/lib", binary)
|
||||
|
||||
def remove_dev_tools_rapths(binary):
|
||||
for path in get_rpaths_dev_tools(binary):
|
||||
install_name_tool_delete_rpath(path, binary)
|
||||
process_libraries(libs_dict, binary, ls)
|
||||
|
||||
def main():
|
||||
binary = os.path.abspath(sys.argv[1])
|
||||
if not os.path.exists(lib_path(binary)):
|
||||
os.makedirs(lib_path(binary))
|
||||
print(">> gathering all linked libraries")
|
||||
libs = libraries(binary)
|
||||
|
||||
print(">> copying and processing all linked libraries")
|
||||
print(libs)
|
||||
process_libraries(libs, binary)
|
||||
|
||||
print(">> removing rpath definitions towards dev tools")
|
||||
remove_dev_tools_rapths(binary)
|
||||
|
||||
print(">> copying and processing swift libraries")
|
||||
process_swift_libraries(binary)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Convert the contents of a file into a C string constant.
|
||||
# Note that the compiler will implicitly add an extra 0 byte at the end
|
||||
@@ -22,21 +22,26 @@
|
||||
# License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import sys
|
||||
|
||||
# Indexing a byte string yields int on Python 3.x, and a str on Python 2.x
|
||||
def pord(c):
|
||||
return ord(c) if type(c) == str else c
|
||||
|
||||
def file2string(infilename, infile, outfile):
|
||||
outfile.write("// Generated from %s\n\n" % infilename)
|
||||
|
||||
conv = ["\\%03o" % c for c in range(256)]
|
||||
conv = ['\\' + ("%03o" % c) for c in range(256)]
|
||||
safe_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" \
|
||||
"0123456789!#%&'()*+,-./:;<=>[]^_{|}~ "
|
||||
"0123456789!#%&'()*+,-./:;<=>?[]^_{|}~ "
|
||||
|
||||
for c in safe_chars:
|
||||
conv[ord(c)] = c
|
||||
for c, esc in ("\nn", "\tt", r"\\", '""'):
|
||||
conv[ord(c)] = '\\' + esc
|
||||
for line in infile:
|
||||
outfile.write('"' + ''.join(conv[c] for c in line) + '"\n')
|
||||
outfile.write('"' + ''.join(conv[pord(c)] for c in line) + '"\n')
|
||||
|
||||
if __name__ == "__main__":
|
||||
with open(sys.argv[1], 'rb') as infile:
|
||||
|
||||
@@ -6,3 +6,8 @@ mp.observe_property("audio-device-list", "native", function(name, val)
|
||||
print(" - '" .. e.name .. "' (" .. e.description .. ")")
|
||||
end
|
||||
end)
|
||||
|
||||
mp.observe_property("audio-out-detected-device", "native", function(name, val)
|
||||
print("Detected audio device changed:")
|
||||
print(" - '" .. val)
|
||||
end)
|
||||
+85
-293
@@ -1,322 +1,114 @@
|
||||
--[[
|
||||
This script uses the lavfi cropdetect filter to automatically
|
||||
insert a crop filter with appropriate parameters for the
|
||||
currently playing video.
|
||||
|
||||
It will automatically crop the video, when playback starts.
|
||||
|
||||
Also It registers the key-binding "C" (shift+c). You can manually
|
||||
crop the video by pressing the "C" (shift+c) key.
|
||||
|
||||
If the "C" key is pressed again, the crop filter is removed
|
||||
restoring playback to its original state.
|
||||
|
||||
The workflow is as follows: First, it inserts the filter
|
||||
vf=lavfi=cropdetect. After <detect_seconds> (default is 1)
|
||||
seconds, it then inserts the filter vf=crop=w:h:x:y, where
|
||||
w,h,x,y are determined from the vf-metadata gathered by
|
||||
cropdetect. The cropdetect filter is removed immediately after
|
||||
the crop filter is inserted as it is no longer needed.
|
||||
|
||||
Since the crop parameters are determined from the 1 second of
|
||||
video between inserting the cropdetect and crop filters, the "C"
|
||||
key should be pressed at a position in the video where the crop
|
||||
region is unambiguous (i.e., not a black frame, black background
|
||||
title card, or dark scene).
|
||||
|
||||
The default options can be overridden by adding
|
||||
script-opts-append=autocrop-<parameter>=<value> into mpv.conf
|
||||
|
||||
List of available parameters (For default values, see <options>):
|
||||
|
||||
auto: bool - Whether to automatically apply crop at the start of
|
||||
playback. If you don't want to crop automatically, set it to
|
||||
false or add "script-opts-append=autocrop-auto=no" into
|
||||
mpv.conf.
|
||||
|
||||
auto_delay: seconds - Delay before starting crop in auto mode.
|
||||
You can try to increase this value to avoid dark scene or
|
||||
fade in at beginning. Automatic cropping will not occur if
|
||||
the value is larger than the remaining playback time.
|
||||
|
||||
detect_limit: number[0-255] - Black threshold for cropdetect.
|
||||
Smaller values will generally result in less cropping.
|
||||
See limit of https://ffmpeg.org/ffmpeg-filters.html#cropdetect
|
||||
|
||||
detect_round: number[2^n] - The value which the width/height
|
||||
should be divisible by. Smaller values have better detection
|
||||
accuracy. If you have problems with other filters,
|
||||
you can try to set it to 4 or 16.
|
||||
See round of https://ffmpeg.org/ffmpeg-filters.html#cropdetect
|
||||
|
||||
detect_min_ratio: number[0.0-1.0] - The ratio of the minimum clip
|
||||
size to the original. If the picture is over cropped or under
|
||||
cropped, try adjusting this value.
|
||||
|
||||
detect_seconds: seconds - How long to gather cropdetect data.
|
||||
Increasing this may be desirable to allow cropdetect more
|
||||
time to collect data.
|
||||
--]]
|
||||
|
||||
-- This script uses the lavfi cropdetect filter to automatically
|
||||
-- insert a crop filter with appropriate parameters for the currently
|
||||
-- playing video.
|
||||
--
|
||||
-- It registers the key-binding "C" (shift+c), which when pressed,
|
||||
-- inserts the filter vf=lavfi=cropdetect. After 1 second, it then
|
||||
-- inserts the filter vf=crop=w:h:x:y, where w,h,x,y are determined
|
||||
-- from the vf-metadata gathered by cropdetect. The cropdetect filter
|
||||
-- is removed immediately after the crop filter is inserted as it is
|
||||
-- no longer needed.
|
||||
--
|
||||
-- If the "C" key is pressed again, the crop filter is removed
|
||||
-- restoring playback to its original state.
|
||||
--
|
||||
-- Since the crop parameters are determined from the 1 second of video
|
||||
-- between inserting the cropdetect and crop filters, the "C" key
|
||||
-- should be pressed at a position in the video where the crop region
|
||||
-- is unambiguous (i.e., not a black frame, black background title
|
||||
-- card, or dark scene).
|
||||
--
|
||||
-- The default delay between insertion of the cropdetect and
|
||||
-- crop filters may be overridden by adding
|
||||
--
|
||||
-- --script-opts=autocrop.detect_seconds=<number of seconds>
|
||||
--
|
||||
-- to mpv's arguments. This may be desirable to allow cropdetect more
|
||||
-- time to collect data.
|
||||
require "mp.msg"
|
||||
require 'mp.options'
|
||||
|
||||
local options = {
|
||||
auto = true,
|
||||
auto_delay = 4,
|
||||
detect_limit = "24/255",
|
||||
detect_round = 2,
|
||||
detect_min_ratio = 0.5,
|
||||
script_name = mp.get_script_name()
|
||||
cropdetect_label = string.format("%s-cropdetect", script_name)
|
||||
crop_label = string.format("%s-crop", script_name)
|
||||
|
||||
-- number of seconds to gather cropdetect data
|
||||
detect_seconds = tonumber(mp.get_opt(string.format("%s.detect_seconds", script_name)))
|
||||
if not detect_seconds then
|
||||
detect_seconds = 1
|
||||
}
|
||||
read_options(options)
|
||||
end
|
||||
|
||||
local label_prefix = mp.get_script_name()
|
||||
local labels = {
|
||||
crop = string.format("%s-crop", label_prefix),
|
||||
cropdetect = string.format("%s-cropdetect", label_prefix)
|
||||
}
|
||||
|
||||
timers = {
|
||||
auto_delay = nil,
|
||||
detect_crop = nil
|
||||
}
|
||||
|
||||
function is_filter_present(label)
|
||||
local filters = mp.get_property_native("vf")
|
||||
for index, filter in pairs(filters) do
|
||||
if filter["label"] == label then
|
||||
function del_filter_if_present(label)
|
||||
-- necessary because mp.command('vf del @label:filter') raises an
|
||||
-- error if the filter doesn't exist
|
||||
local vfs = mp.get_property_native("vf")
|
||||
for i,vf in pairs(vfs) do
|
||||
if vf["label"] == label then
|
||||
table.remove(vfs, i)
|
||||
mp.set_property_native("vf", vfs)
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function is_enough_time(seconds)
|
||||
|
||||
-- Plus 1 second for deviation.
|
||||
local time_needed = seconds + 1
|
||||
local playtime_remaining = mp.get_property_native("playtime-remaining")
|
||||
|
||||
return playtime_remaining and time_needed < playtime_remaining
|
||||
end
|
||||
|
||||
function is_cropable()
|
||||
local vid = mp.get_property_native("vid")
|
||||
local is_album = vid and mp.get_property_native(
|
||||
string.format("track-list/%d/albumart", vid)
|
||||
) or false
|
||||
|
||||
return vid and not is_album
|
||||
end
|
||||
|
||||
function remove_filter(label)
|
||||
if is_filter_present(label) then
|
||||
mp.command(string.format('vf remove @%s', label))
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function cleanup()
|
||||
|
||||
-- Remove all existing filters.
|
||||
for key, value in pairs(labels) do
|
||||
remove_filter(value)
|
||||
end
|
||||
|
||||
-- Kill all timers.
|
||||
for index, timer in pairs(timers) do
|
||||
if timer then
|
||||
timer:kill()
|
||||
timer = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function detect_crop()
|
||||
|
||||
-- If it's not cropable, exit.
|
||||
if not is_cropable() then
|
||||
mp.msg.warn("autocrop only works for videos.")
|
||||
function autocrop_start()
|
||||
-- exit if cropdetection is already in progress
|
||||
if timer then
|
||||
mp.msg.warn("already cropdetecting!")
|
||||
return
|
||||
end
|
||||
|
||||
-- Verify if there is enough time to detect crop.
|
||||
local time_needed = options.detect_seconds
|
||||
|
||||
if not is_enough_time(time_needed) then
|
||||
mp.msg.warn("Not enough time to detect crop.")
|
||||
-- if there's a crop filter, remove it and exit
|
||||
if del_filter_if_present(crop_label) then
|
||||
return
|
||||
end
|
||||
|
||||
-- Insert the cropdetect filter.
|
||||
local limit = options.detect_limit
|
||||
local round = options.detect_round
|
||||
|
||||
mp.command(
|
||||
-- insert the cropdetect filter
|
||||
ret=mp.command(
|
||||
string.format(
|
||||
'vf pre @%s:cropdetect=limit=%s:round=%d:reset=0',
|
||||
labels.cropdetect, limit, round
|
||||
'vf add @%s:cropdetect=limit=%f:round=2:reset=0',
|
||||
cropdetect_label, 24/255
|
||||
)
|
||||
)
|
||||
|
||||
-- Wait to gather data.
|
||||
timers.detect_crop = mp.add_timeout(time_needed, detect_end)
|
||||
-- wait to gather data
|
||||
timer=mp.add_timeout(detect_seconds, do_crop)
|
||||
end
|
||||
|
||||
function detect_end()
|
||||
|
||||
-- Get the metadata and remove the cropdetect filter.
|
||||
local cropdetect_metadata =
|
||||
mp.get_property_native(
|
||||
string.format("vf-metadata/%s",
|
||||
labels.cropdetect
|
||||
)
|
||||
function do_crop()
|
||||
-- get the metadata
|
||||
local cropdetect_metadata = mp.get_property_native(
|
||||
string.format("vf-metadata/%s", cropdetect_label)
|
||||
)
|
||||
remove_filter(labels.cropdetect)
|
||||
|
||||
-- Remove the timer of detect crop.
|
||||
if timers.detect_crop then
|
||||
timers.detect_crop:kill()
|
||||
timers.detect_crop = nil
|
||||
end
|
||||
|
||||
local meta = {}
|
||||
|
||||
-- Verify the existence of metadata.
|
||||
-- use it to crop if its valid
|
||||
if cropdetect_metadata then
|
||||
meta = {
|
||||
w = cropdetect_metadata["lavfi.cropdetect.w"],
|
||||
h = cropdetect_metadata["lavfi.cropdetect.h"],
|
||||
x = cropdetect_metadata["lavfi.cropdetect.x"],
|
||||
y = cropdetect_metadata["lavfi.cropdetect.y"],
|
||||
}
|
||||
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?")
|
||||
return
|
||||
end
|
||||
|
||||
-- Verify that the metadata meets the requirements and convert it.
|
||||
if meta.w and meta.h and meta.x and meta.y then
|
||||
local width = mp.get_property_native("width")
|
||||
local height = mp.get_property_native("height")
|
||||
|
||||
meta = {
|
||||
w = tonumber(meta.w),
|
||||
h = tonumber(meta.h),
|
||||
x = tonumber(meta.x),
|
||||
y = tonumber(meta.y),
|
||||
min_w = width * options.detect_min_ratio,
|
||||
min_h = height * options.detect_min_ratio,
|
||||
max_w = width,
|
||||
max_h = height
|
||||
}
|
||||
else
|
||||
mp.msg.error("Got empty crop data.")
|
||||
mp.msg.info("You might need to increase detect_seconds.")
|
||||
return
|
||||
end
|
||||
|
||||
apply_crop(meta)
|
||||
end
|
||||
|
||||
function apply_crop(meta)
|
||||
|
||||
-- Verify if it is necessary to crop.
|
||||
local is_effective = meta.x > 0 or meta.y > 0
|
||||
or meta.w < meta.max_w or meta.h < meta.max_h
|
||||
|
||||
if not is_effective then
|
||||
mp.msg.info("No area detected for cropping.")
|
||||
return
|
||||
end
|
||||
|
||||
-- Verify it is not over cropped.
|
||||
local is_excessive = meta.w < meta.min_w and meta.h < meta.min_h
|
||||
|
||||
if is_excessive then
|
||||
mp.msg.info("The area to be cropped is too large.")
|
||||
mp.msg.info("You might need to decrease detect_min_ratio.")
|
||||
return
|
||||
end
|
||||
|
||||
-- Remove existing crop.
|
||||
remove_filter(labels.crop)
|
||||
|
||||
-- Apply crop.
|
||||
mp.command(
|
||||
string.format("vf pre @%s:lavfi-crop=w=%s:h=%s:x=%s:y=%s",
|
||||
labels.crop, meta.w, meta.h, meta.x, meta.y
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
function on_start()
|
||||
|
||||
-- Clean up at the beginning.
|
||||
cleanup()
|
||||
|
||||
-- If auto is not true, exit.
|
||||
if not options.auto then
|
||||
return
|
||||
end
|
||||
|
||||
-- If it is the beginning, wait for detect_crop
|
||||
-- after auto_delay seconds, otherwise immediately.
|
||||
local playback_time = mp.get_property_native("playback-time")
|
||||
local is_delay_needed = playback_time
|
||||
and options.auto_delay > playback_time
|
||||
|
||||
if is_delay_needed then
|
||||
|
||||
-- Verify if there is enough time for autocrop.
|
||||
local time_needed = options.auto_delay + options.detect_seconds
|
||||
|
||||
if not is_enough_time(time_needed) then
|
||||
mp.msg.warn("Not enough time for autocrop.")
|
||||
return
|
||||
if cropdetect_metadata["lavfi.cropdetect.w"]
|
||||
and cropdetect_metadata["lavfi.cropdetect.h"]
|
||||
and cropdetect_metadata["lavfi.cropdetect.x"]
|
||||
and cropdetect_metadata["lavfi.cropdetect.y"]
|
||||
then
|
||||
mp.command(string.format("vf add @%s:lavfi-crop=w=%s:h=%s:x=%s:y=%s",
|
||||
crop_label,
|
||||
cropdetect_metadata["lavfi.cropdetect.w"],
|
||||
cropdetect_metadata["lavfi.cropdetect.h"],
|
||||
cropdetect_metadata["lavfi.cropdetect.x"],
|
||||
cropdetect_metadata["lavfi.cropdetect.y"]))
|
||||
else
|
||||
mp.msg.error(
|
||||
"Got empty crop data. You might need to increase detect_seconds."
|
||||
)
|
||||
end
|
||||
|
||||
timers.auto_delay = mp.add_timeout(time_needed,
|
||||
function()
|
||||
detect_crop()
|
||||
|
||||
-- Remove the timer of auto delay.
|
||||
timers.auto_delay:kill()
|
||||
timers.auto_delay = nil
|
||||
end
|
||||
)
|
||||
else
|
||||
detect_crop()
|
||||
mp.msg.error(
|
||||
"No crop data. Was the cropdetect filter successfully inserted?"
|
||||
)
|
||||
mp.msg.error(
|
||||
"Does your version of ffmpeg/libav support AVFrame metadata?"
|
||||
)
|
||||
end
|
||||
-- remove the cropdetect filter
|
||||
del_filter_if_present(cropdetect_label)
|
||||
timer=nil
|
||||
end
|
||||
|
||||
function on_toggle()
|
||||
|
||||
-- If it is during auto_delay, kill the timer.
|
||||
if timers.auto_delay then
|
||||
timers.auto_delay:kill()
|
||||
timers.auto_delay = nil
|
||||
end
|
||||
|
||||
-- Cropped => Remove it.
|
||||
if remove_filter(labels.crop) then
|
||||
return
|
||||
end
|
||||
|
||||
-- Detecting => Leave it.
|
||||
if timers.detect_crop then
|
||||
mp.msg.warn("Already cropdetecting!")
|
||||
return
|
||||
end
|
||||
|
||||
-- Neither => Do delectcrop.
|
||||
detect_crop()
|
||||
end
|
||||
|
||||
mp.add_key_binding("C", "toggle_crop", on_toggle)
|
||||
mp.register_event("end-file", cleanup)
|
||||
mp.register_event("file-loaded", on_start)
|
||||
mp.add_key_binding("C", "auto_crop", autocrop_start)
|
||||
+15
-67
@@ -2,34 +2,15 @@
|
||||
-- the currently played file. It does so by scanning the directory a file is
|
||||
-- located in when starting playback. It sorts the directory entries
|
||||
-- alphabetically, and adds entries before and after the current file to
|
||||
-- the internal playlist. (It stops if it would add an already existing
|
||||
-- the internal playlist. (It stops if the it would add an already existing
|
||||
-- playlist entry at the same position - this makes it "stable".)
|
||||
-- Add at most 5000 * 2 files when starting a file (before + after).
|
||||
|
||||
--[[
|
||||
To configure this script use file autoload.conf in directory script-opts (the "script-opts"
|
||||
directory must be in the mpv configuration directory, typically ~/.config/mpv/).
|
||||
|
||||
Example configuration would be:
|
||||
|
||||
disabled=no
|
||||
images=no
|
||||
videos=yes
|
||||
audio=yes
|
||||
|
||||
--]]
|
||||
|
||||
MAXENTRIES = 5000
|
||||
|
||||
local msg = require 'mp.msg'
|
||||
local options = require 'mp.options'
|
||||
local utils = require 'mp.utils'
|
||||
|
||||
o = {
|
||||
disabled = false,
|
||||
images = true,
|
||||
videos = true,
|
||||
audio = true
|
||||
disabled = false
|
||||
}
|
||||
options.read_options(o)
|
||||
|
||||
@@ -39,29 +20,12 @@ function Set (t)
|
||||
return set
|
||||
end
|
||||
|
||||
function SetUnion (a,b)
|
||||
local res = {}
|
||||
for k in pairs(a) do res[k] = true end
|
||||
for k in pairs(b) do res[k] = true end
|
||||
return res
|
||||
end
|
||||
|
||||
EXTENSIONS_VIDEO = Set {
|
||||
'mkv', 'avi', 'mp4', 'ogv', 'webm', 'rmvb', 'flv', 'wmv', 'mpeg', 'mpg', 'm4v', '3gp'
|
||||
EXTENSIONS = Set {
|
||||
'mkv', 'avi', 'mp4', 'ogv', 'webm', 'rmvb', 'flv', 'wmv', 'mpeg', 'mpg', 'm4v', '3gp',
|
||||
'mp3', 'wav', 'ogv', 'flac', 'm4a', 'wma',
|
||||
}
|
||||
|
||||
EXTENSIONS_AUDIO = Set {
|
||||
'mp3', 'wav', 'ogm', 'flac', 'm4a', 'wma', 'ogg', 'opus'
|
||||
}
|
||||
|
||||
EXTENSIONS_IMAGES = Set {
|
||||
'jpg', 'jpeg', 'png', 'tif', 'tiff', 'gif', 'webp', 'svg', 'bmp'
|
||||
}
|
||||
|
||||
EXTENSIONS = Set {}
|
||||
if o.videos then EXTENSIONS = SetUnion(EXTENSIONS, EXTENSIONS_VIDEO) end
|
||||
if o.audio then EXTENSIONS = SetUnion(EXTENSIONS, EXTENSIONS_AUDIO) end
|
||||
if o.images then EXTENSIONS = SetUnion(EXTENSIONS, EXTENSIONS_IMAGES) end
|
||||
mputils = require 'mp.utils'
|
||||
|
||||
function add_files_at(index, files)
|
||||
index = index - 1
|
||||
@@ -120,38 +84,22 @@ function alnumcomp(x, y)
|
||||
return #xt < #yt
|
||||
end
|
||||
|
||||
local autoloaded = nil
|
||||
|
||||
function find_and_add_entries()
|
||||
local path = mp.get_property("path", "")
|
||||
local dir, filename = utils.split_path(path)
|
||||
msg.trace(("dir: %s, filename: %s"):format(dir, filename))
|
||||
if o.disabled then
|
||||
msg.verbose("stopping: autoload disabled")
|
||||
return
|
||||
elseif #dir == 0 then
|
||||
msg.verbose("stopping: not a local path")
|
||||
local dir, filename = mputils.split_path(path)
|
||||
if o.disabled or #dir == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local pl_count = mp.get_property_number("playlist-count", 1)
|
||||
-- check if this is a manually made playlist
|
||||
if (pl_count > 1 and autoloaded == nil) or
|
||||
if (pl_count > 1 and autoload == nil) or
|
||||
(pl_count == 1 and EXTENSIONS[string.lower(get_extension(filename))] == nil) then
|
||||
msg.verbose("stopping: manually made playlist")
|
||||
return
|
||||
else
|
||||
autoloaded = true
|
||||
autoload = true
|
||||
end
|
||||
|
||||
local pl = mp.get_property_native("playlist", {})
|
||||
local pl_current = mp.get_property_number("playlist-pos-1", 1)
|
||||
msg.trace(("playlist-pos-1: %s, playlist: %s"):format(pl_current,
|
||||
utils.to_string(pl)))
|
||||
|
||||
local files = utils.readdir(dir, "files")
|
||||
local files = mputils.readdir(dir, "files")
|
||||
if files == nil then
|
||||
msg.verbose("no other files in directory")
|
||||
return
|
||||
end
|
||||
table.filter(files, function (v, k)
|
||||
@@ -170,6 +118,8 @@ function find_and_add_entries()
|
||||
dir = ""
|
||||
end
|
||||
|
||||
local pl = mp.get_property_native("playlist", {})
|
||||
local pl_current = mp.get_property_number("playlist-pos", 0) + 1
|
||||
-- Find the current pl entry (dir+"/"+filename) in the sorted dir list
|
||||
local current
|
||||
for i = 1, #files do
|
||||
@@ -181,7 +131,6 @@ function find_and_add_entries()
|
||||
if current == nil then
|
||||
return
|
||||
end
|
||||
msg.trace("current file position in files: "..current)
|
||||
|
||||
local append = {[-1] = {}, [1] = {}}
|
||||
for direction = -1, 1, 2 do -- 2 iterations, with direction = -1 and +1
|
||||
@@ -195,7 +144,6 @@ function find_and_add_entries()
|
||||
local filepath = dir .. file
|
||||
if pl_e then
|
||||
-- If there's a playlist entry, and it's the same file, stop.
|
||||
msg.trace(pl_e.filename.." == "..filepath.." ?")
|
||||
if pl_e.filename == filepath then
|
||||
break
|
||||
end
|
||||
@@ -203,11 +151,11 @@ function find_and_add_entries()
|
||||
|
||||
if direction == -1 then
|
||||
if pl_current == 1 then -- never add additional entries in the middle
|
||||
msg.info("Prepending " .. file)
|
||||
mp.msg.info("Prepending " .. file)
|
||||
table.insert(append[-1], 1, filepath)
|
||||
end
|
||||
else
|
||||
msg.info("Adding " .. file)
|
||||
mp.msg.info("Adding " .. file)
|
||||
table.insert(append[1], filepath)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
-- Test script for some command API details.
|
||||
|
||||
local utils = require("mp.utils")
|
||||
|
||||
function join(sep, arr, count)
|
||||
local r = ""
|
||||
if count == nil then
|
||||
count = #arr
|
||||
end
|
||||
for i = 1, count do
|
||||
if i > 1 then
|
||||
r = r .. sep
|
||||
end
|
||||
r = r .. utils.to_string(arr[i])
|
||||
end
|
||||
return r
|
||||
end
|
||||
|
||||
mp.observe_property("vo-configured", "bool", function(_, v)
|
||||
if v ~= true then
|
||||
return
|
||||
end
|
||||
|
||||
print("async expand-text")
|
||||
mp.command_native_async({"expand-text", "hello ${path}!"},
|
||||
function(res, val, err)
|
||||
print("done async expand-text: " .. join(" ", {res, val, err}))
|
||||
end)
|
||||
|
||||
-- make screenshot writing very slow
|
||||
mp.set_property("screenshot-format", "png")
|
||||
mp.set_property("screenshot-png-compression", "9")
|
||||
|
||||
timer = mp.add_periodic_timer(0.1, function() print("I'm alive") end)
|
||||
timer:resume()
|
||||
|
||||
print("Slow screenshot command...")
|
||||
res, err = mp.command_native({"screenshot"})
|
||||
print("done, res: " .. utils.to_string(res))
|
||||
|
||||
print("Slow screenshot async command...")
|
||||
res, err = mp.command_native_async({"screenshot"}, function(res)
|
||||
print("done (async), res: " .. utils.to_string(res))
|
||||
timer:kill()
|
||||
end)
|
||||
print("done (sending), res: " .. utils.to_string(res))
|
||||
|
||||
print("Broken screenshot async command...")
|
||||
mp.command_native_async({"screenshot-to-file", "/nonexistent/bogus.png"},
|
||||
function(res, val, err)
|
||||
print("done err scr.: " .. join(" ", {res, val, err}))
|
||||
end)
|
||||
|
||||
mp.command_native_async({name = "subprocess", args = {"sh", "-c", "echo hi && sleep 10s"}, capture_stdout = true},
|
||||
function(res, val, err)
|
||||
print("done subprocess: " .. join(" ", {res, val, err}))
|
||||
end)
|
||||
|
||||
local x = mp.command_native_async({name = "subprocess", args = {"sleep", "inf"}},
|
||||
function(res, val, err)
|
||||
print("done sleep inf subprocess: " .. join(" ", {res, val, err}))
|
||||
end)
|
||||
mp.add_timeout(15, function()
|
||||
print("aborting sleep inf subprocess after timeout")
|
||||
mp.abort_async_command(x)
|
||||
end)
|
||||
|
||||
-- (assuming this "freezes")
|
||||
local y = mp.command_native_async({name = "sub-add", url = "-"},
|
||||
function(res, val, err)
|
||||
print("done sub-add stdin: " .. join(" ", {res, val, err}))
|
||||
end)
|
||||
mp.add_timeout(20, function()
|
||||
print("aborting sub-add stdin after timeout")
|
||||
mp.abort_async_command(y)
|
||||
end)
|
||||
|
||||
|
||||
mp.command_native_async({name = "subprocess", args = {"wc", "-c"},
|
||||
stdin_data = "hello", capture_stdout = true},
|
||||
function(res, val, err)
|
||||
print("Should be '5': " .. val.stdout)
|
||||
end)
|
||||
-- blocking stdin by default
|
||||
mp.command_native_async({name = "subprocess", args = {"cat"},
|
||||
capture_stdout = true},
|
||||
function(res, val, err)
|
||||
print("Should be 0: " .. #val.stdout)
|
||||
end)
|
||||
-- stdin + detached
|
||||
mp.command_native_async({name = "subprocess",
|
||||
args = {"bash", "-c", "(sleep 5s ; cat)"},
|
||||
stdin_data = "this should appear after 5s.\n",
|
||||
detach = true},
|
||||
function(res, val, err)
|
||||
print("5s test: " .. val.status)
|
||||
end)
|
||||
|
||||
-- This should get killed on script exit.
|
||||
mp.command_native_async({name = "subprocess", playback_only = false,
|
||||
args = {"sleep", "inf"}}, function()end)
|
||||
|
||||
-- Runs detached; should be killed on player exit (forces timeout)
|
||||
mp.command_native({_flags={"async"}, name = "subprocess",
|
||||
playback_only = false, args = {"sleep", "inf"}})
|
||||
end)
|
||||
|
||||
function freeze_test(playback_only)
|
||||
-- This "freezes" the script, should be killed via timeout.
|
||||
counter = counter and counter + 1 or 0
|
||||
print("freeze! " .. counter)
|
||||
local x = mp.command_native({name = "subprocess",
|
||||
playback_only = playback_only,
|
||||
args = {"sleep", "inf"}})
|
||||
print("done, killed=" .. utils.to_string(x.killed_by_us))
|
||||
end
|
||||
|
||||
mp.register_event("shutdown", function()
|
||||
freeze_test(false)
|
||||
end)
|
||||
|
||||
mp.register_event("idle", function()
|
||||
freeze_test(true)
|
||||
end)
|
||||
@@ -1,37 +0,0 @@
|
||||
-- Test a float property which internally uses NaN.
|
||||
-- Run with --no-config (or just scale-param1 not set).
|
||||
|
||||
local utils = require 'mp.utils'
|
||||
|
||||
prop_name = "scale-param1"
|
||||
|
||||
-- internal NaN, return string "default" instead of NaN
|
||||
v = mp.get_property_native(prop_name, "fail")
|
||||
print("Exp:", "string", "\"default\"")
|
||||
print("Got:", type(v), utils.to_string(v))
|
||||
|
||||
v = mp.get_property(prop_name)
|
||||
print("Exp:", "default")
|
||||
print("Got:", v)
|
||||
|
||||
-- not representable -> return provided fallback value
|
||||
v = mp.get_property_number(prop_name, -100)
|
||||
print("Exp:", -100)
|
||||
print("Got:", v)
|
||||
|
||||
mp.set_property_native(prop_name, 123)
|
||||
v = mp.get_property_number(prop_name, -100)
|
||||
print("Exp:", "number", 123)
|
||||
print("Got:", type(v), utils.to_string(v))
|
||||
|
||||
-- try to set an actual NaN
|
||||
st, msg = mp.set_property_number(prop_name, 0.0/0)
|
||||
print("Exp:", nil, "<message>")
|
||||
print("Got:", st, msg)
|
||||
|
||||
-- set default
|
||||
mp.set_property(prop_name, "default")
|
||||
|
||||
v = mp.get_property(prop_name)
|
||||
print("Exp:", "default")
|
||||
print("Got:", v)
|
||||
@@ -1,35 +0,0 @@
|
||||
local assdraw = require 'mp.assdraw'
|
||||
local utils = require 'mp.utils'
|
||||
|
||||
things = {}
|
||||
for i = 1, 2 do
|
||||
things[i] = {
|
||||
osd1 = mp.create_osd_overlay("ass-events"),
|
||||
osd2 = mp.create_osd_overlay("ass-events")
|
||||
}
|
||||
end
|
||||
things[1].text = "{\\an5}hello\\Nworld"
|
||||
things[2].text = "{\\pos(400, 200)}something something"
|
||||
|
||||
mp.add_periodic_timer(2, function()
|
||||
for i, thing in ipairs(things) do
|
||||
thing.osd1.data = thing.text
|
||||
thing.osd1.compute_bounds = true
|
||||
--thing.osd1.hidden = true
|
||||
local res = thing.osd1:update()
|
||||
print("res " .. i .. ": " .. utils.to_string(res))
|
||||
|
||||
thing.osd2.hidden = true
|
||||
if res ~= nil and res.x0 ~= nil then
|
||||
local draw = assdraw.ass_new()
|
||||
draw:append("{\\alpha&H80}")
|
||||
draw:draw_start()
|
||||
draw:pos(0, 0)
|
||||
draw:rect_cw(res.x0, res.y0, res.x1, res.y1)
|
||||
draw:draw_stop()
|
||||
thing.osd2.hidden = false
|
||||
thing.osd2.data = draw.text
|
||||
end
|
||||
thing.osd2:update()
|
||||
end
|
||||
end)
|
||||
@@ -1,265 +0,0 @@
|
||||
--[[
|
||||
|
||||
Automatically skip in files if video frames with pre-supplied fingerprints are
|
||||
detected. This will skip ahead by a pre-configured amount of time if a matching
|
||||
video frame is detected.
|
||||
|
||||
This requires the vf_fingerprint video filter to be compiled in. Read the
|
||||
documentation of this filter for caveats (which will automatically apply to
|
||||
this script as well), such as no support for zero-copy hardware decoding.
|
||||
|
||||
You need to manually gather and provide fingerprints for video frames and add
|
||||
them to a configuration file in script-opts/skip-logo.conf (the "script-opts"
|
||||
directory must be in the mpv configuration directory, typically ~/.config/mpv/).
|
||||
|
||||
Example script-opts/skip-logo.conf:
|
||||
|
||||
|
||||
cases = {
|
||||
{
|
||||
-- Skip ahead 10 seconds if a black frame was detected
|
||||
-- Note: this is dangerous non-sense. It's just for demonstration.
|
||||
name = "black frame", -- print if matched
|
||||
skip = 10, -- number of seconds to skip forward
|
||||
score = 0.3, -- required score
|
||||
fingerprint = "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||
},
|
||||
{
|
||||
-- Skip ahead 20 seconds if a white frame was detected
|
||||
-- Note: this is dangerous non-sense. It's just for demonstration.
|
||||
name = "fun2",
|
||||
skip = 20,
|
||||
fingerprint = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
},
|
||||
}
|
||||
|
||||
This is actually a lua file. Lua was chosen because it seemed less of a pain to
|
||||
parse. Future versions of this script may change the format.
|
||||
|
||||
The fingerprint is a video frame, converted to "gray" (8 bit per pixels), full
|
||||
range, each pixel concatenated into an array, converted to a hex string. You
|
||||
can produce these fingerprints by running this manually:
|
||||
|
||||
mpv --vf=fingerprint:print yourfile.mkv
|
||||
|
||||
This will log the fingerprint of each video frame to the console, along with its
|
||||
timestamp. You find the fingerprint of a unique-enough looking frame, and add
|
||||
it as entry to skip-logo.conf.
|
||||
|
||||
You can provide a score for "fuzziness". If no score is provided, a default
|
||||
value of 0.3 is used. The score is inverse: 0 means exactly the same, while a
|
||||
higher score means a higher difference. Currently, the score is computed as
|
||||
euclidean distance between the video frame and the pre-provided fingerprint,
|
||||
thus the highest score is 16. You probably want a score lower than 1 at least.
|
||||
(This algorithm is very primitive, but also simple and fast to compute.)
|
||||
|
||||
There's always the danger of false positives, which might be quite annoying.
|
||||
It's up to you what you hate more, the logo, or random skips if false positives
|
||||
are detected. Also, it's always active, and might eat too much CPU with files
|
||||
that have a high resolution or framerate. To temporarily disable the script,
|
||||
having a keybind like this in your input.conf will be helpful:
|
||||
|
||||
ctrl+k vf toggle @skip-logo
|
||||
|
||||
This will disable/enable the fingerprint filter, which the script automatically
|
||||
adds at start.
|
||||
|
||||
Another important caveat is that the script currently disables matching during
|
||||
seeking or playback initialization, which means it cannot match the first few
|
||||
frames of a video. This could be fixed, but the author was too lazy to do so.
|
||||
|
||||
--]]
|
||||
|
||||
local utils = require "mp.utils"
|
||||
local msg = require "mp.msg"
|
||||
|
||||
local label = "skip-logo"
|
||||
local meta_property = string.format("vf-metadata/%s", label)
|
||||
|
||||
local config = {}
|
||||
local cases = {}
|
||||
local cur_bmp
|
||||
local seeking = false
|
||||
local playback_start_pts = nil
|
||||
|
||||
-- Convert a hex string to an array. Convert each byte to a [0,1] float by
|
||||
-- interpreting it as normalized uint8_t.
|
||||
-- The data parameter, if not nil, may be used as storage (avoiding garbage).
|
||||
local function hex_to_norm8(hex, data)
|
||||
local size = math.floor(#hex / 2)
|
||||
if #hex ~= size * 2 then
|
||||
return nil
|
||||
end
|
||||
local res
|
||||
if (data ~= nil) and (#data == size) then
|
||||
res = data
|
||||
else
|
||||
res = {}
|
||||
end
|
||||
for i = 1, size do
|
||||
local num = tonumber(hex:sub(i * 2, i * 2 + 1), 16)
|
||||
if num == nil then
|
||||
return nil
|
||||
end
|
||||
res[i] = num / 255.0
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
local function compare_bmp(a, b)
|
||||
if #a ~= #b then
|
||||
return nil -- can't compare
|
||||
end
|
||||
local sum = 0
|
||||
for i = 1, #a do
|
||||
local diff = a[i] - b[i]
|
||||
sum = sum + diff * diff
|
||||
end
|
||||
return math.sqrt(sum)
|
||||
end
|
||||
|
||||
local function load_config()
|
||||
local conf_file = mp.find_config_file("script-opts/skip-logo.conf")
|
||||
local conf_fn
|
||||
local err = nil
|
||||
if conf_file then
|
||||
if setfenv then
|
||||
conf_fn, err = loadfile(conf_file)
|
||||
if conf_fn then
|
||||
setfenv(conf_fn, config)
|
||||
end
|
||||
else
|
||||
conf_fn, err = loadfile(conf_file, "t", config)
|
||||
end
|
||||
else
|
||||
err = "config file not found"
|
||||
end
|
||||
|
||||
if conf_fn and (not err) then
|
||||
local ok, err2 = pcall(conf_fn)
|
||||
err = err2
|
||||
end
|
||||
|
||||
if err then
|
||||
msg.error("Failed to load config file:", err)
|
||||
end
|
||||
|
||||
if config.cases then
|
||||
for n, case in ipairs(config.cases) do
|
||||
local err = nil
|
||||
case.bitmap = hex_to_norm8(case.fingerprint)
|
||||
if case.bitmap == nil then
|
||||
err = "invalid or missing fingerprint field"
|
||||
end
|
||||
if case.score == nil then
|
||||
case.score = 0.3
|
||||
end
|
||||
if type(case.score) ~= "number" then
|
||||
err = "score field is not a number"
|
||||
end
|
||||
if type(case.skip) ~= "number" then
|
||||
err = "skip field is not a number or missing"
|
||||
end
|
||||
if case.name == nil then
|
||||
case.name = ("Entry %d"):format(n)
|
||||
end
|
||||
if err == nil then
|
||||
cases[#cases + 1] = case
|
||||
else
|
||||
msg.error(("Entry %s: %s, ignoring."):format(case.name, err))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
load_config()
|
||||
|
||||
-- Returns true on match and if something was done.
|
||||
local function check_fingerprint(hex, pts)
|
||||
local bmp = hex_to_norm8(hex, cur_bmp)
|
||||
cur_bmp = bmp
|
||||
|
||||
-- If parsing the filter's result failed (well, it shouldn't).
|
||||
assert(bmp ~= nil, "filter returned nonsense")
|
||||
|
||||
for _, case in ipairs(cases) do
|
||||
local score = compare_bmp(case.bitmap, bmp)
|
||||
if (score ~= nil) and (score <= case.score) then
|
||||
msg.warn(("Matching %s: score=%f (required: %f) at %s, skipping %f seconds"):
|
||||
format(case.name, score, case.score, mp.format_time(pts), case.skip))
|
||||
mp.commandv("seek", pts + case.skip, "absolute+exact")
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
local function read_frames()
|
||||
local result = mp.get_property_native(meta_property)
|
||||
if result == nil then
|
||||
return
|
||||
end
|
||||
|
||||
-- Try to get all entries. Out of laziness, assume that there are at most
|
||||
-- 100 entries. (In fact, vf_fingerprint limits it to 10.)
|
||||
for i = 0, 99 do
|
||||
local prefix = string.format("fp%d.", i)
|
||||
local hex = result[prefix .. "hex"]
|
||||
|
||||
local pts = tonumber(result[prefix .. "pts"])
|
||||
if (hex == nil) or (pts == nil) then
|
||||
break
|
||||
end
|
||||
|
||||
local skip = false -- blame Lua for not having "continue" or "goto", not me
|
||||
|
||||
-- If seeking just stopped, there will be frames before the seek target,
|
||||
-- ignore them by checking the timestamps.
|
||||
if playback_start_pts ~= nil then
|
||||
if pts >= playback_start_pts then
|
||||
playback_start_pts = nil -- just for robustness
|
||||
else
|
||||
skip = true
|
||||
end
|
||||
end
|
||||
|
||||
if not skip then
|
||||
if check_fingerprint(hex, pts) then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
mp.observe_property(meta_property, "none", function()
|
||||
-- Ignore frames that are decoded/filtered during seeking.
|
||||
if seeking then
|
||||
return
|
||||
end
|
||||
|
||||
read_frames()
|
||||
end)
|
||||
|
||||
mp.observe_property("seeking", "bool", function(name, val)
|
||||
seeking = val
|
||||
if seeking == false then
|
||||
playback_start_pts = mp.get_property_number("playback-time")
|
||||
read_frames()
|
||||
end
|
||||
end)
|
||||
|
||||
local filters = mp.get_property_native("option-info/vf/choices", {})
|
||||
local found = false
|
||||
for _, f in ipairs(filters) do
|
||||
if f == "fingerprint" then
|
||||
found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if found then
|
||||
mp.command(("no-osd vf add @%s:fingerprint"):format(label, filter))
|
||||
else
|
||||
msg.warn("vf_fingerprint not found")
|
||||
end
|
||||
@@ -1,32 +0,0 @@
|
||||
local utils = require("mp.utils")
|
||||
|
||||
function hardsleep()
|
||||
os.execute("sleep 1s")
|
||||
end
|
||||
|
||||
local hooks = {"on_before_start_file", "on_load", "on_load_fail",
|
||||
"on_preloaded", "on_unload", "on_after_end_file"}
|
||||
|
||||
for _, name in ipairs(hooks) do
|
||||
mp.add_hook(name, 0, function()
|
||||
print("--- hook: " .. name)
|
||||
hardsleep()
|
||||
print(" ... continue")
|
||||
end)
|
||||
end
|
||||
|
||||
local events = {"start-file", "end-file", "file-loaded", "seek",
|
||||
"playback-restart", "idle", "shutdown"}
|
||||
for _, name in ipairs(events) do
|
||||
mp.register_event(name, function()
|
||||
print("--- event: " .. name)
|
||||
end)
|
||||
end
|
||||
|
||||
local props = {"path", "metadata"}
|
||||
for _, name in ipairs(props) do
|
||||
mp.observe_property(name, "native", function(name, val)
|
||||
print("property '" .. name .. "' changed to '" ..
|
||||
utils.to_string(val) .. "'")
|
||||
end)
|
||||
end
|
||||
+2
-4
@@ -213,7 +213,6 @@ elements_matroska = (
|
||||
'Tag*, 7373, sub', (
|
||||
'Targets, 63c0, sub', (
|
||||
'TargetTypeValue, 68ca, uint',
|
||||
'TargetType, 63ca, str',
|
||||
'TargetTrackUID, 63c5, uint',
|
||||
'TargetEditionUID, 63c9, uint',
|
||||
'TargetChapterUID, 63c4, uint',
|
||||
@@ -222,8 +221,7 @@ elements_matroska = (
|
||||
'SimpleTag*, 67c8, sub', (
|
||||
'TagName, 45a3, str',
|
||||
'TagLanguage, 447a, str',
|
||||
'TagString, 4487, str',
|
||||
'TagDefault, 4484, uint',
|
||||
'TagString, 4487, str'
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -276,7 +274,7 @@ class MatroskaElement(object):
|
||||
|
||||
def add_subelements(self, subelements):
|
||||
self.subelements = subelements
|
||||
self.subids = {x[0].elid for x in subelements}
|
||||
self.subids = set(x[0].elid for x in subelements)
|
||||
|
||||
elementd = {}
|
||||
elementlist = []
|
||||
|
||||
@@ -22,7 +22,7 @@ OS2Version: 3
|
||||
OS2_WeightWidthSlopeOnly: 0
|
||||
OS2_UseTypoMetrics: 0
|
||||
CreationTime: 1408646554
|
||||
ModificationTime: 1576096543
|
||||
ModificationTime: 1492879730
|
||||
PfmFamily: 81
|
||||
TTFWeight: 400
|
||||
TTFWidth: 5
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
StartChar: uniE112
|
||||
Encoding: 57618 57618 30
|
||||
Width: 768
|
||||
VWidth: 1176
|
||||
Flags: HW
|
||||
LayerCount: 2
|
||||
Fore
|
||||
SplineSet
|
||||
512 40 m 1
|
||||
0 40 l 1
|
||||
0 168 l 1
|
||||
512 168 l 1
|
||||
512 40 l 1
|
||||
EndSplineSet
|
||||
EndChar
|
||||
@@ -1,20 +0,0 @@
|
||||
StartChar: uniE113
|
||||
Encoding: 57619 57619 31
|
||||
Width: 622
|
||||
VWidth: 1178
|
||||
Flags: HW
|
||||
LayerCount: 2
|
||||
Fore
|
||||
SplineSet
|
||||
768 42 m 5
|
||||
0 42 l 5
|
||||
0 746 l 5
|
||||
768 746 l 5
|
||||
768 42 l 5
|
||||
704 106 m 5
|
||||
704 618 l 5
|
||||
64 618 l 5
|
||||
64 106 l 5
|
||||
704 106 l 5
|
||||
EndSplineSet
|
||||
EndChar
|
||||
@@ -1,31 +0,0 @@
|
||||
StartChar: uniE114
|
||||
Encoding: 57620 57620 32
|
||||
Width: 896
|
||||
VWidth: 1178
|
||||
Flags: HW
|
||||
LayerCount: 2
|
||||
Fore
|
||||
SplineSet
|
||||
768 298 m 1
|
||||
576 298 l 1
|
||||
576 42 l 1
|
||||
0 42 l 1
|
||||
0 490 l 1
|
||||
192 490 l 1
|
||||
192 746 l 1
|
||||
768 746 l 1
|
||||
768 298 l 1
|
||||
704 362 m 1
|
||||
704 618 l 1
|
||||
256 618 l 1
|
||||
256 490 l 1
|
||||
576 490 l 1
|
||||
576 362 l 1
|
||||
704 362 l 1
|
||||
512 106 m 1
|
||||
512 362 l 1
|
||||
64 362 l 1
|
||||
64 106 l 1
|
||||
512 106 l 1
|
||||
EndSplineSet
|
||||
EndChar
|
||||
@@ -1,27 +0,0 @@
|
||||
StartChar: uniE115
|
||||
Encoding: 57621 57621 33
|
||||
Width: 844
|
||||
VWidth: 1112
|
||||
Flags: HW
|
||||
LayerCount: 2
|
||||
Fore
|
||||
SplineSet
|
||||
671 40 m 1
|
||||
575 40 l 1
|
||||
335 277 l 1
|
||||
98 40 l 1
|
||||
-1 40 l 1
|
||||
-1 136 l 1
|
||||
236 376 l 1
|
||||
-1 613 l 1
|
||||
-1 712 l 1
|
||||
98 712 l 1
|
||||
335 475 l 1
|
||||
575 712 l 1
|
||||
671 712 l 1
|
||||
671 613 l 1
|
||||
434 376 l 1
|
||||
671 139 l 1
|
||||
671 40 l 1
|
||||
EndSplineSet
|
||||
EndChar
|
||||
+4
-10
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
@@ -7,7 +7,7 @@ import fileinput
|
||||
from optparse import OptionParser
|
||||
|
||||
def sh(command):
|
||||
return os.popen(command).read().strip()
|
||||
return os.popen(command).read()
|
||||
|
||||
def bundle_path(binary_name):
|
||||
return "%s.app" % binary_name
|
||||
@@ -37,11 +37,7 @@ def copy_binary(binary_name):
|
||||
|
||||
def apply_plist_template(plist_file, version):
|
||||
for line in fileinput.input(plist_file, inplace=1):
|
||||
print(line.rstrip().replace('${VERSION}', version))
|
||||
|
||||
def create_bundle_symlink(binary_name, symlink_name):
|
||||
os.symlink(os.path.basename(binary_name),
|
||||
os.path.join(target_directory(binary_name), symlink_name))
|
||||
print (line.rstrip().replace('${VERSION}', version))
|
||||
|
||||
def bundle_version():
|
||||
if os.path.exists('VERSION'):
|
||||
@@ -73,14 +69,12 @@ def main():
|
||||
copy_bundle(binary_name)
|
||||
print("> copying binary")
|
||||
copy_binary(binary_name)
|
||||
print("> create bundle symlink")
|
||||
create_bundle_symlink(binary_name, "mpv-bundle")
|
||||
print("> generating Info.plist")
|
||||
apply_plist_template(target_plist(binary_name), version)
|
||||
|
||||
if options.deps:
|
||||
print("> bundling dependencies")
|
||||
print(sh(" ".join(["TOOLS/dylib-unhell.py", target_binary(binary_name)])))
|
||||
sh(" ".join(["TOOLS/dylib-unhell.py", target_binary(binary_name)]))
|
||||
|
||||
print("done.")
|
||||
|
||||
|
||||
@@ -173,7 +173,7 @@
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>mpv-bundle</string>
|
||||
<string>mpv-wrapper.sh</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>icon</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
@@ -217,7 +217,6 @@
|
||||
<string>ftp</string>
|
||||
<string>udp</string>
|
||||
<string>smb</string>
|
||||
<string>srt</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
export MPVBUNDLE="true"
|
||||
|
||||
# set the right args for the user specified standard shell
|
||||
# to load the expected profiles and configs
|
||||
args="-c"
|
||||
case "$SHELL" in
|
||||
*bash) args="-l $args";;
|
||||
*zsh) args="-l -i $args";;
|
||||
esac
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
$SHELL $args "./mpv --player-operation-mode=pseudo-gui"
|
||||
@@ -1,2 +0,0 @@
|
||||
player-operation-mode=pseudo-gui
|
||||
log-file=~/Library/Logs/mpv.log
|
||||
+2
-2
@@ -147,8 +147,8 @@ if hasval:
|
||||
ax[1] = win.addPlot()
|
||||
ax[1].setXLink(ax[0])
|
||||
for cur in ax:
|
||||
if cur is not None:
|
||||
cur.addLegend(offset = (-1, 1))
|
||||
if cur is not None:
|
||||
cur.addLegend(offset = (-1, 1))
|
||||
for e in G.sevents:
|
||||
cur = ax[1 if e.type == "value" else 0]
|
||||
if not cur in G.curveno:
|
||||
|
||||
+6
-2
@@ -91,9 +91,13 @@ class Libav < TravisDepsBuilder
|
||||
:action => :git,
|
||||
:url => "git://git.libav.org/libav.git"
|
||||
},
|
||||
"ffmpeg-stable" => {
|
||||
:action => :stable,
|
||||
:url => 'http://www.ffmpeg.org/releases/ffmpeg-3.2.2.tar.gz'
|
||||
},
|
||||
"ffmpeg-git" => {
|
||||
:action => :git,
|
||||
:url => "https://github.com/FFmpeg/FFmpeg.git"
|
||||
:url => "git://github.com/FFmpeg/FFmpeg.git"
|
||||
}
|
||||
}
|
||||
end
|
||||
@@ -106,7 +110,7 @@ end
|
||||
class LibavOsx < Libav
|
||||
def build_map
|
||||
{
|
||||
"ffmpeg-git" => { :action => :package, :url => 'ffmpeg' },
|
||||
"ffmpeg-stable" => { :action => :package, :url => 'ffmpeg' },
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ "x$TARGET" != "xx86_64-w64-mingw32" ]; then
|
||||
if [ "x$LIBAV" != "xffmpeg-git" ] || [ "x$TRAVIS_OS_NAME" != "xlinux" ]; then
|
||||
# trigger build only on one of the matrix nodes
|
||||
exit;
|
||||
fi
|
||||
|
||||
+24
-15
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
This script emulates "unique application" functionality on Linux. When starting
|
||||
@@ -19,6 +19,10 @@ will typically just fill ~/.xsession-errors with garbage.
|
||||
mpv will terminate if there are no more files to play, and running the umpv
|
||||
script after that will start a new mpv instance.
|
||||
|
||||
Note that you can control the mpv instance by writing to the command fifo:
|
||||
|
||||
echo "cycle fullscreen" > ~/.umpv_fifo
|
||||
|
||||
Note: you can supply custom mpv path and options with the MPV environment
|
||||
variable. The environment variable will be split on whitespace, and the
|
||||
first item is used as path to mpv binary and the rest is passed as options
|
||||
@@ -28,7 +32,6 @@ Note: you can supply custom mpv path and options with the MPV environment
|
||||
|
||||
import sys
|
||||
import os
|
||||
import socket
|
||||
import errno
|
||||
import subprocess
|
||||
import fcntl
|
||||
@@ -54,34 +57,40 @@ def make_abs(filename):
|
||||
return filename
|
||||
files = [make_abs(f) for f in files]
|
||||
|
||||
SOCK = os.path.join(os.getenv("HOME"), ".umpv_socket")
|
||||
FIFO = os.path.join(os.getenv("HOME"), ".umpv_fifo")
|
||||
|
||||
sock = None
|
||||
fifo_fd = -1
|
||||
try:
|
||||
sock = socket.socket(socket.AF_UNIX)
|
||||
sock.connect(SOCK)
|
||||
except socket.error as e:
|
||||
if e.errno == errno.ECONNREFUSED:
|
||||
sock = None
|
||||
pass # abandoned socket
|
||||
fifo_fd = os.open(FIFO, os.O_NONBLOCK | os.O_WRONLY)
|
||||
except OSError as e:
|
||||
if e.errno == errno.ENXIO:
|
||||
pass # pipe has no writer
|
||||
elif e.errno == errno.ENOENT:
|
||||
sock = None
|
||||
pass # doesn't exist
|
||||
else:
|
||||
raise e
|
||||
|
||||
if sock:
|
||||
if fifo_fd >= 0:
|
||||
# Unhandled race condition: what if mpv is terminating right now?
|
||||
fcntl.fcntl(fifo_fd, fcntl.F_SETFL, 0) # set blocking mode
|
||||
fifo = os.fdopen(fifo_fd, "w")
|
||||
for f in files:
|
||||
# escape: \ \n "
|
||||
f = f.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n")
|
||||
f = "\"" + f + "\""
|
||||
sock.send(("raw loadfile " + f + " append\n").encode("utf-8"))
|
||||
fifo.write("raw loadfile " + f + " append\n")
|
||||
else:
|
||||
# Let mpv recreate socket if it doesn't already exist.
|
||||
# Recreate pipe if it doesn't already exist.
|
||||
# Also makes sure it's safe, and no other user can create a bogus pipe
|
||||
# that breaks security.
|
||||
try:
|
||||
os.unlink(FIFO)
|
||||
except OSError as e:
|
||||
pass
|
||||
os.mkfifo(FIFO, 0o600)
|
||||
|
||||
opts = (os.getenv("MPV") or "mpv").split()
|
||||
opts.extend(["--no-terminal", "--force-window", "--input-ipc-server=" + SOCK,
|
||||
opts.extend(["--no-terminal", "--force-window", "--input-file=" + FIFO,
|
||||
"--"])
|
||||
opts.extend(files)
|
||||
|
||||
|
||||
Executable
+283
@@ -0,0 +1,283 @@
|
||||
#!/usr/bin/perl
|
||||
|
||||
# Generate ZSH completion
|
||||
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use warnings FATAL => 'uninitialized';
|
||||
|
||||
my $mpv = $ARGV[0] || 'mpv';
|
||||
|
||||
my @opts = parse_main_opts('--list-options', '^ (\-\-[^\s\*]*)\*?\s*(.*)');
|
||||
|
||||
die "Couldn't find any options" unless (@opts);
|
||||
|
||||
my @ao = parse_opts('--ao=help', '^ ([^\s\:]*)\s*: (.*)');
|
||||
my @vo = parse_opts('--vo=help', '^ ([^\s\:]*)\s*: (.*)');
|
||||
|
||||
my @af = parse_opts('--af=help', '^ ([^\s\:]*)\s*: (.*)');
|
||||
my @vf = parse_opts('--vf=help', '^ ([^\s\:]*)\s*: (.*)');
|
||||
|
||||
my @protos = parse_opts('--list-protocols', '^ ([^\s]*)');
|
||||
|
||||
my ($opts_str, $ao_str, $vo_str, $af_str, $vf_str, $protos_str);
|
||||
|
||||
$opts_str .= qq{ '$_' \\\n} foreach (@opts);
|
||||
chomp $opts_str;
|
||||
|
||||
$ao_str .= qq{ '$_' \\\n} foreach (@ao);
|
||||
chomp $ao_str;
|
||||
|
||||
$vo_str .= qq{ '$_' \\\n} foreach (@vo);
|
||||
chomp $vo_str;
|
||||
|
||||
$af_str .= qq{ '$_' \\\n} foreach (@af);
|
||||
chomp $af_str;
|
||||
|
||||
$vf_str .= qq{ '$_' \\\n} foreach (@vf);
|
||||
chomp $vf_str;
|
||||
|
||||
$protos_str = join(' ', @protos);
|
||||
|
||||
my $runtime_completions = <<'EOS';
|
||||
profile|show-profile)
|
||||
local -a profiles
|
||||
local current
|
||||
for current in "${(@f)$($words[1] --profile=help)}"; do
|
||||
current=${current//\*/\\\*}
|
||||
current=${current//\:/\\\:}
|
||||
current=${current//\[/\\\[}
|
||||
current=${current//\]/\\\]}
|
||||
if [[ $current =~ $'\t'([^$'\t']*)$'\t'(.*) ]]; then
|
||||
if [[ -n $match[2] ]]; then
|
||||
current="$match[1][$match[2]]"
|
||||
else
|
||||
current="$match[1]"
|
||||
fi
|
||||
profiles=($profiles $current)
|
||||
fi
|
||||
done
|
||||
if [[ $state == show-profile ]]; then
|
||||
# For --show-profile, only one allowed
|
||||
if (( ${#profiles} > 0 )); then
|
||||
_values 'profile' $profiles && rc=0
|
||||
fi
|
||||
else
|
||||
# For --profile, multiple allowed
|
||||
profiles=($profiles 'help[list profiles]')
|
||||
_values -s , 'profile(s)' $profiles && rc=0
|
||||
fi
|
||||
;;
|
||||
|
||||
audio-device)
|
||||
local -a audio_devices
|
||||
local current
|
||||
for current in "${(@f)$($words[1] --audio-device=help)}"; do
|
||||
current=${current//\*/\\\*}
|
||||
current=${current//\:/\\\:}
|
||||
current=${current//\[/\\\[}
|
||||
current=${current//\]/\\\]}
|
||||
if [[ $current =~ ' '\'([^\']*)\'' \('(.*)'\)' ]]; then
|
||||
audio_devices=($audio_devices "$match[1][$match[2]]")
|
||||
fi
|
||||
done
|
||||
audio_devices=($audio_devices 'help[list audio devices]')
|
||||
_values 'audio device' $audio_devices && rc=0
|
||||
;;
|
||||
EOS
|
||||
chomp $runtime_completions;
|
||||
|
||||
my $tmpl = <<"EOS";
|
||||
#compdef mpv
|
||||
|
||||
# For customization, see:
|
||||
# https://github.com/mpv-player/mpv/wiki/Zsh-completion-customization
|
||||
|
||||
local curcontext="\$curcontext" state state_descr line
|
||||
typeset -A opt_args
|
||||
|
||||
local -a match mbegin mend
|
||||
local MATCH MBEGIN MEND
|
||||
|
||||
# By default, don't complete URLs unless no files match
|
||||
local -a tag_order
|
||||
zstyle -a ":completion:*:*:\$service:*" tag-order tag_order || \
|
||||
zstyle ":completion:*:*:\$service:*" tag-order '!urls'
|
||||
|
||||
local rc=1
|
||||
|
||||
_arguments -C -S \\
|
||||
$opts_str
|
||||
'*:files:->mfiles' && rc=0
|
||||
|
||||
case \$state in
|
||||
ao)
|
||||
_values -s , 'audio outputs' \\
|
||||
$ao_str
|
||||
&& rc=0
|
||||
;;
|
||||
|
||||
vo)
|
||||
_values -s , 'video outputs' \\
|
||||
$vo_str
|
||||
&& rc=0
|
||||
;;
|
||||
|
||||
af)
|
||||
_values -s , 'audio filters' \\
|
||||
$af_str
|
||||
&& rc=0
|
||||
;;
|
||||
|
||||
vf)
|
||||
_values -s , 'video filters' \\
|
||||
$vf_str
|
||||
&& rc=0
|
||||
;;
|
||||
|
||||
$runtime_completions
|
||||
|
||||
files)
|
||||
compset -P '*,'
|
||||
compset -S ',*'
|
||||
_files -r ',/ \\t\\n\\-' && rc=0
|
||||
;;
|
||||
|
||||
mfiles)
|
||||
local expl
|
||||
_tags files urls
|
||||
while _tags; do
|
||||
_requested files expl 'media file' _files && rc=0
|
||||
if _requested urls; then
|
||||
while _next_label urls expl URL; do
|
||||
_urls "\$expl[@]" && rc=0
|
||||
compadd -S '' "\$expl[@]" $protos_str && rc=0
|
||||
done
|
||||
fi
|
||||
(( rc )) || return 0
|
||||
done
|
||||
;;
|
||||
esac
|
||||
|
||||
return rc
|
||||
EOS
|
||||
|
||||
print $tmpl;
|
||||
|
||||
sub parse_main_opts {
|
||||
my ($cmd, $regex) = @_;
|
||||
|
||||
my @list;
|
||||
my @lines = call_mpv($cmd);
|
||||
|
||||
foreach my $line (@lines) {
|
||||
my ($name, $desc) = ($line =~ /^$regex/) or next;
|
||||
|
||||
next if ($desc eq 'removed' || $desc eq 'alias');
|
||||
|
||||
if ($desc =~ /^Flag/) {
|
||||
|
||||
push @list, $name;
|
||||
|
||||
$name =~ /^--(.*)/;
|
||||
if ($1 !~ /^(\{|\}|v|list-options|really-quiet|no-.*)$/) {
|
||||
push @list, "--no-$1";
|
||||
}
|
||||
|
||||
} elsif ($desc =~ /^Print/) {
|
||||
|
||||
push @list, $name;
|
||||
|
||||
} else {
|
||||
|
||||
# Option takes argument
|
||||
|
||||
my $entry = $name;
|
||||
|
||||
$desc =~ s/\:/\\:/g;
|
||||
$entry .= "=-:$desc:";
|
||||
|
||||
if ($desc =~ /^Choices\\: ([^(]*)/) {
|
||||
my $choices = $1;
|
||||
$choices =~ s/ +$//; # strip trailing space
|
||||
$entry .= "($choices)";
|
||||
|
||||
# If "no" is one of the choices, it can also be
|
||||
# negated like a flag (--no-whatever).
|
||||
if ($choices =~ /\bno\b/) {
|
||||
$name =~ s/^--/--no-/;
|
||||
push @list, $name;
|
||||
}
|
||||
} elsif ($line =~ /\[file\]/) {
|
||||
$entry .= '->files';
|
||||
} elsif ($name =~ /^--(ao|vo|af|vf|profile|show-profile|audio-device)$/) {
|
||||
$entry .= "->$1";
|
||||
}
|
||||
push @list, $entry;
|
||||
}
|
||||
}
|
||||
|
||||
# Sort longest first, because zsh won't complete an option listed
|
||||
# after one that's a prefix of it.
|
||||
@list = sort {
|
||||
$a =~ /([^=]*)/; my $ma = $1;
|
||||
$b =~ /([^=]*)/; my $mb = $1;
|
||||
|
||||
length($mb) <=> length($ma)
|
||||
} @list;
|
||||
|
||||
return @list;
|
||||
}
|
||||
|
||||
sub parse_opts {
|
||||
my ($cmd, $regex) = @_;
|
||||
|
||||
my @list;
|
||||
my @lines = call_mpv($cmd);
|
||||
|
||||
foreach my $line (@lines) {
|
||||
if ($line !~ /^$regex/) {
|
||||
next;
|
||||
}
|
||||
|
||||
my $entry = $1;
|
||||
|
||||
if (defined $2) {
|
||||
my $desc = $2;
|
||||
$desc =~ s/\:/\\:/g;
|
||||
$entry .= "[$desc]";
|
||||
}
|
||||
|
||||
push @list, $entry
|
||||
}
|
||||
|
||||
return @list;
|
||||
}
|
||||
|
||||
sub call_mpv {
|
||||
my ($cmd) = @_;
|
||||
my $output = `"$mpv" --no-config $cmd`;
|
||||
if ($? == -1) {
|
||||
die "Could not run mpv: $!";
|
||||
} elsif ((my $exit_code = $? >> 8) != 0) {
|
||||
die "mpv returned $exit_code with output:\n$output";
|
||||
}
|
||||
return split /\n/, $output;
|
||||
}
|
||||
+4
-19
@@ -2,7 +2,6 @@ branches:
|
||||
only:
|
||||
- master
|
||||
- appveyor
|
||||
- ci
|
||||
|
||||
environment:
|
||||
global:
|
||||
@@ -15,28 +14,14 @@ shallow_clone: true
|
||||
test: off
|
||||
|
||||
install:
|
||||
# Work around age/brokenness of Appveyor's msys2 by adding the new
|
||||
# maintainer's keyring.
|
||||
- bash -lc "curl -O http://repo.msys2.org/msys/x86_64/msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz"
|
||||
- bash -lc "curl -O http://repo.msys2.org/msys/x86_64/msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig"
|
||||
- bash -lc "pacman-key --verify msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig"
|
||||
- bash -lc "pacman -U --noconfirm msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz"
|
||||
# Support for Ada and Objective-C was removed from MSYS2. GCC won't update if
|
||||
# these packages are installed.
|
||||
- >-
|
||||
C:\msys64\usr\bin\pacman -R --noconfirm --noprogressbar
|
||||
mingw-w64-i686-gcc-ada mingw-w64-i686-gcc-objc mingw-w64-x86_64-gcc-ada
|
||||
mingw-w64-x86_64-gcc-objc
|
||||
# Update core packages
|
||||
- C:\msys64\usr\bin\pacman -Syyuu --noconfirm --noprogressbar --ask=20
|
||||
# Explicitly kill any remaining msys2 processes after core update
|
||||
- taskkill /f /fi "MODULES eq msys-2.0.dll"
|
||||
- C:\msys64\usr\bin\pacman -Syyuu --noconfirm
|
||||
# Update non-core packages
|
||||
- C:\msys64\usr\bin\pacman -Suu --noconfirm --noprogressbar --ask=20
|
||||
- C:\msys64\usr\bin\pacman -Suu --noconfirm
|
||||
# Install required MSYS2 packages
|
||||
- C:\msys64\usr\bin\pacman -S --noconfirm --noprogressbar --needed base-devel perl python pkg-config
|
||||
- C:\msys64\usr\bin\pacman -S --noconfirm --needed perl python pkg-config
|
||||
# Now MSYS2 is up to date, do the rest of the install from a bash script
|
||||
- C:\msys64\usr\bin\bash -lc "cd && exec \"$APPVEYOR_BUILD_FOLDER\"/TOOLS/appveyor-install.sh"
|
||||
- C:\msys64\usr\bin\bash -lc "cd \"$APPVEYOR_BUILD_FOLDER\" && exec ./TOOLS/appveyor-install.sh"
|
||||
build_script:
|
||||
- C:\msys64\usr\bin\bash -lc "cd \"$APPVEYOR_BUILD_FOLDER\" && exec ./TOOLS/appveyor-build.sh"
|
||||
on_failure:
|
||||
|
||||
+23
-231
@@ -32,11 +32,6 @@ struct mp_aframe {
|
||||
// We support spdif formats, which are allocated as AV_SAMPLE_FMT_S16.
|
||||
int format;
|
||||
double pts;
|
||||
double speed;
|
||||
};
|
||||
|
||||
struct avframe_opaque {
|
||||
double speed;
|
||||
};
|
||||
|
||||
static void free_frame(void *ptr)
|
||||
@@ -48,11 +43,11 @@ static void free_frame(void *ptr)
|
||||
struct mp_aframe *mp_aframe_create(void)
|
||||
{
|
||||
struct mp_aframe *frame = talloc_zero(NULL, struct mp_aframe);
|
||||
frame->pts = MP_NOPTS_VALUE;
|
||||
frame->av_frame = av_frame_alloc();
|
||||
if (!frame->av_frame)
|
||||
abort();
|
||||
talloc_set_destructor(frame, free_frame);
|
||||
mp_aframe_reset(frame);
|
||||
return frame;
|
||||
}
|
||||
|
||||
@@ -66,7 +61,6 @@ struct mp_aframe *mp_aframe_new_ref(struct mp_aframe *frame)
|
||||
dst->chmap = frame->chmap;
|
||||
dst->format = frame->format;
|
||||
dst->pts = frame->pts;
|
||||
dst->speed = frame->speed;
|
||||
|
||||
if (mp_aframe_is_allocated(frame)) {
|
||||
if (av_frame_ref(dst->av_frame, frame->av_frame) < 0)
|
||||
@@ -86,7 +80,6 @@ void mp_aframe_reset(struct mp_aframe *frame)
|
||||
frame->chmap.num = 0;
|
||||
frame->format = 0;
|
||||
frame->pts = MP_NOPTS_VALUE;
|
||||
frame->speed = 1.0;
|
||||
}
|
||||
|
||||
// Remove all actual audio data and leave only the metadata.
|
||||
@@ -100,19 +93,6 @@ void mp_aframe_unref_data(struct mp_aframe *frame)
|
||||
talloc_free(tmp);
|
||||
}
|
||||
|
||||
// Allocate this much data. Returns false for failure (data already allocated,
|
||||
// invalid sample count or format, allocation failures).
|
||||
// Normally you're supposed to use a frame pool and mp_aframe_pool_allocate().
|
||||
bool mp_aframe_alloc_data(struct mp_aframe *frame, int samples)
|
||||
{
|
||||
if (mp_aframe_is_allocated(frame))
|
||||
return false;
|
||||
struct mp_aframe_pool *p = mp_aframe_pool_create(NULL);
|
||||
int r = mp_aframe_pool_allocate(p, frame, samples);
|
||||
talloc_free(p);
|
||||
return r >= 0;
|
||||
}
|
||||
|
||||
// Return a new reference to the data in av_frame. av_frame itself is not
|
||||
// touched. Returns NULL if not representable, or if input is NULL.
|
||||
// Does not copy the timestamps.
|
||||
@@ -134,14 +114,11 @@ struct mp_aframe *mp_aframe_from_avframe(struct AVFrame *av_frame)
|
||||
frame->format = format;
|
||||
mp_chmap_from_lavc(&frame->chmap, frame->av_frame->channel_layout);
|
||||
|
||||
#if LIBAVUTIL_VERSION_MICRO >= 100
|
||||
// FFmpeg being a stupid POS again
|
||||
if (frame->chmap.num != frame->av_frame->channels)
|
||||
mp_chmap_from_channels(&frame->chmap, av_frame->channels);
|
||||
|
||||
if (av_frame->opaque_ref) {
|
||||
struct avframe_opaque *op = (void *)av_frame->opaque_ref->data;
|
||||
frame->speed = op->speed;
|
||||
}
|
||||
#endif
|
||||
|
||||
return frame;
|
||||
}
|
||||
@@ -160,16 +137,6 @@ struct AVFrame *mp_aframe_to_avframe(struct mp_aframe *frame)
|
||||
if (!mp_chmap_is_lavc(&frame->chmap))
|
||||
return NULL;
|
||||
|
||||
if (!frame->av_frame->opaque_ref && frame->speed != 1.0) {
|
||||
frame->av_frame->opaque_ref =
|
||||
av_buffer_alloc(sizeof(struct avframe_opaque));
|
||||
if (!frame->av_frame->opaque_ref)
|
||||
return NULL;
|
||||
|
||||
struct avframe_opaque *op = (void *)frame->av_frame->opaque_ref->data;
|
||||
op->speed = frame->speed;
|
||||
}
|
||||
|
||||
return av_frame_clone(frame->av_frame);
|
||||
}
|
||||
|
||||
@@ -199,29 +166,16 @@ void mp_aframe_config_copy(struct mp_aframe *dst, struct mp_aframe *src)
|
||||
|
||||
dst->chmap = src->chmap;
|
||||
dst->format = src->format;
|
||||
|
||||
mp_aframe_copy_attributes(dst, src);
|
||||
|
||||
dst->av_frame->sample_rate = src->av_frame->sample_rate;
|
||||
dst->av_frame->format = src->av_frame->format;
|
||||
dst->av_frame->channel_layout = src->av_frame->channel_layout;
|
||||
// FFmpeg being a stupid POS again
|
||||
dst->av_frame->channels = src->av_frame->channels;
|
||||
}
|
||||
|
||||
// Copy "soft" attributes from src to dst, excluding things which affect
|
||||
// frame allocation and organization.
|
||||
void mp_aframe_copy_attributes(struct mp_aframe *dst, struct mp_aframe *src)
|
||||
{
|
||||
dst->pts = src->pts;
|
||||
dst->speed = src->speed;
|
||||
|
||||
int rate = dst->av_frame->sample_rate;
|
||||
|
||||
if (av_frame_copy_props(dst->av_frame, src->av_frame) < 0)
|
||||
abort();
|
||||
|
||||
dst->av_frame->sample_rate = rate;
|
||||
dst->av_frame->format = src->av_frame->format;
|
||||
dst->av_frame->channel_layout = src->av_frame->channel_layout;
|
||||
#if LIBAVUTIL_VERSION_MICRO >= 100
|
||||
// FFmpeg being a stupid POS again
|
||||
dst->av_frame->channels = src->av_frame->channels;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Return whether a and b use the same physical audio format. Extra metadata
|
||||
@@ -320,14 +274,16 @@ bool mp_aframe_set_chmap(struct mp_aframe *frame, struct mp_chmap *in)
|
||||
return false;
|
||||
frame->chmap = *in;
|
||||
frame->av_frame->channel_layout = lavc_layout;
|
||||
#if LIBAVUTIL_VERSION_MICRO >= 100
|
||||
// FFmpeg being a stupid POS again
|
||||
frame->av_frame->channels = frame->chmap.num;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mp_aframe_set_rate(struct mp_aframe *frame, int rate)
|
||||
{
|
||||
if (rate < 1 || rate > 10000000)
|
||||
if (rate < 1 && rate > 10000000)
|
||||
return false;
|
||||
frame->av_frame->sample_rate = rate;
|
||||
return true;
|
||||
@@ -346,37 +302,6 @@ void mp_aframe_set_pts(struct mp_aframe *frame, double pts)
|
||||
frame->pts = pts;
|
||||
}
|
||||
|
||||
// Set a speed factor. This is multiplied with the sample rate to get the
|
||||
// "effective" samplerate (mp_aframe_get_effective_rate()), which will be used
|
||||
// to do PTS calculations. If speed!=1.0, the PTS values always refer to the
|
||||
// original PTS (before changing speed), and if you want reasonably continuous
|
||||
// PTS between frames, you need to use the effective samplerate.
|
||||
void mp_aframe_set_speed(struct mp_aframe *frame, double factor)
|
||||
{
|
||||
frame->speed = factor;
|
||||
}
|
||||
|
||||
// Adjust current speed factor.
|
||||
void mp_aframe_mul_speed(struct mp_aframe *frame, double factor)
|
||||
{
|
||||
frame->speed *= factor;
|
||||
}
|
||||
|
||||
double mp_aframe_get_speed(struct mp_aframe *frame)
|
||||
{
|
||||
return frame->speed;
|
||||
}
|
||||
|
||||
// Matters for speed changed frames (such as a frame which has been resampled
|
||||
// to play at a different speed).
|
||||
// Return the sample rate at which the frame would have to be played to result
|
||||
// in the same duration as the original frame before the speed change.
|
||||
// This is used for A/V sync.
|
||||
double mp_aframe_get_effective_rate(struct mp_aframe *frame)
|
||||
{
|
||||
return mp_aframe_get_rate(frame) / frame->speed;
|
||||
}
|
||||
|
||||
// Return number of data pointers.
|
||||
int mp_aframe_get_planes(struct mp_aframe *frame)
|
||||
{
|
||||
@@ -392,73 +317,46 @@ size_t mp_aframe_get_sstride(struct mp_aframe *frame)
|
||||
(af_fmt_is_planar(format) ? 1 : mp_aframe_get_channels(frame));
|
||||
}
|
||||
|
||||
// Return total number of samples on each plane.
|
||||
int mp_aframe_get_total_plane_samples(struct mp_aframe *frame)
|
||||
{
|
||||
return frame->av_frame->nb_samples *
|
||||
(af_fmt_is_planar(mp_aframe_get_format(frame))
|
||||
? 1 : mp_aframe_get_channels(frame));
|
||||
}
|
||||
|
||||
char *mp_aframe_format_str_buf(char *buf, size_t buf_size, struct mp_aframe *fmt)
|
||||
{
|
||||
char ch[128];
|
||||
mp_chmap_to_str_buf(ch, sizeof(ch), &fmt->chmap);
|
||||
char *hr_ch = mp_chmap_to_str_hr(&fmt->chmap);
|
||||
if (strcmp(hr_ch, ch) != 0)
|
||||
mp_snprintf_cat(ch, sizeof(ch), " (%s)", hr_ch);
|
||||
snprintf(buf, buf_size, "%dHz %s %dch %s", fmt->av_frame->sample_rate,
|
||||
ch, fmt->chmap.num, af_fmt_to_str(fmt->format));
|
||||
return buf;
|
||||
}
|
||||
|
||||
// Set data to the audio after the given number of samples (i.e. slice it).
|
||||
void mp_aframe_skip_samples(struct mp_aframe *f, int samples)
|
||||
{
|
||||
assert(samples >= 0 && samples <= mp_aframe_get_size(f));
|
||||
|
||||
if (av_frame_make_writable(f->av_frame) < 0)
|
||||
return; // go complain to ffmpeg
|
||||
|
||||
int num_planes = mp_aframe_get_planes(f);
|
||||
size_t sstride = mp_aframe_get_sstride(f);
|
||||
for (int n = 0; n < num_planes; n++) {
|
||||
memmove(f->av_frame->extended_data[n],
|
||||
f->av_frame->extended_data[n] + samples * sstride,
|
||||
(f->av_frame->nb_samples - samples) * sstride);
|
||||
}
|
||||
for (int n = 0; n < num_planes; n++)
|
||||
f->av_frame->extended_data[n] += samples * sstride;
|
||||
|
||||
f->av_frame->nb_samples -= samples;
|
||||
|
||||
if (f->pts != MP_NOPTS_VALUE)
|
||||
f->pts += samples / mp_aframe_get_effective_rate(f);
|
||||
f->pts += samples / (double)mp_aframe_get_rate(f);
|
||||
}
|
||||
|
||||
// Return the timestamp of the sample just after the end of this frame.
|
||||
double mp_aframe_end_pts(struct mp_aframe *f)
|
||||
{
|
||||
double rate = mp_aframe_get_effective_rate(f);
|
||||
if (f->pts == MP_NOPTS_VALUE || rate <= 0)
|
||||
int rate = mp_aframe_get_rate(f);
|
||||
if (f->pts == MP_NOPTS_VALUE || rate < 1)
|
||||
return MP_NOPTS_VALUE;
|
||||
return f->pts + f->av_frame->nb_samples / rate;
|
||||
return f->pts + f->av_frame->nb_samples / (double)rate;
|
||||
}
|
||||
|
||||
// Return the duration in seconds of the frame (0 if invalid).
|
||||
double mp_aframe_duration(struct mp_aframe *f)
|
||||
{
|
||||
double rate = mp_aframe_get_effective_rate(f);
|
||||
if (rate <= 0)
|
||||
int rate = mp_aframe_get_rate(f);
|
||||
if (rate < 1)
|
||||
return 0;
|
||||
return f->av_frame->nb_samples / rate;
|
||||
return f->av_frame->nb_samples / (double)rate;
|
||||
}
|
||||
|
||||
// Clip the given frame to the given timestamp range. Adjusts the frame size
|
||||
// and timestamp.
|
||||
// Refuses to change spdif frames.
|
||||
void mp_aframe_clip_timestamps(struct mp_aframe *f, double start, double end)
|
||||
{
|
||||
double f_end = mp_aframe_end_pts(f);
|
||||
double rate = mp_aframe_get_effective_rate(f);
|
||||
int rate = mp_aframe_get_rate(f);
|
||||
if (f_end == MP_NOPTS_VALUE)
|
||||
return;
|
||||
if (end != MP_NOPTS_VALUE) {
|
||||
@@ -466,8 +364,6 @@ void mp_aframe_clip_timestamps(struct mp_aframe *f, double start, double end)
|
||||
if (f->pts >= end) {
|
||||
f->av_frame->nb_samples = 0;
|
||||
} else {
|
||||
if (af_fmt_is_spdif(mp_aframe_get_format(f)))
|
||||
return;
|
||||
int new = (end - f->pts) * rate;
|
||||
f->av_frame->nb_samples = MPCLAMP(new, 0, f->av_frame->nb_samples);
|
||||
}
|
||||
@@ -479,8 +375,6 @@ void mp_aframe_clip_timestamps(struct mp_aframe *f, double start, double end)
|
||||
f->av_frame->nb_samples = 0;
|
||||
f->pts = f_end;
|
||||
} else {
|
||||
if (af_fmt_is_spdif(mp_aframe_get_format(f)))
|
||||
return;
|
||||
int skip = (start - f->pts) * rate;
|
||||
skip = MPCLAMP(skip, 0, f->av_frame->nb_samples);
|
||||
mp_aframe_skip_samples(f, skip);
|
||||
@@ -489,102 +383,6 @@ void mp_aframe_clip_timestamps(struct mp_aframe *f, double start, double end)
|
||||
}
|
||||
}
|
||||
|
||||
bool mp_aframe_copy_samples(struct mp_aframe *dst, int dst_offset,
|
||||
struct mp_aframe *src, int src_offset,
|
||||
int samples)
|
||||
{
|
||||
if (!mp_aframe_config_equals(dst, src))
|
||||
return false;
|
||||
|
||||
if (mp_aframe_get_size(dst) < dst_offset + samples ||
|
||||
mp_aframe_get_size(src) < src_offset + samples)
|
||||
return false;
|
||||
|
||||
uint8_t **s = mp_aframe_get_data_ro(src);
|
||||
uint8_t **d = mp_aframe_get_data_rw(dst);
|
||||
if (!s || !d)
|
||||
return false;
|
||||
|
||||
int planes = mp_aframe_get_planes(dst);
|
||||
size_t sstride = mp_aframe_get_sstride(dst);
|
||||
|
||||
for (int n = 0; n < planes; n++) {
|
||||
memcpy(d[n] + dst_offset * sstride, s[n] + src_offset * sstride,
|
||||
samples * sstride);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mp_aframe_set_silence(struct mp_aframe *f, int offset, int samples)
|
||||
{
|
||||
if (mp_aframe_get_size(f) < offset + samples)
|
||||
return false;
|
||||
|
||||
int format = mp_aframe_get_format(f);
|
||||
uint8_t **d = mp_aframe_get_data_rw(f);
|
||||
if (!d)
|
||||
return false;
|
||||
|
||||
int planes = mp_aframe_get_planes(f);
|
||||
size_t sstride = mp_aframe_get_sstride(f);
|
||||
|
||||
for (int n = 0; n < planes; n++)
|
||||
af_fill_silence(d[n] + offset * sstride, samples * sstride, format);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mp_aframe_reverse(struct mp_aframe *f)
|
||||
{
|
||||
int format = mp_aframe_get_format(f);
|
||||
size_t bps = af_fmt_to_bytes(format);
|
||||
if (!af_fmt_is_pcm(format) || bps > 16)
|
||||
return false;
|
||||
|
||||
uint8_t **d = mp_aframe_get_data_rw(f);
|
||||
if (!d)
|
||||
return false;
|
||||
|
||||
int planes = mp_aframe_get_planes(f);
|
||||
int samples = mp_aframe_get_size(f);
|
||||
int channels = mp_aframe_get_channels(f);
|
||||
size_t sstride = mp_aframe_get_sstride(f);
|
||||
|
||||
int plane_samples = channels;
|
||||
if (af_fmt_is_planar(format))
|
||||
plane_samples = 1;
|
||||
|
||||
for (int p = 0; p < planes; p++) {
|
||||
for (int n = 0; n < samples / 2; n++) {
|
||||
int s1_offset = n * sstride;
|
||||
int s2_offset = (samples - 1 - n) * sstride;
|
||||
for (int c = 0; c < plane_samples; c++) {
|
||||
// Nobody said it'd be fast.
|
||||
char tmp[16];
|
||||
uint8_t *s1 = d[p] + s1_offset + c * bps;
|
||||
uint8_t *s2 = d[p] + s2_offset + c * bps;
|
||||
memcpy(tmp, s2, bps);
|
||||
memcpy(s2, s1, bps);
|
||||
memcpy(s1, tmp, bps);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int mp_aframe_approx_byte_size(struct mp_aframe *frame)
|
||||
{
|
||||
// God damn, AVFrame is too fucking annoying. Just go with the size that
|
||||
// allocating a new frame would use.
|
||||
int planes = mp_aframe_get_planes(frame);
|
||||
size_t sstride = mp_aframe_get_sstride(frame);
|
||||
int samples = frame->av_frame->nb_samples;
|
||||
int plane_size = MP_ALIGN_UP(sstride * MPMAX(samples, 1), 32);
|
||||
return plane_size * planes + sizeof(*frame);
|
||||
}
|
||||
|
||||
struct mp_aframe_pool {
|
||||
AVBufferPool *avpool;
|
||||
int element_size;
|
||||
@@ -607,11 +405,7 @@ int mp_aframe_pool_allocate(struct mp_aframe_pool *pool, struct mp_aframe *frame
|
||||
{
|
||||
int planes = mp_aframe_get_planes(frame);
|
||||
size_t sstride = mp_aframe_get_sstride(frame);
|
||||
// FFmpeg hardcodes similar hidden possibly-requirements in a number of
|
||||
// places: av_frame_get_buffer(), libavcodec's get_buffer(), mem.c,
|
||||
// probably more.
|
||||
int align_samples = MP_ALIGN_UP(MPMAX(samples, 1), 32);
|
||||
int plane_size = MP_ALIGN_UP(sstride * align_samples, 64);
|
||||
int plane_size = MP_ALIGN_UP(sstride * MPMAX(samples, 1), 32);
|
||||
int size = plane_size * planes;
|
||||
|
||||
if (size <= 0 || mp_aframe_is_allocated(frame))
|
||||
@@ -645,8 +439,6 @@ int mp_aframe_pool_allocate(struct mp_aframe_pool *pool, struct mp_aframe *frame
|
||||
av_frame->linesize[0] = samples * sstride;
|
||||
for (int n = 0; n < planes; n++)
|
||||
av_frame->extended_data[n] = av_frame->buf[0]->data + n * plane_size;
|
||||
for (int n = 0; n < MPMIN(planes, AV_NUM_DATA_POINTERS); n++)
|
||||
av_frame->data[n] = av_frame->extended_data[n];
|
||||
av_frame->nb_samples = samples;
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
struct mp_aframe;
|
||||
struct AVFrame;
|
||||
@@ -20,14 +18,11 @@ struct AVFrame *mp_aframe_to_avframe_and_unref(struct mp_aframe *frame);
|
||||
struct AVFrame *mp_aframe_get_raw_avframe(struct mp_aframe *frame);
|
||||
|
||||
bool mp_aframe_is_allocated(struct mp_aframe *frame);
|
||||
bool mp_aframe_alloc_data(struct mp_aframe *frame, int samples);
|
||||
|
||||
void mp_aframe_config_copy(struct mp_aframe *dst, struct mp_aframe *src);
|
||||
bool mp_aframe_config_equals(struct mp_aframe *a, struct mp_aframe *b);
|
||||
bool mp_aframe_config_is_valid(struct mp_aframe *frame);
|
||||
|
||||
void mp_aframe_copy_attributes(struct mp_aframe *dst, struct mp_aframe *src);
|
||||
|
||||
uint8_t **mp_aframe_get_data_ro(struct mp_aframe *frame);
|
||||
uint8_t **mp_aframe_get_data_rw(struct mp_aframe *frame);
|
||||
|
||||
@@ -37,36 +32,20 @@ int mp_aframe_get_channels(struct mp_aframe *frame);
|
||||
int mp_aframe_get_rate(struct mp_aframe *frame);
|
||||
int mp_aframe_get_size(struct mp_aframe *frame);
|
||||
double mp_aframe_get_pts(struct mp_aframe *frame);
|
||||
double mp_aframe_get_speed(struct mp_aframe *frame);
|
||||
double mp_aframe_get_effective_rate(struct mp_aframe *frame);
|
||||
|
||||
bool mp_aframe_set_format(struct mp_aframe *frame, int format);
|
||||
bool mp_aframe_set_chmap(struct mp_aframe *frame, struct mp_chmap *in);
|
||||
bool mp_aframe_set_rate(struct mp_aframe *frame, int rate);
|
||||
bool mp_aframe_set_size(struct mp_aframe *frame, int samples);
|
||||
void mp_aframe_set_pts(struct mp_aframe *frame, double pts);
|
||||
void mp_aframe_set_speed(struct mp_aframe *frame, double factor);
|
||||
void mp_aframe_mul_speed(struct mp_aframe *frame, double factor);
|
||||
|
||||
int mp_aframe_get_planes(struct mp_aframe *frame);
|
||||
int mp_aframe_get_total_plane_samples(struct mp_aframe *frame);
|
||||
size_t mp_aframe_get_sstride(struct mp_aframe *frame);
|
||||
|
||||
bool mp_aframe_reverse(struct mp_aframe *frame);
|
||||
|
||||
int mp_aframe_approx_byte_size(struct mp_aframe *frame);
|
||||
|
||||
char *mp_aframe_format_str_buf(char *buf, size_t buf_size, struct mp_aframe *fmt);
|
||||
#define mp_aframe_format_str(fmt) mp_aframe_format_str_buf((char[32]){0}, 32, (fmt))
|
||||
|
||||
void mp_aframe_skip_samples(struct mp_aframe *f, int samples);
|
||||
double mp_aframe_end_pts(struct mp_aframe *f);
|
||||
double mp_aframe_duration(struct mp_aframe *f);
|
||||
void mp_aframe_clip_timestamps(struct mp_aframe *f, double start, double end);
|
||||
bool mp_aframe_copy_samples(struct mp_aframe *dst, int dst_offset,
|
||||
struct mp_aframe *src, int src_offset,
|
||||
int samples);
|
||||
bool mp_aframe_set_silence(struct mp_aframe *f, int offset, int samples);
|
||||
|
||||
struct mp_aframe_pool;
|
||||
struct mp_aframe_pool *mp_aframe_pool_create(void *ta_parent);
|
||||
|
||||
+594
@@ -0,0 +1,594 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <libavutil/buffer.h>
|
||||
#include <libavutil/frame.h>
|
||||
#include <libavutil/mem.h>
|
||||
#include <libavutil/version.h>
|
||||
|
||||
#include "mpv_talloc.h"
|
||||
#include "common/common.h"
|
||||
#include "fmt-conversion.h"
|
||||
#include "audio.h"
|
||||
#include "aframe.h"
|
||||
|
||||
static void update_redundant_info(struct mp_audio *mpa)
|
||||
{
|
||||
assert(mp_chmap_is_empty(&mpa->channels) ||
|
||||
mp_chmap_is_valid(&mpa->channels));
|
||||
mpa->nch = mpa->channels.num;
|
||||
mpa->bps = af_fmt_to_bytes(mpa->format);
|
||||
if (af_fmt_is_planar(mpa->format)) {
|
||||
mpa->spf = 1;
|
||||
mpa->num_planes = mpa->nch;
|
||||
mpa->sstride = mpa->bps;
|
||||
} else {
|
||||
mpa->spf = mpa->nch;
|
||||
mpa->num_planes = 1;
|
||||
mpa->sstride = mpa->bps * mpa->nch;
|
||||
}
|
||||
}
|
||||
|
||||
void mp_audio_set_format(struct mp_audio *mpa, int format)
|
||||
{
|
||||
mpa->format = format;
|
||||
update_redundant_info(mpa);
|
||||
}
|
||||
|
||||
void mp_audio_set_num_channels(struct mp_audio *mpa, int num_channels)
|
||||
{
|
||||
mp_chmap_from_channels(&mpa->channels, num_channels);
|
||||
update_redundant_info(mpa);
|
||||
}
|
||||
|
||||
void mp_audio_set_channels(struct mp_audio *mpa, const struct mp_chmap *chmap)
|
||||
{
|
||||
mpa->channels = *chmap;
|
||||
update_redundant_info(mpa);
|
||||
}
|
||||
|
||||
void mp_audio_copy_config(struct mp_audio *dst, const struct mp_audio *src)
|
||||
{
|
||||
dst->format = src->format;
|
||||
dst->channels = src->channels;
|
||||
dst->rate = src->rate;
|
||||
update_redundant_info(dst);
|
||||
}
|
||||
|
||||
bool mp_audio_config_equals(const struct mp_audio *a, const struct mp_audio *b)
|
||||
{
|
||||
return a->format == b->format && a->rate == b->rate &&
|
||||
mp_chmap_equals(&a->channels, &b->channels);
|
||||
}
|
||||
|
||||
bool mp_audio_config_valid(const struct mp_audio *mpa)
|
||||
{
|
||||
return mp_chmap_is_valid(&mpa->channels) && af_fmt_is_valid(mpa->format)
|
||||
&& mpa->rate >= 1 && mpa->rate < 10000000;
|
||||
}
|
||||
|
||||
char *mp_audio_config_to_str_buf(char *buf, size_t buf_sz, struct mp_audio *mpa)
|
||||
{
|
||||
char ch[128];
|
||||
mp_chmap_to_str_buf(ch, sizeof(ch), &mpa->channels);
|
||||
char *hr_ch = mp_chmap_to_str_hr(&mpa->channels);
|
||||
if (strcmp(hr_ch, ch) != 0)
|
||||
mp_snprintf_cat(ch, sizeof(ch), " (%s)", hr_ch);
|
||||
snprintf(buf, buf_sz, "%dHz %s %dch %s", mpa->rate,
|
||||
ch, mpa->channels.num, af_fmt_to_str(mpa->format));
|
||||
return buf;
|
||||
}
|
||||
|
||||
void mp_audio_force_interleaved_format(struct mp_audio *mpa)
|
||||
{
|
||||
if (af_fmt_is_planar(mpa->format))
|
||||
mp_audio_set_format(mpa, af_fmt_from_planar(mpa->format));
|
||||
}
|
||||
|
||||
// Return used size of a plane. (The size is the same for all planes.)
|
||||
int mp_audio_psize(struct mp_audio *mpa)
|
||||
{
|
||||
return mpa->samples * mpa->sstride;
|
||||
}
|
||||
|
||||
void mp_audio_set_null_data(struct mp_audio *mpa)
|
||||
{
|
||||
for (int n = 0; n < MP_NUM_CHANNELS; n++) {
|
||||
mpa->planes[n] = NULL;
|
||||
mpa->allocated[n] = NULL;
|
||||
}
|
||||
mpa->samples = 0;
|
||||
}
|
||||
|
||||
static int get_plane_size(const struct mp_audio *mpa, int samples)
|
||||
{
|
||||
if (samples < 0 || !mp_audio_config_valid(mpa))
|
||||
return -1;
|
||||
if (samples >= INT_MAX / mpa->sstride)
|
||||
return -1;
|
||||
return MPMAX(samples * mpa->sstride, 1);
|
||||
}
|
||||
|
||||
static void mp_audio_destructor(void *ptr)
|
||||
{
|
||||
struct mp_audio *mpa = ptr;
|
||||
for (int n = 0; n < MP_NUM_CHANNELS; n++)
|
||||
av_buffer_unref(&mpa->allocated[n]);
|
||||
}
|
||||
|
||||
/* Reallocate the data stored in mpa->planes[n] so that enough samples are
|
||||
* available on every plane. The previous data is kept (for the smallest
|
||||
* common number of samples before/after resize).
|
||||
*
|
||||
* This also makes sure the resulting buffer is writable (even in the case
|
||||
* the buffer has the correct size).
|
||||
*
|
||||
* mpa->samples is not set or used.
|
||||
*
|
||||
* This function is flexible enough to handle format and channel layout
|
||||
* changes. In these cases, all planes are reallocated as needed. Unused
|
||||
* planes are freed.
|
||||
*
|
||||
* mp_audio_realloc(mpa, 0) will still yield non-NULL for mpa->data[n].
|
||||
*
|
||||
* Allocated data is implicitly freed on talloc_free(mpa).
|
||||
*/
|
||||
void mp_audio_realloc(struct mp_audio *mpa, int samples)
|
||||
{
|
||||
int size = get_plane_size(mpa, samples);
|
||||
if (size < 0)
|
||||
abort(); // oom or invalid parameters
|
||||
if (!mp_audio_is_writeable(mpa)) {
|
||||
for (int n = 0; n < MP_NUM_CHANNELS; n++) {
|
||||
av_buffer_unref(&mpa->allocated[n]);
|
||||
mpa->planes[n] = NULL;
|
||||
}
|
||||
}
|
||||
for (int n = 0; n < mpa->num_planes; n++) {
|
||||
if (!mpa->allocated[n] || size != mpa->allocated[n]->size) {
|
||||
if (av_buffer_realloc(&mpa->allocated[n], size) < 0)
|
||||
abort(); // OOM
|
||||
}
|
||||
mpa->planes[n] = mpa->allocated[n]->data;
|
||||
}
|
||||
for (int n = mpa->num_planes; n < MP_NUM_CHANNELS; n++) {
|
||||
av_buffer_unref(&mpa->allocated[n]);
|
||||
mpa->planes[n] = NULL;
|
||||
}
|
||||
talloc_set_destructor(mpa, mp_audio_destructor);
|
||||
}
|
||||
|
||||
// Like mp_audio_realloc(), but only reallocate if the audio grows in size.
|
||||
// If the buffer is reallocated, also preallocate.
|
||||
void mp_audio_realloc_min(struct mp_audio *mpa, int samples)
|
||||
{
|
||||
if (samples > mp_audio_get_allocated_size(mpa) || !mp_audio_is_writeable(mpa)) {
|
||||
size_t alloc = ta_calc_prealloc_elems(samples);
|
||||
if (alloc > INT_MAX)
|
||||
abort(); // oom
|
||||
mp_audio_realloc(mpa, alloc);
|
||||
}
|
||||
}
|
||||
|
||||
/* Get the size allocated for the data, in number of samples. If the allocated
|
||||
* size isn't on sample boundaries (e.g. after format changes), the returned
|
||||
* sample number is a rounded down value.
|
||||
*
|
||||
* Note that this only works in situations where mp_audio_realloc() also works!
|
||||
*/
|
||||
int mp_audio_get_allocated_size(struct mp_audio *mpa)
|
||||
{
|
||||
int size = 0;
|
||||
for (int n = 0; n < mpa->num_planes; n++) {
|
||||
for (int i = 0; i < MP_NUM_CHANNELS && mpa->allocated[i]; i++) {
|
||||
uint8_t *start = mpa->allocated[i]->data;
|
||||
uint8_t *end = start + mpa->allocated[i]->size;
|
||||
uint8_t *plane = mpa->planes[n];
|
||||
if (plane >= start && plane < end) {
|
||||
int s = MPMIN((end - plane) / mpa->sstride, INT_MAX);
|
||||
size = n == 0 ? s : MPMIN(size, s);
|
||||
goto next;
|
||||
}
|
||||
}
|
||||
return 0; // plane is not covered by any buffer
|
||||
next: ;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
// Clear the samples [start, start + length) with silence.
|
||||
void mp_audio_fill_silence(struct mp_audio *mpa, int start, int length)
|
||||
{
|
||||
assert(start >= 0 && length >= 0 && start + length <= mpa->samples);
|
||||
int offset = start * mpa->sstride;
|
||||
int size = length * mpa->sstride;
|
||||
for (int n = 0; n < mpa->num_planes; n++) {
|
||||
if (n > 0 && mpa->planes[n] == mpa->planes[0])
|
||||
continue; // silly optimization for special cases
|
||||
af_fill_silence((char *)mpa->planes[n] + offset, size, mpa->format);
|
||||
}
|
||||
}
|
||||
|
||||
// All integer parameters are in samples.
|
||||
// dst and src can overlap.
|
||||
void mp_audio_copy(struct mp_audio *dst, int dst_offset,
|
||||
struct mp_audio *src, int src_offset, int length)
|
||||
{
|
||||
assert(mp_audio_config_equals(dst, src));
|
||||
assert(length >= 0);
|
||||
assert(dst_offset >= 0 && dst_offset + length <= dst->samples);
|
||||
assert(src_offset >= 0 && src_offset + length <= src->samples);
|
||||
|
||||
for (int n = 0; n < dst->num_planes; n++) {
|
||||
memmove((char *)dst->planes[n] + dst_offset * dst->sstride,
|
||||
(char *)src->planes[n] + src_offset * src->sstride,
|
||||
length * dst->sstride);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy fields that describe characteristics of the audio frame, but which are
|
||||
// not part of the core format (format/channels/rate), and not part of the
|
||||
// data (samples).
|
||||
void mp_audio_copy_attributes(struct mp_audio *dst, struct mp_audio *src)
|
||||
{
|
||||
// nothing yet
|
||||
}
|
||||
|
||||
// Set data to the audio after the given number of samples (i.e. slice it).
|
||||
void mp_audio_skip_samples(struct mp_audio *data, int samples)
|
||||
{
|
||||
assert(samples >= 0 && samples <= data->samples);
|
||||
|
||||
for (int n = 0; n < data->num_planes; n++)
|
||||
data->planes[n] = (uint8_t *)data->planes[n] + samples * data->sstride;
|
||||
|
||||
data->samples -= samples;
|
||||
|
||||
if (data->pts != MP_NOPTS_VALUE)
|
||||
data->pts += samples / (double)data->rate;
|
||||
}
|
||||
|
||||
// Return the timestamp of the sample just after the end of this frame.
|
||||
double mp_audio_end_pts(struct mp_audio *f)
|
||||
{
|
||||
if (f->pts == MP_NOPTS_VALUE || f->rate < 1)
|
||||
return MP_NOPTS_VALUE;
|
||||
return f->pts + f->samples / (double)f->rate;
|
||||
}
|
||||
|
||||
// Clip the given frame to the given timestamp range. Adjusts the frame size
|
||||
// and timestamp.
|
||||
void mp_audio_clip_timestamps(struct mp_audio *f, double start, double end)
|
||||
{
|
||||
double f_end = mp_audio_end_pts(f);
|
||||
if (f_end == MP_NOPTS_VALUE)
|
||||
return;
|
||||
if (end != MP_NOPTS_VALUE) {
|
||||
if (f_end >= end) {
|
||||
if (f->pts >= end) {
|
||||
f->samples = 0;
|
||||
} else {
|
||||
int new = (end - f->pts) * f->rate;
|
||||
f->samples = MPCLAMP(new, 0, f->samples);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (start != MP_NOPTS_VALUE) {
|
||||
if (f->pts < start) {
|
||||
if (f_end <= start) {
|
||||
f->samples = 0;
|
||||
f->pts = f_end;
|
||||
} else {
|
||||
int skip = (start - f->pts) * f->rate;
|
||||
skip = MPCLAMP(skip, 0, f->samples);
|
||||
mp_audio_skip_samples(f, skip);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Return false if the frame data is shared, true otherwise.
|
||||
// Will return true for non-refcounted frames.
|
||||
bool mp_audio_is_writeable(struct mp_audio *data)
|
||||
{
|
||||
bool ok = true;
|
||||
for (int n = 0; n < MP_NUM_CHANNELS; n++) {
|
||||
if (data->allocated[n])
|
||||
ok &= av_buffer_is_writable(data->allocated[n]);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
static void mp_audio_steal_data(struct mp_audio *dst, struct mp_audio *src)
|
||||
{
|
||||
talloc_set_destructor(dst, mp_audio_destructor);
|
||||
mp_audio_destructor(dst);
|
||||
*dst = *src;
|
||||
talloc_set_destructor(src, NULL);
|
||||
talloc_free(src);
|
||||
}
|
||||
|
||||
// Make sure the frame owns the audio data, and if not, copy the data.
|
||||
// Return negative value on failure (which means it can't be made writeable).
|
||||
// Non-refcounted frames are always considered writeable.
|
||||
int mp_audio_make_writeable(struct mp_audio *data)
|
||||
{
|
||||
if (!mp_audio_is_writeable(data)) {
|
||||
struct mp_audio *new = talloc(NULL, struct mp_audio);
|
||||
*new = *data;
|
||||
mp_audio_set_null_data(new); // use format only
|
||||
mp_audio_realloc(new, data->samples);
|
||||
new->samples = data->samples;
|
||||
mp_audio_copy(new, 0, data, 0, data->samples);
|
||||
mp_audio_steal_data(data, new);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct mp_audio *mp_audio_from_avframe(struct AVFrame *avframe)
|
||||
{
|
||||
AVFrame *tmp = NULL;
|
||||
struct mp_audio *new = talloc_zero(NULL, struct mp_audio);
|
||||
talloc_set_destructor(new, mp_audio_destructor);
|
||||
|
||||
mp_audio_set_format(new, af_from_avformat(avframe->format));
|
||||
|
||||
struct mp_chmap lavc_chmap;
|
||||
mp_chmap_from_lavc(&lavc_chmap, avframe->channel_layout);
|
||||
|
||||
#if LIBAVUTIL_VERSION_MICRO >= 100
|
||||
// FFmpeg being stupid POS again
|
||||
if (lavc_chmap.num != av_frame_get_channels(avframe))
|
||||
mp_chmap_from_channels(&lavc_chmap, av_frame_get_channels(avframe));
|
||||
#endif
|
||||
|
||||
new->rate = avframe->sample_rate;
|
||||
|
||||
mp_audio_set_channels(new, &lavc_chmap);
|
||||
|
||||
// Force refcounted frame.
|
||||
if (!avframe->buf[0]) {
|
||||
tmp = av_frame_alloc();
|
||||
if (!tmp)
|
||||
goto fail;
|
||||
if (av_frame_ref(tmp, avframe) < 0)
|
||||
goto fail;
|
||||
avframe = tmp;
|
||||
}
|
||||
|
||||
// If we can't handle the format (e.g. too many channels), bail out.
|
||||
if (!mp_audio_config_valid(new))
|
||||
goto fail;
|
||||
|
||||
for (int n = 0; n < AV_NUM_DATA_POINTERS + avframe->nb_extended_buf; n++) {
|
||||
AVBufferRef *buf = n < AV_NUM_DATA_POINTERS ? avframe->buf[n]
|
||||
: avframe->extended_buf[n - AV_NUM_DATA_POINTERS];
|
||||
if (!buf)
|
||||
break;
|
||||
if (n >= MP_NUM_CHANNELS)
|
||||
goto fail;
|
||||
new->allocated[n] = av_buffer_ref(buf);
|
||||
if (!new->allocated[n])
|
||||
goto fail;
|
||||
}
|
||||
|
||||
for (int n = 0; n < new->num_planes; n++)
|
||||
new->planes[n] = avframe->extended_data[n];
|
||||
new->samples = avframe->nb_samples;
|
||||
|
||||
return new;
|
||||
|
||||
fail:
|
||||
talloc_free(new);
|
||||
av_frame_free(&tmp);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct mp_audio *mp_audio_from_aframe(struct mp_aframe *aframe)
|
||||
{
|
||||
struct AVFrame *av = mp_aframe_get_raw_avframe(aframe);
|
||||
struct mp_audio *res = mp_audio_from_avframe(av);
|
||||
if (!res)
|
||||
return NULL;
|
||||
struct mp_chmap chmap = {0};
|
||||
mp_aframe_get_chmap(aframe, &chmap);
|
||||
mp_audio_set_channels(res, &chmap);
|
||||
mp_audio_set_format(res, mp_aframe_get_format(aframe));
|
||||
res->pts = mp_aframe_get_pts(aframe);
|
||||
return res;
|
||||
}
|
||||
|
||||
void mp_audio_config_from_aframe(struct mp_audio *dst, struct mp_aframe *src)
|
||||
{
|
||||
*dst = (struct mp_audio){0};
|
||||
struct mp_chmap chmap = {0};
|
||||
mp_aframe_get_chmap(src, &chmap);
|
||||
mp_audio_set_channels(dst, &chmap);
|
||||
mp_audio_set_format(dst, mp_aframe_get_format(src));
|
||||
dst->rate = mp_aframe_get_rate(src);
|
||||
}
|
||||
|
||||
int mp_audio_to_avframe(struct mp_audio *frame, struct AVFrame *avframe)
|
||||
{
|
||||
av_frame_unref(avframe);
|
||||
|
||||
avframe->nb_samples = frame->samples;
|
||||
avframe->format = af_to_avformat(frame->format);
|
||||
if (avframe->format == AV_SAMPLE_FMT_NONE)
|
||||
goto fail;
|
||||
|
||||
avframe->channel_layout = mp_chmap_to_lavc(&frame->channels);
|
||||
if (!avframe->channel_layout)
|
||||
goto fail;
|
||||
#if LIBAVUTIL_VERSION_MICRO >= 100
|
||||
// FFmpeg being a stupid POS again
|
||||
av_frame_set_channels(avframe, frame->channels.num);
|
||||
#endif
|
||||
avframe->sample_rate = frame->rate;
|
||||
|
||||
if (frame->num_planes > AV_NUM_DATA_POINTERS) {
|
||||
avframe->extended_data =
|
||||
av_mallocz_array(frame->num_planes, sizeof(avframe->extended_data[0]));
|
||||
int extbufs = frame->num_planes - AV_NUM_DATA_POINTERS;
|
||||
avframe->extended_buf =
|
||||
av_mallocz_array(extbufs, sizeof(avframe->extended_buf[0]));
|
||||
if (!avframe->extended_data || !avframe->extended_buf)
|
||||
goto fail;
|
||||
avframe->nb_extended_buf = extbufs;
|
||||
}
|
||||
|
||||
for (int p = 0; p < frame->num_planes; p++)
|
||||
avframe->extended_data[p] = frame->planes[p];
|
||||
avframe->linesize[0] = frame->samples * frame->sstride;
|
||||
|
||||
for (int p = 0; p < AV_NUM_DATA_POINTERS; p++)
|
||||
avframe->data[p] = avframe->extended_data[p];
|
||||
|
||||
for (int p = 0; p < frame->num_planes; p++) {
|
||||
if (!frame->allocated[p])
|
||||
break;
|
||||
AVBufferRef *nref = av_buffer_ref(frame->allocated[p]);
|
||||
if (!nref)
|
||||
goto fail;
|
||||
if (p < AV_NUM_DATA_POINTERS) {
|
||||
avframe->buf[p] = nref;
|
||||
} else {
|
||||
avframe->extended_buf[p - AV_NUM_DATA_POINTERS] = nref;
|
||||
}
|
||||
}
|
||||
|
||||
// Force refcounted frame.
|
||||
if (!avframe->buf[0]) {
|
||||
AVFrame *tmp = av_frame_alloc();
|
||||
if (!tmp)
|
||||
goto fail;
|
||||
if (av_frame_ref(tmp, avframe) < 0)
|
||||
goto fail;
|
||||
av_frame_free(&avframe);
|
||||
avframe = tmp;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
av_frame_unref(avframe);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Returns NULL on failure. The input is always unreffed.
|
||||
struct AVFrame *mp_audio_to_avframe_and_unref(struct mp_audio *frame)
|
||||
{
|
||||
struct AVFrame *avframe = av_frame_alloc();
|
||||
if (!avframe)
|
||||
goto fail;
|
||||
|
||||
if (mp_audio_to_avframe(frame, avframe) < 0)
|
||||
goto fail;
|
||||
|
||||
talloc_free(frame);
|
||||
return avframe;
|
||||
|
||||
fail:
|
||||
av_frame_free(&avframe);
|
||||
talloc_free(frame);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct mp_audio_pool {
|
||||
AVBufferPool *avpool;
|
||||
int element_size;
|
||||
};
|
||||
|
||||
struct mp_audio_pool *mp_audio_pool_create(void *ta_parent)
|
||||
{
|
||||
return talloc_zero(ta_parent, struct mp_audio_pool);
|
||||
}
|
||||
|
||||
static void mp_audio_pool_destructor(void *p)
|
||||
{
|
||||
struct mp_audio_pool *pool = p;
|
||||
av_buffer_pool_uninit(&pool->avpool);
|
||||
}
|
||||
|
||||
// Allocate data using the given format and number of samples.
|
||||
// Returns NULL on error.
|
||||
struct mp_audio *mp_audio_pool_get(struct mp_audio_pool *pool,
|
||||
const struct mp_audio *fmt, int samples)
|
||||
{
|
||||
int size = get_plane_size(fmt, samples);
|
||||
if (size < 0)
|
||||
return NULL;
|
||||
if (!pool->avpool || size > pool->element_size) {
|
||||
size_t alloc = ta_calc_prealloc_elems(size);
|
||||
if (alloc >= INT_MAX)
|
||||
return NULL;
|
||||
av_buffer_pool_uninit(&pool->avpool);
|
||||
pool->element_size = alloc;
|
||||
pool->avpool = av_buffer_pool_init(pool->element_size, NULL);
|
||||
if (!pool->avpool)
|
||||
return NULL;
|
||||
talloc_set_destructor(pool, mp_audio_pool_destructor);
|
||||
}
|
||||
struct mp_audio *new = talloc_ptrtype(NULL, new);
|
||||
talloc_set_destructor(new, mp_audio_destructor);
|
||||
*new = *fmt;
|
||||
mp_audio_set_null_data(new);
|
||||
new->samples = samples;
|
||||
for (int n = 0; n < new->num_planes; n++) {
|
||||
new->allocated[n] = av_buffer_pool_get(pool->avpool);
|
||||
if (!new->allocated[n]) {
|
||||
talloc_free(new);
|
||||
return NULL;
|
||||
}
|
||||
new->planes[n] = new->allocated[n]->data;
|
||||
}
|
||||
return new;
|
||||
}
|
||||
|
||||
// Return a copy of the given frame.
|
||||
// Returns NULL on error.
|
||||
struct mp_audio *mp_audio_pool_new_copy(struct mp_audio_pool *pool,
|
||||
struct mp_audio *frame)
|
||||
{
|
||||
struct mp_audio *new = mp_audio_pool_get(pool, frame, frame->samples);
|
||||
if (new) {
|
||||
mp_audio_copy(new, 0, frame, 0, new->samples);
|
||||
mp_audio_copy_attributes(new, frame);
|
||||
}
|
||||
return new;
|
||||
}
|
||||
|
||||
// Exactly like mp_audio_make_writeable(), but get the data from the pool.
|
||||
int mp_audio_pool_make_writeable(struct mp_audio_pool *pool,
|
||||
struct mp_audio *data)
|
||||
{
|
||||
if (mp_audio_is_writeable(data))
|
||||
return 0;
|
||||
struct mp_audio *new = mp_audio_pool_get(pool, data, data->samples);
|
||||
if (!new)
|
||||
return -1;
|
||||
mp_audio_copy(new, 0, data, 0, data->samples);
|
||||
mp_audio_copy_attributes(new, data);
|
||||
mp_audio_steal_data(data, new);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MP_AUDIO_H
|
||||
#define MP_AUDIO_H
|
||||
|
||||
#include "format.h"
|
||||
#include "chmap.h"
|
||||
|
||||
// Audio data chunk
|
||||
struct mp_audio {
|
||||
int samples; // number of samples in data (per channel)
|
||||
void *planes[MP_NUM_CHANNELS]; // data buffer (one per plane)
|
||||
int rate; // sample rate
|
||||
struct mp_chmap channels; // channel layout, use mp_audio_set_*() to set
|
||||
int format; // format (AF_FORMAT_...), use mp_audio_set_format() to set
|
||||
// Redundant fields, for convenience
|
||||
int sstride; // distance between 2 samples in bytes on a plane
|
||||
// interleaved: bps * nch
|
||||
// planar: bps
|
||||
int nch; // number of channels (redundant with chmap)
|
||||
int spf; // sub-samples per sample on each plane
|
||||
int num_planes; // number of planes
|
||||
int bps; // size of sub-samples (af_fmt_to_bytes(format))
|
||||
|
||||
double pts; // currently invalid within the filter chain
|
||||
|
||||
// --- private
|
||||
// These do not necessarily map directly to planes[]. They can have
|
||||
// different order or count. There shouldn't be more buffers than planes.
|
||||
// If allocated[n] is NULL, allocated[n+1] must also be NULL.
|
||||
struct AVBufferRef *allocated[MP_NUM_CHANNELS];
|
||||
};
|
||||
|
||||
void mp_audio_set_format(struct mp_audio *mpa, int format);
|
||||
void mp_audio_set_num_channels(struct mp_audio *mpa, int num_channels);
|
||||
void mp_audio_set_channels(struct mp_audio *mpa, const struct mp_chmap *chmap);
|
||||
void mp_audio_copy_config(struct mp_audio *dst, const struct mp_audio *src);
|
||||
bool mp_audio_config_equals(const struct mp_audio *a, const struct mp_audio *b);
|
||||
bool mp_audio_config_valid(const struct mp_audio *mpa);
|
||||
|
||||
char *mp_audio_config_to_str_buf(char *buf, size_t buf_sz, struct mp_audio *mpa);
|
||||
#define mp_audio_config_to_str(m) mp_audio_config_to_str_buf((char[64]){0}, 64, (m))
|
||||
|
||||
void mp_audio_force_interleaved_format(struct mp_audio *mpa);
|
||||
|
||||
int mp_audio_psize(struct mp_audio *mpa);
|
||||
|
||||
void mp_audio_set_null_data(struct mp_audio *mpa);
|
||||
|
||||
void mp_audio_realloc(struct mp_audio *mpa, int samples);
|
||||
void mp_audio_realloc_min(struct mp_audio *mpa, int samples);
|
||||
int mp_audio_get_allocated_size(struct mp_audio *mpa);
|
||||
|
||||
void mp_audio_fill_silence(struct mp_audio *mpa, int start, int length);
|
||||
|
||||
void mp_audio_copy(struct mp_audio *dst, int dst_offset,
|
||||
struct mp_audio *src, int src_offset, int length);
|
||||
void mp_audio_copy_attributes(struct mp_audio *dst, struct mp_audio *src);
|
||||
void mp_audio_skip_samples(struct mp_audio *data, int samples);
|
||||
void mp_audio_clip_timestamps(struct mp_audio *f, double start, double end);
|
||||
double mp_audio_end_pts(struct mp_audio *data);
|
||||
|
||||
bool mp_audio_is_writeable(struct mp_audio *data);
|
||||
int mp_audio_make_writeable(struct mp_audio *data);
|
||||
|
||||
struct AVFrame;
|
||||
struct mp_audio *mp_audio_from_avframe(struct AVFrame *avframe);
|
||||
struct AVFrame *mp_audio_to_avframe_and_unref(struct mp_audio *frame);
|
||||
int mp_audio_to_avframe(struct mp_audio *frame, struct AVFrame *avframe);
|
||||
|
||||
struct mp_aframe;
|
||||
struct mp_audio *mp_audio_from_aframe(struct mp_aframe *aframe);
|
||||
void mp_audio_config_from_aframe(struct mp_audio *dst, struct mp_aframe *src);
|
||||
|
||||
struct mp_audio_pool;
|
||||
struct mp_audio_pool *mp_audio_pool_create(void *ta_parent);
|
||||
struct mp_audio *mp_audio_pool_get(struct mp_audio_pool *pool,
|
||||
const struct mp_audio *fmt, int samples);
|
||||
struct mp_audio *mp_audio_pool_new_copy(struct mp_audio_pool *pool,
|
||||
struct mp_audio *frame);
|
||||
int mp_audio_pool_make_writeable(struct mp_audio_pool *pool,
|
||||
struct mp_audio *frame);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <limits.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "common/common.h"
|
||||
|
||||
#include "audio_buffer.h"
|
||||
#include "audio.h"
|
||||
#include "format.h"
|
||||
|
||||
struct mp_audio_buffer {
|
||||
struct mp_audio *buffer;
|
||||
};
|
||||
|
||||
struct mp_audio_buffer *mp_audio_buffer_create(void *talloc_ctx)
|
||||
{
|
||||
struct mp_audio_buffer *ab = talloc(talloc_ctx, struct mp_audio_buffer);
|
||||
*ab = (struct mp_audio_buffer) {
|
||||
.buffer = talloc_zero(ab, struct mp_audio),
|
||||
};
|
||||
return ab;
|
||||
}
|
||||
|
||||
// Reinitialize the buffer, set a new format, drop old data.
|
||||
// The audio data in fmt is not used, only the format.
|
||||
void mp_audio_buffer_reinit(struct mp_audio_buffer *ab, struct mp_audio *fmt)
|
||||
{
|
||||
mp_audio_copy_config(ab->buffer, fmt);
|
||||
mp_audio_realloc(ab->buffer, 1);
|
||||
ab->buffer->samples = 0;
|
||||
}
|
||||
|
||||
void mp_audio_buffer_reinit_fmt(struct mp_audio_buffer *ab, int format,
|
||||
const struct mp_chmap *channels, int srate)
|
||||
{
|
||||
struct mp_audio mpa = {0};
|
||||
mp_audio_set_format(&mpa, format);
|
||||
mp_audio_set_channels(&mpa, channels);
|
||||
mpa.rate = srate;
|
||||
mp_audio_buffer_reinit(ab, &mpa);
|
||||
}
|
||||
|
||||
void mp_audio_buffer_get_format(struct mp_audio_buffer *ab,
|
||||
struct mp_audio *out_fmt)
|
||||
{
|
||||
*out_fmt = (struct mp_audio){0};
|
||||
mp_audio_copy_config(out_fmt, ab->buffer);
|
||||
}
|
||||
|
||||
// Make the total size of the internal buffer at least this number of samples.
|
||||
void mp_audio_buffer_preallocate_min(struct mp_audio_buffer *ab, int samples)
|
||||
{
|
||||
mp_audio_realloc_min(ab->buffer, samples);
|
||||
}
|
||||
|
||||
// Get number of samples that can be written without forcing a resize of the
|
||||
// internal buffer.
|
||||
int mp_audio_buffer_get_write_available(struct mp_audio_buffer *ab)
|
||||
{
|
||||
return mp_audio_get_allocated_size(ab->buffer) - ab->buffer->samples;
|
||||
}
|
||||
|
||||
// Get a pointer to the end of the buffer (where writing would append). If the
|
||||
// internal buffer is too small for the given number of samples, it's resized.
|
||||
// After writing to the buffer, mp_audio_buffer_finish_write() has to be used
|
||||
// to make the written data part of the readable buffer.
|
||||
void mp_audio_buffer_get_write_buffer(struct mp_audio_buffer *ab, int samples,
|
||||
struct mp_audio *out_buffer)
|
||||
{
|
||||
assert(samples >= 0);
|
||||
mp_audio_realloc_min(ab->buffer, ab->buffer->samples + samples);
|
||||
*out_buffer = *ab->buffer;
|
||||
out_buffer->samples = ab->buffer->samples + samples;
|
||||
mp_audio_skip_samples(out_buffer, ab->buffer->samples);
|
||||
}
|
||||
|
||||
void mp_audio_buffer_finish_write(struct mp_audio_buffer *ab, int samples)
|
||||
{
|
||||
assert(samples >= 0 && samples <= mp_audio_buffer_get_write_available(ab));
|
||||
ab->buffer->samples += samples;
|
||||
}
|
||||
|
||||
// Append data to the end of the buffer.
|
||||
// If the buffer is not large enough, it is transparently resized.
|
||||
// For now always copies the data.
|
||||
void mp_audio_buffer_append(struct mp_audio_buffer *ab, struct mp_audio *mpa)
|
||||
{
|
||||
int offset = ab->buffer->samples;
|
||||
ab->buffer->samples += mpa->samples;
|
||||
mp_audio_realloc_min(ab->buffer, ab->buffer->samples);
|
||||
mp_audio_copy(ab->buffer, offset, mpa, 0, mpa->samples);
|
||||
}
|
||||
|
||||
// Prepend silence to the start of the buffer.
|
||||
void mp_audio_buffer_prepend_silence(struct mp_audio_buffer *ab, int samples)
|
||||
{
|
||||
assert(samples >= 0);
|
||||
int oldlen = ab->buffer->samples;
|
||||
ab->buffer->samples += samples;
|
||||
mp_audio_realloc_min(ab->buffer, ab->buffer->samples);
|
||||
mp_audio_copy(ab->buffer, samples, ab->buffer, 0, oldlen);
|
||||
mp_audio_fill_silence(ab->buffer, 0, samples);
|
||||
}
|
||||
|
||||
void mp_audio_buffer_duplicate(struct mp_audio_buffer *ab, int samples)
|
||||
{
|
||||
assert(samples >= 0 && samples <= ab->buffer->samples);
|
||||
int oldlen = ab->buffer->samples;
|
||||
ab->buffer->samples += samples;
|
||||
mp_audio_realloc_min(ab->buffer, ab->buffer->samples);
|
||||
mp_audio_copy(ab->buffer, oldlen, ab->buffer, oldlen - samples, samples);
|
||||
}
|
||||
|
||||
// Get the start of the current readable buffer.
|
||||
void mp_audio_buffer_peek(struct mp_audio_buffer *ab, struct mp_audio *out_mpa)
|
||||
{
|
||||
*out_mpa = *ab->buffer;
|
||||
}
|
||||
|
||||
// Skip leading samples. (Used with mp_audio_buffer_peek() to read data.)
|
||||
void mp_audio_buffer_skip(struct mp_audio_buffer *ab, int samples)
|
||||
{
|
||||
assert(samples >= 0 && samples <= ab->buffer->samples);
|
||||
mp_audio_copy(ab->buffer, 0, ab->buffer, samples,
|
||||
ab->buffer->samples - samples);
|
||||
ab->buffer->samples -= samples;
|
||||
}
|
||||
|
||||
void mp_audio_buffer_clear(struct mp_audio_buffer *ab)
|
||||
{
|
||||
ab->buffer->samples = 0;
|
||||
}
|
||||
|
||||
// Return number of buffered audio samples
|
||||
int mp_audio_buffer_samples(struct mp_audio_buffer *ab)
|
||||
{
|
||||
return ab->buffer->samples;
|
||||
}
|
||||
|
||||
// Return amount of buffered audio in seconds.
|
||||
double mp_audio_buffer_seconds(struct mp_audio_buffer *ab)
|
||||
{
|
||||
return ab->buffer->samples / (double)ab->buffer->rate;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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 MP_AUDIO_BUFFER_H
|
||||
#define MP_AUDIO_BUFFER_H
|
||||
|
||||
struct mp_audio_buffer;
|
||||
struct mp_audio;
|
||||
struct mp_chmap;
|
||||
|
||||
struct mp_audio_buffer *mp_audio_buffer_create(void *talloc_ctx);
|
||||
void mp_audio_buffer_reinit(struct mp_audio_buffer *ab, struct mp_audio *fmt);
|
||||
void mp_audio_buffer_reinit_fmt(struct mp_audio_buffer *ab, int format,
|
||||
const struct mp_chmap *channels, int srate);
|
||||
void mp_audio_buffer_get_format(struct mp_audio_buffer *ab,
|
||||
struct mp_audio *out_fmt);
|
||||
void mp_audio_buffer_preallocate_min(struct mp_audio_buffer *ab, int samples);
|
||||
int mp_audio_buffer_get_write_available(struct mp_audio_buffer *ab);
|
||||
void mp_audio_buffer_get_write_buffer(struct mp_audio_buffer *ab, int minsamples,
|
||||
struct mp_audio *out_buffer);
|
||||
void mp_audio_buffer_finish_write(struct mp_audio_buffer *ab, int samples);
|
||||
void mp_audio_buffer_append(struct mp_audio_buffer *ab, struct mp_audio *mpa);
|
||||
void mp_audio_buffer_prepend_silence(struct mp_audio_buffer *ab, int samples);
|
||||
void mp_audio_buffer_duplicate(struct mp_audio_buffer *ab, int samples);
|
||||
void mp_audio_buffer_peek(struct mp_audio_buffer *ab, struct mp_audio *out_mpa);
|
||||
void mp_audio_buffer_skip(struct mp_audio_buffer *ab, int samples);
|
||||
void mp_audio_buffer_clear(struct mp_audio_buffer *ab);
|
||||
int mp_audio_buffer_samples(struct mp_audio_buffer *ab);
|
||||
double mp_audio_buffer_seconds(struct mp_audio_buffer *ab);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MPLAYER_AD_H
|
||||
#define MPLAYER_AD_H
|
||||
|
||||
#include "common/codecs.h"
|
||||
#include "demux/stheader.h"
|
||||
#include "demux/demux.h"
|
||||
|
||||
#include "audio/format.h"
|
||||
#include "audio/aframe.h"
|
||||
#include "dec_audio.h"
|
||||
|
||||
struct mp_decoder_list;
|
||||
|
||||
/* interface of audio decoder drivers */
|
||||
struct ad_functions {
|
||||
const char *name;
|
||||
void (*add_decoders)(struct mp_decoder_list *list);
|
||||
int (*init)(struct dec_audio *da, const char *decoder);
|
||||
void (*uninit)(struct dec_audio *da);
|
||||
int (*control)(struct dec_audio *da, int cmd, void *arg);
|
||||
// Return whether or not the packet has been consumed.
|
||||
bool (*send_packet)(struct dec_audio *da, struct demux_packet *pkt);
|
||||
// Return whether decoding is still going on (false if EOF was reached).
|
||||
// Never returns false & *out set, but can return true with !*out.
|
||||
bool (*receive_frame)(struct dec_audio *da, struct mp_aframe **out);
|
||||
};
|
||||
|
||||
enum ad_ctrl {
|
||||
ADCTRL_RESET = 1, // flush and reset state, e.g. after seeking
|
||||
};
|
||||
|
||||
#endif /* MPLAYER_AD_H */
|
||||
+76
-99
@@ -27,32 +27,28 @@
|
||||
#include <libavutil/intreadwrite.h>
|
||||
|
||||
#include "mpv_talloc.h"
|
||||
#include "audio/aframe.h"
|
||||
#include "audio/fmt-conversion.h"
|
||||
|
||||
#include "config.h"
|
||||
#include "common/av_common.h"
|
||||
#include "common/codecs.h"
|
||||
#include "common/global.h"
|
||||
#include "common/msg.h"
|
||||
#include "demux/packet.h"
|
||||
#include "demux/stheader.h"
|
||||
#include "filters/f_decoder_wrapper.h"
|
||||
#include "filters/filter_internal.h"
|
||||
#include "options/m_config.h"
|
||||
#include "options/options.h"
|
||||
|
||||
#include "ad.h"
|
||||
#include "audio/fmt-conversion.h"
|
||||
|
||||
struct priv {
|
||||
AVCodecContext *avctx;
|
||||
AVFrame *avframe;
|
||||
struct mp_chmap force_channel_map;
|
||||
bool force_channel_map;
|
||||
uint32_t skip_samples, trim_samples;
|
||||
bool preroll_done;
|
||||
double next_pts;
|
||||
AVRational codec_timebase;
|
||||
struct lavc_state state;
|
||||
|
||||
struct mp_decoder public;
|
||||
};
|
||||
|
||||
static void uninit(struct dec_audio *da);
|
||||
|
||||
#define OPT_BASE_STRUCT struct ad_lavc_params
|
||||
struct ad_lavc_params {
|
||||
float ac3drc;
|
||||
@@ -63,39 +59,40 @@ struct ad_lavc_params {
|
||||
|
||||
const struct m_sub_options ad_lavc_conf = {
|
||||
.opts = (const m_option_t[]) {
|
||||
{"ac3drc", OPT_FLOAT(ac3drc), M_RANGE(0, 6)},
|
||||
{"downmix", OPT_FLAG(downmix)},
|
||||
{"threads", OPT_INT(threads), M_RANGE(0, 16)},
|
||||
{"o", OPT_KEYVALUELIST(avopts)},
|
||||
OPT_FLOATRANGE("ac3drc", ac3drc, 0, 0, 6),
|
||||
OPT_FLAG("downmix", downmix, 0),
|
||||
OPT_INTRANGE("threads", threads, 0, 0, 16),
|
||||
OPT_KEYVALUELIST("o", avopts, 0),
|
||||
{0}
|
||||
},
|
||||
.size = sizeof(struct ad_lavc_params),
|
||||
.defaults = &(const struct ad_lavc_params){
|
||||
.ac3drc = 0,
|
||||
.downmix = 0,
|
||||
.downmix = 1,
|
||||
.threads = 1,
|
||||
},
|
||||
};
|
||||
|
||||
static bool init(struct mp_filter *da, struct mp_codec_params *codec,
|
||||
const char *decoder)
|
||||
static int init(struct dec_audio *da, const char *decoder)
|
||||
{
|
||||
struct priv *ctx = da->priv;
|
||||
struct MPOpts *mpopts = mp_get_config_group(ctx, da->global, &mp_opt_root);
|
||||
struct ad_lavc_params *opts =
|
||||
mp_get_config_group(ctx, da->global, &ad_lavc_conf);
|
||||
struct MPOpts *mpopts = da->opts;
|
||||
struct ad_lavc_params *opts = mpopts->ad_lavc_params;
|
||||
AVCodecContext *lavc_context;
|
||||
AVCodec *lavc_codec;
|
||||
struct mp_codec_params *c = da->codec;
|
||||
|
||||
ctx->codec_timebase = mp_get_codec_timebase(codec);
|
||||
struct priv *ctx = talloc_zero(NULL, struct priv);
|
||||
da->priv = ctx;
|
||||
|
||||
if (codec->force_channels)
|
||||
ctx->force_channel_map = codec->channels;
|
||||
ctx->codec_timebase = mp_get_codec_timebase(da->codec);
|
||||
|
||||
ctx->force_channel_map = c->force_channels;
|
||||
|
||||
lavc_codec = avcodec_find_decoder_by_name(decoder);
|
||||
if (!lavc_codec) {
|
||||
MP_ERR(da, "Cannot find codec '%s' in libavcodec...\n", decoder);
|
||||
return false;
|
||||
uninit(da);
|
||||
return 0;
|
||||
}
|
||||
|
||||
lavc_context = avcodec_alloc_context3(lavc_codec);
|
||||
@@ -103,7 +100,10 @@ static bool init(struct mp_filter *da, struct mp_codec_params *codec,
|
||||
ctx->avframe = av_frame_alloc();
|
||||
lavc_context->codec_type = AVMEDIA_TYPE_AUDIO;
|
||||
lavc_context->codec_id = lavc_codec->id;
|
||||
|
||||
#if LIBAVCODEC_VERSION_MICRO >= 100
|
||||
lavc_context->pkt_timebase = ctx->codec_timebase;
|
||||
#endif
|
||||
|
||||
if (opts->downmix && mpopts->audio_output_channels.num_chmaps == 1) {
|
||||
lavc_context->request_channel_layout =
|
||||
@@ -114,14 +114,17 @@ static bool init(struct mp_filter *da, struct mp_codec_params *codec,
|
||||
av_opt_set_double(lavc_context, "drc_scale", opts->ac3drc,
|
||||
AV_OPT_SEARCH_CHILDREN);
|
||||
|
||||
#if LIBAVCODEC_VERSION_MICRO >= 100
|
||||
// Let decoder add AV_FRAME_DATA_SKIP_SAMPLES.
|
||||
av_opt_set(lavc_context, "flags2", "+skip_manual", AV_OPT_SEARCH_CHILDREN);
|
||||
#endif
|
||||
|
||||
mp_set_avopts(da->log, lavc_context, opts->avopts);
|
||||
|
||||
if (mp_set_avctx_codec_headers(lavc_context, codec) < 0) {
|
||||
if (mp_set_avctx_codec_headers(lavc_context, c) < 0) {
|
||||
MP_ERR(da, "Could not set decoder parameters.\n");
|
||||
return false;
|
||||
uninit(da);
|
||||
return 0;
|
||||
}
|
||||
|
||||
mp_set_avcodec_threads(da->log, lavc_context, opts->threads);
|
||||
@@ -129,35 +132,41 @@ static bool init(struct mp_filter *da, struct mp_codec_params *codec,
|
||||
/* open it */
|
||||
if (avcodec_open2(lavc_context, lavc_codec, NULL) < 0) {
|
||||
MP_ERR(da, "Could not open codec.\n");
|
||||
return false;
|
||||
uninit(da);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ctx->next_pts = MP_NOPTS_VALUE;
|
||||
|
||||
return true;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void destroy(struct mp_filter *da)
|
||||
static void uninit(struct dec_audio *da)
|
||||
{
|
||||
struct priv *ctx = da->priv;
|
||||
if (!ctx)
|
||||
return;
|
||||
|
||||
avcodec_free_context(&ctx->avctx);
|
||||
av_frame_free(&ctx->avframe);
|
||||
}
|
||||
|
||||
static void reset(struct mp_filter *da)
|
||||
static int control(struct dec_audio *da, int cmd, void *arg)
|
||||
{
|
||||
struct priv *ctx = da->priv;
|
||||
|
||||
avcodec_flush_buffers(ctx->avctx);
|
||||
ctx->skip_samples = 0;
|
||||
ctx->trim_samples = 0;
|
||||
ctx->preroll_done = false;
|
||||
ctx->next_pts = MP_NOPTS_VALUE;
|
||||
ctx->state = (struct lavc_state){0};
|
||||
switch (cmd) {
|
||||
case ADCTRL_RESET:
|
||||
avcodec_flush_buffers(ctx->avctx);
|
||||
ctx->skip_samples = 0;
|
||||
ctx->trim_samples = 0;
|
||||
ctx->preroll_done = false;
|
||||
ctx->next_pts = MP_NOPTS_VALUE;
|
||||
return CONTROL_TRUE;
|
||||
}
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
|
||||
static int send_packet(struct mp_filter *da, struct demux_packet *mpkt)
|
||||
static bool send_packet(struct dec_audio *da, struct demux_packet *mpkt)
|
||||
{
|
||||
struct priv *priv = da->priv;
|
||||
AVCodecContext *avctx = priv->avctx;
|
||||
@@ -172,12 +181,16 @@ static int send_packet(struct mp_filter *da, struct demux_packet *mpkt)
|
||||
mp_set_av_packet(&pkt, mpkt, &priv->codec_timebase);
|
||||
|
||||
int ret = avcodec_send_packet(avctx, mpkt ? &pkt : NULL);
|
||||
|
||||
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
|
||||
return false;
|
||||
|
||||
if (ret < 0)
|
||||
MP_ERR(da, "Error decoding audio.\n");
|
||||
return ret;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int receive_frame(struct mp_filter *da, struct mp_frame *out)
|
||||
static bool receive_frame(struct dec_audio *da, struct mp_aframe **out)
|
||||
{
|
||||
struct priv *priv = da->priv;
|
||||
AVCodecContext *avctx = priv->avctx;
|
||||
@@ -187,29 +200,28 @@ static int receive_frame(struct mp_filter *da, struct mp_frame *out)
|
||||
if (ret == AVERROR_EOF) {
|
||||
// If flushing was initialized earlier and has ended now, make it start
|
||||
// over in case we get new packets at some point in the future.
|
||||
// (Dont' reset the filter itself, we want to keep other state.)
|
||||
avcodec_flush_buffers(priv->avctx);
|
||||
return ret;
|
||||
control(da, ADCTRL_RESET, NULL);
|
||||
return false;
|
||||
} else if (ret < 0 && ret != AVERROR(EAGAIN)) {
|
||||
MP_ERR(da, "Error decoding audio.\n");
|
||||
}
|
||||
|
||||
#if LIBAVCODEC_VERSION_MICRO >= 100
|
||||
if (priv->avframe->flags & AV_FRAME_FLAG_DISCARD)
|
||||
av_frame_unref(priv->avframe);
|
||||
#endif
|
||||
|
||||
if (!priv->avframe->buf[0])
|
||||
return ret;
|
||||
return true;
|
||||
|
||||
double out_pts = mp_pts_from_av(priv->avframe->pts, &priv->codec_timebase);
|
||||
|
||||
struct mp_aframe *mpframe = mp_aframe_from_avframe(priv->avframe);
|
||||
if (!mpframe) {
|
||||
MP_ERR(da, "Converting libavcodec frame to mpv frame failed.\n");
|
||||
return ret;
|
||||
}
|
||||
if (!mpframe)
|
||||
return true;
|
||||
|
||||
if (priv->force_channel_map.num)
|
||||
mp_aframe_set_chmap(mpframe, &priv->force_channel_map);
|
||||
if (priv->force_channel_map)
|
||||
mp_aframe_set_chmap(mpframe, &da->codec->channels);
|
||||
|
||||
if (out_pts == MP_NOPTS_VALUE)
|
||||
out_pts = priv->next_pts;
|
||||
@@ -217,6 +229,7 @@ static int receive_frame(struct mp_filter *da, struct mp_frame *out)
|
||||
|
||||
priv->next_pts = mp_aframe_end_pts(mpframe);
|
||||
|
||||
#if LIBAVCODEC_VERSION_MICRO >= 100
|
||||
AVFrameSideData *sd =
|
||||
av_frame_get_side_data(priv->avframe, AV_FRAME_DATA_SKIP_SAMPLES);
|
||||
if (sd && sd->size >= 10) {
|
||||
@@ -224,6 +237,7 @@ static int receive_frame(struct mp_filter *da, struct mp_frame *out)
|
||||
priv->skip_samples += AV_RL32(d + 0);
|
||||
priv->trim_samples += AV_RL32(d + 4);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!priv->preroll_done) {
|
||||
// Skip only if this isn't already handled by AV_FRAME_DATA_SKIP_SAMPLES.
|
||||
@@ -243,53 +257,11 @@ static int receive_frame(struct mp_filter *da, struct mp_frame *out)
|
||||
priv->trim_samples -= trim;
|
||||
}
|
||||
|
||||
if (mp_aframe_get_size(mpframe) > 0) {
|
||||
*out = MAKE_FRAME(MP_FRAME_AUDIO, mpframe);
|
||||
} else {
|
||||
talloc_free(mpframe);
|
||||
}
|
||||
*out = mpframe;
|
||||
|
||||
av_frame_unref(priv->avframe);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void process(struct mp_filter *ad)
|
||||
{
|
||||
struct priv *priv = ad->priv;
|
||||
|
||||
lavc_process(ad, &priv->state, send_packet, receive_frame);
|
||||
}
|
||||
|
||||
static const struct mp_filter_info ad_lavc_filter = {
|
||||
.name = "ad_lavc",
|
||||
.priv_size = sizeof(struct priv),
|
||||
.process = process,
|
||||
.reset = reset,
|
||||
.destroy = destroy,
|
||||
};
|
||||
|
||||
static struct mp_decoder *create(struct mp_filter *parent,
|
||||
struct mp_codec_params *codec,
|
||||
const char *decoder)
|
||||
{
|
||||
struct mp_filter *da = mp_filter_create(parent, &ad_lavc_filter);
|
||||
if (!da)
|
||||
return NULL;
|
||||
|
||||
mp_filter_add_pin(da, MP_PIN_IN, "in");
|
||||
mp_filter_add_pin(da, MP_PIN_OUT, "out");
|
||||
|
||||
da->log = mp_log_new(da, parent->log, NULL);
|
||||
|
||||
struct priv *priv = da->priv;
|
||||
priv->public.f = da;
|
||||
|
||||
if (!init(da, codec, decoder)) {
|
||||
talloc_free(da);
|
||||
return NULL;
|
||||
}
|
||||
return &priv->public;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void add_decoders(struct mp_decoder_list *list)
|
||||
@@ -297,7 +269,12 @@ static void add_decoders(struct mp_decoder_list *list)
|
||||
mp_add_lavc_decoders(list, AVMEDIA_TYPE_AUDIO);
|
||||
}
|
||||
|
||||
const struct mp_decoder_fns ad_lavc = {
|
||||
.create = create,
|
||||
const struct ad_functions ad_lavc = {
|
||||
.name = "lavc",
|
||||
.add_decoders = add_decoders,
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.control = control,
|
||||
.send_packet = send_packet,
|
||||
.receive_frame = receive_frame,
|
||||
};
|
||||
+82
-97
@@ -24,16 +24,11 @@
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavutil/opt.h>
|
||||
|
||||
#include "audio/aframe.h"
|
||||
#include "audio/format.h"
|
||||
#include "common/av_common.h"
|
||||
#include "common/codecs.h"
|
||||
#include "config.h"
|
||||
#include "common/msg.h"
|
||||
#include "demux/packet.h"
|
||||
#include "demux/stheader.h"
|
||||
#include "filters/f_decoder_wrapper.h"
|
||||
#include "filters/filter_internal.h"
|
||||
#include "common/av_common.h"
|
||||
#include "options/options.h"
|
||||
#include "ad.h"
|
||||
|
||||
#define OUTBUF_SIZE 65536
|
||||
|
||||
@@ -48,8 +43,8 @@ struct spdifContext {
|
||||
struct mp_aframe *fmt;
|
||||
int sstride;
|
||||
struct mp_aframe_pool *pool;
|
||||
|
||||
struct mp_decoder public;
|
||||
bool got_eof;
|
||||
struct demux_packet *queued_packet;
|
||||
};
|
||||
|
||||
static int write_packet(void *p, uint8_t *buf, int buf_size)
|
||||
@@ -67,8 +62,7 @@ static int write_packet(void *p, uint8_t *buf, int buf_size)
|
||||
return buf_size;
|
||||
}
|
||||
|
||||
// (called on both filter destruction _and_ if lavf fails to init)
|
||||
static void destroy(struct mp_filter *da)
|
||||
static void uninit(struct dec_audio *da)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
AVFormatContext *lavf_ctx = spdif_ctx->lavf_ctx;
|
||||
@@ -80,11 +74,27 @@ static void destroy(struct mp_filter *da)
|
||||
av_freep(&lavf_ctx->pb->buffer);
|
||||
av_freep(&lavf_ctx->pb);
|
||||
avformat_free_context(lavf_ctx);
|
||||
talloc_free(spdif_ctx->queued_packet);
|
||||
spdif_ctx->lavf_ctx = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void determine_codec_params(struct mp_filter *da, AVPacket *pkt,
|
||||
static int init(struct dec_audio *da, const char *decoder)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = talloc_zero(NULL, struct spdifContext);
|
||||
da->priv = spdif_ctx;
|
||||
spdif_ctx->log = da->log;
|
||||
spdif_ctx->use_dts_hd = da->opts->dtshd;
|
||||
spdif_ctx->pool = mp_aframe_pool_create(spdif_ctx);
|
||||
|
||||
if (strcmp(decoder, "spdif_dts_hd") == 0)
|
||||
spdif_ctx->use_dts_hd = true;
|
||||
|
||||
spdif_ctx->codec_id = mp_codec_to_av_codec_id(da->codec->codec);
|
||||
return spdif_ctx->codec_id != AV_CODEC_ID_NONE;
|
||||
}
|
||||
|
||||
static void determine_codec_params(struct dec_audio *da, AVPacket *pkt,
|
||||
int *out_profile, int *out_rate)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
@@ -147,7 +157,7 @@ done:
|
||||
MP_WARN(da, "Failed to parse codec profile.\n");
|
||||
}
|
||||
|
||||
static int init_filter(struct mp_filter *da, AVPacket *pkt)
|
||||
static int init_filter(struct dec_audio *da, AVPacket *pkt)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
|
||||
@@ -176,8 +186,10 @@ static int init_filter(struct mp_filter *da, AVPacket *pkt)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
// Request minimal buffering
|
||||
// Request minimal buffering (not available on Libav)
|
||||
#if LIBAVFORMAT_VERSION_MICRO >= 100
|
||||
lavf_ctx->pb->direct = 1;
|
||||
#endif
|
||||
|
||||
AVStream *stream = avformat_new_stream(lavf_ctx, 0);
|
||||
if (!stream)
|
||||
@@ -206,21 +218,13 @@ static int init_filter(struct mp_filter *da, AVPacket *pkt)
|
||||
break;
|
||||
case AV_CODEC_ID_DTS: {
|
||||
bool is_hd = profile == FF_PROFILE_DTS_HD_HRA ||
|
||||
profile == FF_PROFILE_DTS_HD_MA ||
|
||||
profile == FF_PROFILE_DTS_HD_MA ||
|
||||
profile == FF_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 ?
|
||||
2 : 8;
|
||||
if (spdif_ctx->use_dts_hd && is_hd) {
|
||||
av_dict_set_int(&format_opts, "dtshd_rate",
|
||||
dts_hd_spdif_channel_count * 96000, 0);
|
||||
av_dict_set(&format_opts, "dtshd_rate", "768000", 0); // 4*192000
|
||||
sample_format = AF_FORMAT_S_DTSHD;
|
||||
samplerate = 192000;
|
||||
num_channels = dts_hd_spdif_channel_count;
|
||||
num_channels = 2*4;
|
||||
} else {
|
||||
sample_format = AF_FORMAT_S_DTS;
|
||||
samplerate = 48000;
|
||||
@@ -267,36 +271,39 @@ static int init_filter(struct mp_filter *da, AVPacket *pkt)
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
destroy(da);
|
||||
mp_filter_internal_mark_failed(da);
|
||||
uninit(da);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void process(struct mp_filter *da)
|
||||
|
||||
static bool send_packet(struct dec_audio *da, struct demux_packet *mpkt)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
|
||||
if (!mp_pin_can_transfer_data(da->ppins[1], da->ppins[0]))
|
||||
return;
|
||||
if (spdif_ctx->queued_packet || spdif_ctx->got_eof)
|
||||
return false;
|
||||
|
||||
struct mp_frame inframe = mp_pin_out_read(da->ppins[0]);
|
||||
if (inframe.type == MP_FRAME_EOF) {
|
||||
mp_pin_in_write(da->ppins[1], inframe);
|
||||
return;
|
||||
} else if (inframe.type != MP_FRAME_PACKET) {
|
||||
if (inframe.type) {
|
||||
MP_ERR(da, "unknown frame type\n");
|
||||
mp_filter_internal_mark_failed(da);
|
||||
}
|
||||
return;
|
||||
spdif_ctx->queued_packet = mpkt ? demux_copy_packet(mpkt) : NULL;
|
||||
spdif_ctx->got_eof = !mpkt;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool receive_frame(struct dec_audio *da, struct mp_aframe **out)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
|
||||
if (spdif_ctx->got_eof) {
|
||||
spdif_ctx->got_eof = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
struct demux_packet *mpkt = inframe.data;
|
||||
struct mp_aframe *out = NULL;
|
||||
double pts = mpkt->pts;
|
||||
if (!spdif_ctx->queued_packet)
|
||||
return true;
|
||||
|
||||
double pts = spdif_ctx->queued_packet->pts;
|
||||
|
||||
AVPacket pkt;
|
||||
mp_set_av_packet(&pkt, mpkt, NULL);
|
||||
mp_set_av_packet(&pkt, spdif_ctx->queued_packet, NULL);
|
||||
pkt.pts = pkt.dts = 0;
|
||||
if (!spdif_ctx->lavf_ctx) {
|
||||
if (init_filter(da, &pkt) < 0)
|
||||
@@ -310,29 +317,39 @@ static void process(struct mp_filter *da)
|
||||
goto done;
|
||||
}
|
||||
|
||||
out = mp_aframe_new_ref(spdif_ctx->fmt);
|
||||
*out = mp_aframe_new_ref(spdif_ctx->fmt);
|
||||
int samples = spdif_ctx->out_buffer_len / spdif_ctx->sstride;
|
||||
if (mp_aframe_pool_allocate(spdif_ctx->pool, out, samples) < 0) {
|
||||
TA_FREEP(&out);
|
||||
if (mp_aframe_pool_allocate(spdif_ctx->pool, *out, samples) < 0) {
|
||||
TA_FREEP(out);
|
||||
goto done;
|
||||
}
|
||||
|
||||
uint8_t **data = mp_aframe_get_data_rw(out);
|
||||
uint8_t **data = mp_aframe_get_data_rw(*out);
|
||||
if (!data) {
|
||||
TA_FREEP(&out);
|
||||
TA_FREEP(out);
|
||||
goto done;
|
||||
}
|
||||
|
||||
memcpy(data[0], spdif_ctx->out_buffer, spdif_ctx->out_buffer_len);
|
||||
mp_aframe_set_pts(out, pts);
|
||||
mp_aframe_set_pts(*out, pts);
|
||||
|
||||
done:
|
||||
talloc_free(mpkt);
|
||||
if (out) {
|
||||
mp_pin_in_write(da->ppins[1], MAKE_FRAME(MP_FRAME_AUDIO, out));
|
||||
} else {
|
||||
mp_filter_internal_mark_failed(da);
|
||||
talloc_free(spdif_ctx->queued_packet);
|
||||
spdif_ctx->queued_packet = NULL;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int control(struct dec_audio *da, int cmd, void *arg)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
switch (cmd) {
|
||||
case ADCTRL_RESET:
|
||||
talloc_free(spdif_ctx->queued_packet);
|
||||
spdif_ctx->queued_packet = NULL;
|
||||
spdif_ctx->got_eof = false;
|
||||
return CONTROL_TRUE;
|
||||
}
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
|
||||
static const int codecs[] = {
|
||||
@@ -384,49 +401,17 @@ struct mp_decoder_list *select_spdif_codec(const char *codec, const char *pref)
|
||||
const char *suffix_name = dts_hd_allowed ? "dts_hd" : codec;
|
||||
char name[80];
|
||||
snprintf(name, sizeof(name), "spdif_%s", suffix_name);
|
||||
mp_add_decoder(list, codec, name,
|
||||
mp_add_decoder(list, "spdif", codec, name,
|
||||
"libavformat/spdifenc audio pass-through decoder");
|
||||
return list;
|
||||
}
|
||||
|
||||
static const struct mp_filter_info ad_spdif_filter = {
|
||||
.name = "ad_spdif",
|
||||
.priv_size = sizeof(struct spdifContext),
|
||||
.process = process,
|
||||
.destroy = destroy,
|
||||
};
|
||||
|
||||
static struct mp_decoder *create(struct mp_filter *parent,
|
||||
struct mp_codec_params *codec,
|
||||
const char *decoder)
|
||||
{
|
||||
struct mp_filter *da = mp_filter_create(parent, &ad_spdif_filter);
|
||||
if (!da)
|
||||
return NULL;
|
||||
|
||||
mp_filter_add_pin(da, MP_PIN_IN, "in");
|
||||
mp_filter_add_pin(da, MP_PIN_OUT, "out");
|
||||
|
||||
da->log = mp_log_new(da, parent->log, NULL);
|
||||
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
spdif_ctx->log = da->log;
|
||||
spdif_ctx->pool = mp_aframe_pool_create(spdif_ctx);
|
||||
spdif_ctx->public.f = da;
|
||||
|
||||
if (strcmp(decoder, "spdif_dts_hd") == 0)
|
||||
spdif_ctx->use_dts_hd = true;
|
||||
|
||||
spdif_ctx->codec_id = mp_codec_to_av_codec_id(codec->codec);
|
||||
|
||||
|
||||
if (spdif_ctx->codec_id == AV_CODEC_ID_NONE) {
|
||||
talloc_free(da);
|
||||
return NULL;
|
||||
}
|
||||
return &spdif_ctx->public;
|
||||
}
|
||||
|
||||
const struct mp_decoder_fns ad_spdif = {
|
||||
.create = create,
|
||||
const struct ad_functions ad_spdif = {
|
||||
.name = "spdif",
|
||||
.add_decoders = NULL,
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.control = control,
|
||||
.send_packet = send_packet,
|
||||
.receive_frame = receive_frame,
|
||||
};
|
||||
@@ -0,0 +1,287 @@
|
||||
/*
|
||||
* 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 <unistd.h>
|
||||
#include <math.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <libavutil/mem.h>
|
||||
|
||||
#include "demux/codec_tags.h"
|
||||
|
||||
#include "common/codecs.h"
|
||||
#include "common/msg.h"
|
||||
#include "common/recorder.h"
|
||||
#include "misc/bstr.h"
|
||||
|
||||
#include "stream/stream.h"
|
||||
#include "demux/demux.h"
|
||||
|
||||
#include "demux/stheader.h"
|
||||
|
||||
#include "dec_audio.h"
|
||||
#include "ad.h"
|
||||
#include "audio/format.h"
|
||||
|
||||
#include "audio/filter/af.h"
|
||||
|
||||
extern const struct ad_functions ad_lavc;
|
||||
|
||||
// Not a real codec - specially treated.
|
||||
extern const struct ad_functions ad_spdif;
|
||||
|
||||
static const struct ad_functions * const ad_drivers[] = {
|
||||
&ad_lavc,
|
||||
NULL
|
||||
};
|
||||
|
||||
static void uninit_decoder(struct dec_audio *d_audio)
|
||||
{
|
||||
audio_reset_decoding(d_audio);
|
||||
if (d_audio->ad_driver) {
|
||||
MP_VERBOSE(d_audio, "Uninit audio decoder.\n");
|
||||
d_audio->ad_driver->uninit(d_audio);
|
||||
}
|
||||
d_audio->ad_driver = NULL;
|
||||
talloc_free(d_audio->priv);
|
||||
d_audio->priv = NULL;
|
||||
}
|
||||
|
||||
static int init_audio_codec(struct dec_audio *d_audio, const char *decoder)
|
||||
{
|
||||
if (!d_audio->ad_driver->init(d_audio, decoder)) {
|
||||
MP_VERBOSE(d_audio, "Audio decoder init failed.\n");
|
||||
d_audio->ad_driver = NULL;
|
||||
uninit_decoder(d_audio);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct mp_decoder_list *audio_decoder_list(void)
|
||||
{
|
||||
struct mp_decoder_list *list = talloc_zero(NULL, struct mp_decoder_list);
|
||||
for (int i = 0; ad_drivers[i] != NULL; i++)
|
||||
ad_drivers[i]->add_decoders(list);
|
||||
return list;
|
||||
}
|
||||
|
||||
static struct mp_decoder_list *audio_select_decoders(struct dec_audio *d_audio)
|
||||
{
|
||||
struct MPOpts *opts = d_audio->opts;
|
||||
const char *codec = d_audio->codec->codec;
|
||||
|
||||
struct mp_decoder_list *list = audio_decoder_list();
|
||||
struct mp_decoder_list *new =
|
||||
mp_select_decoders(d_audio->log, list, codec, opts->audio_decoders);
|
||||
if (d_audio->try_spdif && codec) {
|
||||
struct mp_decoder_list *spdif =
|
||||
select_spdif_codec(codec, opts->audio_spdif);
|
||||
mp_append_decoders(spdif, new);
|
||||
talloc_free(new);
|
||||
new = spdif;
|
||||
}
|
||||
talloc_free(list);
|
||||
return new;
|
||||
}
|
||||
|
||||
static const struct ad_functions *find_driver(const char *name)
|
||||
{
|
||||
for (int i = 0; ad_drivers[i] != NULL; i++) {
|
||||
if (strcmp(ad_drivers[i]->name, name) == 0)
|
||||
return ad_drivers[i];
|
||||
}
|
||||
if (strcmp(name, "spdif") == 0)
|
||||
return &ad_spdif;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int audio_init_best_codec(struct dec_audio *d_audio)
|
||||
{
|
||||
uninit_decoder(d_audio);
|
||||
assert(!d_audio->ad_driver);
|
||||
|
||||
struct mp_decoder_entry *decoder = NULL;
|
||||
struct mp_decoder_list *list = audio_select_decoders(d_audio);
|
||||
|
||||
mp_print_decoders(d_audio->log, MSGL_V, "Codec list:", list);
|
||||
|
||||
for (int n = 0; n < list->num_entries; n++) {
|
||||
struct mp_decoder_entry *sel = &list->entries[n];
|
||||
const struct ad_functions *driver = find_driver(sel->family);
|
||||
if (!driver)
|
||||
continue;
|
||||
MP_VERBOSE(d_audio, "Opening audio decoder %s\n", sel->decoder);
|
||||
d_audio->ad_driver = driver;
|
||||
if (init_audio_codec(d_audio, sel->decoder)) {
|
||||
decoder = sel;
|
||||
break;
|
||||
}
|
||||
MP_WARN(d_audio, "Audio decoder init failed for %s\n", sel->decoder);
|
||||
}
|
||||
|
||||
if (d_audio->ad_driver) {
|
||||
d_audio->decoder_desc =
|
||||
talloc_asprintf(d_audio, "%s (%s)", decoder->decoder, decoder->desc);
|
||||
MP_VERBOSE(d_audio, "Selected audio codec: %s\n", d_audio->decoder_desc);
|
||||
} else {
|
||||
MP_ERR(d_audio, "Failed to initialize an audio decoder for codec '%s'.\n",
|
||||
d_audio->codec->codec);
|
||||
}
|
||||
|
||||
talloc_free(list);
|
||||
return !!d_audio->ad_driver;
|
||||
}
|
||||
|
||||
void audio_uninit(struct dec_audio *d_audio)
|
||||
{
|
||||
if (!d_audio)
|
||||
return;
|
||||
uninit_decoder(d_audio);
|
||||
talloc_free(d_audio);
|
||||
}
|
||||
|
||||
void audio_reset_decoding(struct dec_audio *d_audio)
|
||||
{
|
||||
if (d_audio->ad_driver)
|
||||
d_audio->ad_driver->control(d_audio, ADCTRL_RESET, NULL);
|
||||
d_audio->pts = MP_NOPTS_VALUE;
|
||||
talloc_free(d_audio->current_frame);
|
||||
d_audio->current_frame = NULL;
|
||||
talloc_free(d_audio->packet);
|
||||
d_audio->packet = NULL;
|
||||
talloc_free(d_audio->new_segment);
|
||||
d_audio->new_segment = NULL;
|
||||
d_audio->start = d_audio->end = MP_NOPTS_VALUE;
|
||||
}
|
||||
|
||||
static void fix_audio_pts(struct dec_audio *da)
|
||||
{
|
||||
if (!da->current_frame)
|
||||
return;
|
||||
|
||||
double frame_pts = mp_aframe_get_pts(da->current_frame);
|
||||
if (frame_pts != MP_NOPTS_VALUE) {
|
||||
if (da->pts != MP_NOPTS_VALUE)
|
||||
MP_STATS(da, "value %f audio-pts-err", da->pts - frame_pts);
|
||||
|
||||
// Keep the interpolated timestamp if it doesn't deviate more
|
||||
// than 1 ms from the real one. (MKV rounded timestamps.)
|
||||
if (da->pts == MP_NOPTS_VALUE || fabs(da->pts - frame_pts) > 0.001)
|
||||
da->pts = frame_pts;
|
||||
}
|
||||
|
||||
if (da->pts == MP_NOPTS_VALUE && da->header->missing_timestamps)
|
||||
da->pts = 0;
|
||||
|
||||
mp_aframe_set_pts(da->current_frame, da->pts);
|
||||
|
||||
if (da->pts != MP_NOPTS_VALUE)
|
||||
da->pts += mp_aframe_duration(da->current_frame);
|
||||
}
|
||||
|
||||
void audio_work(struct dec_audio *da)
|
||||
{
|
||||
if (da->current_frame || !da->ad_driver)
|
||||
return;
|
||||
|
||||
if (!da->packet && !da->new_segment &&
|
||||
demux_read_packet_async(da->header, &da->packet) == 0)
|
||||
{
|
||||
da->current_state = DATA_WAIT;
|
||||
return;
|
||||
}
|
||||
|
||||
if (da->packet && da->packet->new_segment) {
|
||||
assert(!da->new_segment);
|
||||
da->new_segment = da->packet;
|
||||
da->packet = NULL;
|
||||
}
|
||||
|
||||
if (da->ad_driver->send_packet(da, da->packet)) {
|
||||
if (da->recorder_sink)
|
||||
mp_recorder_feed_packet(da->recorder_sink, da->packet);
|
||||
|
||||
talloc_free(da->packet);
|
||||
da->packet = NULL;
|
||||
}
|
||||
|
||||
bool progress = da->ad_driver->receive_frame(da, &da->current_frame);
|
||||
|
||||
da->current_state = da->current_frame ? DATA_OK : DATA_AGAIN;
|
||||
if (!progress)
|
||||
da->current_state = DATA_EOF;
|
||||
|
||||
fix_audio_pts(da);
|
||||
|
||||
bool segment_end = da->current_state == DATA_EOF;
|
||||
|
||||
if (da->current_frame) {
|
||||
mp_aframe_clip_timestamps(da->current_frame, da->start, da->end);
|
||||
double frame_pts = mp_aframe_get_pts(da->current_frame);
|
||||
if (frame_pts != MP_NOPTS_VALUE && da->start != MP_NOPTS_VALUE)
|
||||
segment_end = frame_pts >= da->end;
|
||||
if (mp_aframe_get_size(da->current_frame) == 0) {
|
||||
talloc_free(da->current_frame);
|
||||
da->current_frame = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// If there's a new segment, start it as soon as we're drained/finished.
|
||||
if (segment_end && da->new_segment) {
|
||||
struct demux_packet *new_segment = da->new_segment;
|
||||
da->new_segment = NULL;
|
||||
|
||||
if (da->codec == new_segment->codec) {
|
||||
audio_reset_decoding(da);
|
||||
} else {
|
||||
da->codec = new_segment->codec;
|
||||
da->ad_driver->uninit(da);
|
||||
da->ad_driver = NULL;
|
||||
audio_init_best_codec(da);
|
||||
}
|
||||
|
||||
da->start = new_segment->start;
|
||||
da->end = new_segment->end;
|
||||
|
||||
new_segment->new_segment = false;
|
||||
|
||||
da->packet = new_segment;
|
||||
da->current_state = DATA_AGAIN;
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch an audio frame decoded with audio_work(). Returns one of:
|
||||
// DATA_OK: *out_frame is set to a new image
|
||||
// DATA_WAIT: waiting for demuxer; will receive a wakeup signal
|
||||
// DATA_EOF: end of file, no more frames to be expected
|
||||
// DATA_AGAIN: dropped frame or something similar
|
||||
int audio_get_frame(struct dec_audio *da, struct mp_aframe **out_frame)
|
||||
{
|
||||
*out_frame = NULL;
|
||||
if (da->current_frame) {
|
||||
*out_frame = da->current_frame;
|
||||
da->current_frame = NULL;
|
||||
return DATA_OK;
|
||||
}
|
||||
if (da->current_state == DATA_OK)
|
||||
return DATA_AGAIN;
|
||||
return da->current_state;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MPLAYER_DEC_AUDIO_H
|
||||
#define MPLAYER_DEC_AUDIO_H
|
||||
|
||||
#include "audio/chmap.h"
|
||||
#include "audio/aframe.h"
|
||||
#include "demux/demux.h"
|
||||
#include "demux/stheader.h"
|
||||
|
||||
struct mp_decoder_list;
|
||||
|
||||
struct dec_audio {
|
||||
struct mp_log *log;
|
||||
struct MPOpts *opts;
|
||||
struct mpv_global *global;
|
||||
const struct ad_functions *ad_driver;
|
||||
struct sh_stream *header;
|
||||
struct mp_codec_params *codec;
|
||||
char *decoder_desc;
|
||||
|
||||
bool try_spdif;
|
||||
|
||||
struct mp_recorder_sink *recorder_sink;
|
||||
|
||||
// For free use by the ad_driver
|
||||
void *priv;
|
||||
|
||||
// Strictly internal (dec_audio.c).
|
||||
|
||||
double pts; // endpts of previous frame
|
||||
double start, end;
|
||||
struct demux_packet *packet;
|
||||
struct demux_packet *new_segment;
|
||||
struct mp_aframe *current_frame;
|
||||
int current_state;
|
||||
};
|
||||
|
||||
struct mp_decoder_list *audio_decoder_list(void);
|
||||
int audio_init_best_codec(struct dec_audio *d_audio);
|
||||
void audio_uninit(struct dec_audio *d_audio);
|
||||
|
||||
void audio_work(struct dec_audio *d_audio);
|
||||
int audio_get_frame(struct dec_audio *d_audio, struct mp_aframe **out_frame);
|
||||
|
||||
void audio_reset_decoding(struct dec_audio *d_audio);
|
||||
|
||||
// ad_spdif.c
|
||||
struct mp_decoder_list *select_spdif_codec(const char *codec, const char *pref);
|
||||
|
||||
#endif /* MPLAYER_DEC_AUDIO_H */
|
||||
@@ -0,0 +1,829 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/global.h"
|
||||
|
||||
#include "options/m_option.h"
|
||||
#include "options/m_config.h"
|
||||
|
||||
#include "audio/audio_buffer.h"
|
||||
#include "af.h"
|
||||
|
||||
// Static list of filters
|
||||
extern const struct af_info af_info_channels;
|
||||
extern const struct af_info af_info_format;
|
||||
extern const struct af_info af_info_volume;
|
||||
extern const struct af_info af_info_equalizer;
|
||||
extern const struct af_info af_info_pan;
|
||||
extern const struct af_info af_info_lavcac3enc;
|
||||
extern const struct af_info af_info_lavrresample;
|
||||
extern const struct af_info af_info_scaletempo;
|
||||
extern const struct af_info af_info_bs2b;
|
||||
extern const struct af_info af_info_lavfi;
|
||||
extern const struct af_info af_info_lavfi_bridge;
|
||||
extern const struct af_info af_info_rubberband;
|
||||
|
||||
static const struct af_info *const filter_list[] = {
|
||||
&af_info_channels,
|
||||
&af_info_format,
|
||||
&af_info_volume,
|
||||
&af_info_equalizer,
|
||||
&af_info_pan,
|
||||
&af_info_lavcac3enc,
|
||||
&af_info_lavrresample,
|
||||
#if HAVE_RUBBERBAND
|
||||
&af_info_rubberband,
|
||||
#endif
|
||||
&af_info_scaletempo,
|
||||
&af_info_lavfi,
|
||||
&af_info_lavfi_bridge,
|
||||
NULL
|
||||
};
|
||||
|
||||
static bool get_desc(struct m_obj_desc *dst, int index)
|
||||
{
|
||||
if (index >= MP_ARRAY_SIZE(filter_list) - 1)
|
||||
return false;
|
||||
const struct af_info *af = filter_list[index];
|
||||
*dst = (struct m_obj_desc) {
|
||||
.name = af->name,
|
||||
.description = af->info,
|
||||
.priv_size = af->priv_size,
|
||||
.priv_defaults = af->priv_defaults,
|
||||
.options = af->options,
|
||||
.p = af,
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
const struct m_obj_list af_obj_list = {
|
||||
.get_desc = get_desc,
|
||||
.description = "audio filters",
|
||||
.allow_disable_entries = true,
|
||||
.allow_unknown_entries = true,
|
||||
.aliases = {
|
||||
{"force", "format"},
|
||||
{0}
|
||||
},
|
||||
};
|
||||
|
||||
static void af_forget_frames(struct af_instance *af)
|
||||
{
|
||||
for (int n = 0; n < af->num_out_queued; n++)
|
||||
talloc_free(af->out_queued[n]);
|
||||
af->num_out_queued = 0;
|
||||
}
|
||||
|
||||
static void af_chain_forget_frames(struct af_stream *s)
|
||||
{
|
||||
for (struct af_instance *cur = s->first; cur; cur = cur->next)
|
||||
af_forget_frames(cur);
|
||||
}
|
||||
|
||||
static void af_copy_unset_fields(struct mp_audio *dst, struct mp_audio *src)
|
||||
{
|
||||
if (dst->format == AF_FORMAT_UNKNOWN)
|
||||
mp_audio_set_format(dst, src->format);
|
||||
if (dst->nch == 0)
|
||||
mp_audio_set_channels(dst, &src->channels);
|
||||
if (dst->rate == 0)
|
||||
dst->rate = src->rate;
|
||||
}
|
||||
|
||||
static int input_control(struct af_instance* af, int cmd, void* arg)
|
||||
{
|
||||
switch (cmd) {
|
||||
case AF_CONTROL_REINIT:
|
||||
assert(arg == &((struct af_stream *)af->priv)->input);
|
||||
return AF_OK;
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static int output_control(struct af_instance* af, int cmd, void* arg)
|
||||
{
|
||||
struct af_stream *s = af->priv;
|
||||
struct mp_audio *output = &s->output;
|
||||
struct mp_audio *filter_output = &s->filter_output;
|
||||
|
||||
switch (cmd) {
|
||||
case AF_CONTROL_REINIT: {
|
||||
struct mp_audio *in = arg;
|
||||
struct mp_audio orig_in = *in;
|
||||
|
||||
*filter_output = *output;
|
||||
af_copy_unset_fields(filter_output, in);
|
||||
*in = *filter_output;
|
||||
return mp_audio_config_equals(in, &orig_in) ? AF_OK : AF_FALSE;
|
||||
}
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static int dummy_filter(struct af_instance *af, struct mp_audio *frame)
|
||||
{
|
||||
af_add_output_frame(af, frame);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Function for creating a new filter of type name.The name may
|
||||
contain the commandline parameters for the filter */
|
||||
static struct af_instance *af_create(struct af_stream *s, char *name,
|
||||
char **args)
|
||||
{
|
||||
const char *lavfi_name = NULL;
|
||||
char **lavfi_args = NULL;
|
||||
struct m_obj_desc desc;
|
||||
if (!m_obj_list_find(&desc, &af_obj_list, bstr0(name))) {
|
||||
if (!m_obj_list_find(&desc, &af_obj_list, bstr0("lavfi-bridge"))) {
|
||||
MP_ERR(s, "Couldn't find audio filter '%s'.\n", name);
|
||||
return NULL;
|
||||
}
|
||||
lavfi_name = name;
|
||||
lavfi_args = args;
|
||||
args = NULL;
|
||||
if (strncmp(lavfi_name, "lavfi-", 6) == 0)
|
||||
lavfi_name += 6;
|
||||
}
|
||||
MP_VERBOSE(s, "Adding filter %s \n", name);
|
||||
|
||||
struct af_instance *af = talloc_zero(NULL, struct af_instance);
|
||||
*af = (struct af_instance) {
|
||||
.full_name = talloc_strdup(af, name),
|
||||
.info = desc.p,
|
||||
.data = talloc_zero(af, struct mp_audio),
|
||||
.log = mp_log_new(af, s->log, name),
|
||||
.opts = s->opts,
|
||||
.replaygain_data = s->replaygain_data,
|
||||
.out_pool = mp_audio_pool_create(af),
|
||||
};
|
||||
struct m_config *config =
|
||||
m_config_from_obj_desc_and_args(af, s->log, NULL, &desc,
|
||||
name, s->opts->af_defs, args);
|
||||
if (!config)
|
||||
goto error;
|
||||
if (lavfi_name) {
|
||||
// Pass the filter arguments as proper sub-options to the bridge filter.
|
||||
struct m_config_option *name_opt = m_config_get_co(config, bstr0("name"));
|
||||
assert(name_opt);
|
||||
assert(name_opt->opt->type == &m_option_type_string);
|
||||
if (m_config_set_option_raw(config, name_opt, &lavfi_name, 0) < 0)
|
||||
goto error;
|
||||
struct m_config_option *opts = m_config_get_co(config, bstr0("opts"));
|
||||
assert(opts);
|
||||
assert(opts->opt->type == &m_option_type_keyvalue_list);
|
||||
if (m_config_set_option_raw(config, opts, &lavfi_args, 0) < 0)
|
||||
goto error;
|
||||
af->full_name = talloc_asprintf(af, "%s (lavfi)", af->full_name);
|
||||
}
|
||||
af->priv = config->optstruct;
|
||||
|
||||
// Initialize the new filter
|
||||
if (af->info->open(af) != AF_OK)
|
||||
goto error;
|
||||
|
||||
return af;
|
||||
|
||||
error:
|
||||
MP_ERR(s, "Couldn't create or open audio filter '%s'\n", name);
|
||||
talloc_free(af);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Create and insert a new filter of type name before the filter in the
|
||||
argument. This function can be called during runtime, the return
|
||||
value is the new filter */
|
||||
static struct af_instance *af_prepend(struct af_stream *s,
|
||||
struct af_instance *af,
|
||||
char *name, char **args)
|
||||
{
|
||||
if (!af)
|
||||
af = s->last;
|
||||
if (af == s->first)
|
||||
af = s->first->next;
|
||||
// Create the new filter and make sure it is OK
|
||||
struct af_instance *new = af_create(s, name, args);
|
||||
if (!new)
|
||||
return NULL;
|
||||
// Update pointers
|
||||
new->next = af;
|
||||
new->prev = af->prev;
|
||||
af->prev = new;
|
||||
new->prev->next = new;
|
||||
return new;
|
||||
}
|
||||
|
||||
// Uninit and remove the filter "af"
|
||||
static void af_remove(struct af_stream *s, struct af_instance *af)
|
||||
{
|
||||
if (!af)
|
||||
return;
|
||||
|
||||
if (af == s->first || af == s->last)
|
||||
return;
|
||||
|
||||
// Print friendly message
|
||||
MP_VERBOSE(s, "Removing filter %s \n", af->info->name);
|
||||
|
||||
// Detach pointers
|
||||
af->prev->next = af->next;
|
||||
af->next->prev = af->prev;
|
||||
|
||||
if (af->uninit)
|
||||
af->uninit(af);
|
||||
af_forget_frames(af);
|
||||
talloc_free(af);
|
||||
}
|
||||
|
||||
static void remove_auto_inserted_filters(struct af_stream *s)
|
||||
{
|
||||
repeat:
|
||||
for (struct af_instance *af = s->first; af; af = af->next) {
|
||||
if (af->auto_inserted) {
|
||||
af_remove(s, af);
|
||||
goto repeat;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void af_print_filter_chain(struct af_stream *s, struct af_instance *at,
|
||||
int msg_level)
|
||||
{
|
||||
MP_MSG(s, msg_level, "Audio filter chain:\n");
|
||||
|
||||
struct af_instance *af = s->first;
|
||||
while (af) {
|
||||
char b[128] = {0};
|
||||
mp_snprintf_cat(b, sizeof(b), " [%s] ", af->full_name);
|
||||
if (af->label)
|
||||
mp_snprintf_cat(b, sizeof(b), "\"%s\" ", af->label);
|
||||
if (af->data)
|
||||
mp_snprintf_cat(b, sizeof(b), "%s", mp_audio_config_to_str(af->data));
|
||||
if (af->auto_inserted)
|
||||
mp_snprintf_cat(b, sizeof(b), " [a]");
|
||||
if (af == at)
|
||||
mp_snprintf_cat(b, sizeof(b), " <-");
|
||||
MP_MSG(s, msg_level, "%s\n", b);
|
||||
|
||||
af = af->next;
|
||||
}
|
||||
|
||||
MP_MSG(s, msg_level, " [ao] %s\n", mp_audio_config_to_str(&s->output));
|
||||
}
|
||||
|
||||
static void reset_formats(struct af_stream *s)
|
||||
{
|
||||
struct mp_audio none = {0};
|
||||
for (struct af_instance *af = s->first; af; af = af->next) {
|
||||
if (af != s->first && af != s->last)
|
||||
mp_audio_copy_config(af->data, &none);
|
||||
}
|
||||
}
|
||||
|
||||
static int filter_reinit(struct af_instance *af)
|
||||
{
|
||||
struct af_instance *prev = af->prev;
|
||||
assert(prev);
|
||||
|
||||
// Check if this is the first filter
|
||||
struct mp_audio in = *prev->data;
|
||||
// Reset just in case...
|
||||
mp_audio_set_null_data(&in);
|
||||
|
||||
if (!mp_audio_config_valid(&in))
|
||||
return AF_ERROR;
|
||||
|
||||
af->fmt_in = in;
|
||||
int rv = af->control(af, AF_CONTROL_REINIT, &in);
|
||||
if (rv == AF_OK && !mp_audio_config_equals(&in, prev->data))
|
||||
rv = AF_FALSE; // conversion filter needed
|
||||
if (rv == AF_FALSE)
|
||||
af->fmt_in = in;
|
||||
|
||||
if (rv == AF_OK) {
|
||||
if (!mp_audio_config_valid(af->data))
|
||||
return AF_ERROR;
|
||||
af->fmt_out = *af->data;
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static int filter_reinit_with_conversion(struct af_stream *s, struct af_instance *af)
|
||||
{
|
||||
int rv = filter_reinit(af);
|
||||
|
||||
// Conversion filter is needed
|
||||
if (rv == AF_FALSE) {
|
||||
// First try if we can change the output format of the previous
|
||||
// filter to the input format the current filter is expecting.
|
||||
struct mp_audio in = af->fmt_in;
|
||||
if (af->prev != s->first && !mp_audio_config_equals(af->prev->data, &in)) {
|
||||
// This should have been successful (because it succeeded
|
||||
// before), even if just reverting to the old output format.
|
||||
mp_audio_copy_config(af->prev->data, &in);
|
||||
rv = filter_reinit(af->prev);
|
||||
if (rv != AF_OK)
|
||||
return rv;
|
||||
}
|
||||
if (!mp_audio_config_equals(af->prev->data, &in)) {
|
||||
// Retry with conversion filter added.
|
||||
struct af_instance *new =
|
||||
af_prepend(s, af, "lavrresample", NULL);
|
||||
if (!new)
|
||||
return AF_ERROR;
|
||||
new->auto_inserted = true;
|
||||
mp_audio_copy_config(new->data, &in);
|
||||
rv = filter_reinit(new);
|
||||
if (rv != AF_OK)
|
||||
af_remove(s, new);
|
||||
}
|
||||
if (rv == AF_OK)
|
||||
rv = filter_reinit(af);
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static int af_find_output_conversion(struct af_stream *s, struct mp_audio *cfg)
|
||||
{
|
||||
assert(mp_audio_config_valid(&s->output));
|
||||
assert(s->initialized > 0);
|
||||
|
||||
if (mp_chmap_equals_reordered(&s->input.channels, &s->output.channels))
|
||||
return AF_ERROR;
|
||||
|
||||
// Heuristic to detect point of conversion. If it looks like something
|
||||
// more complicated is going on, better bail out.
|
||||
// We expect that the last filter converts channels.
|
||||
struct af_instance *conv = s->last->prev;
|
||||
if (!conv->auto_inserted)
|
||||
return AF_ERROR;
|
||||
if (!(mp_chmap_equals_reordered(&conv->fmt_in.channels, &s->input.channels) &&
|
||||
mp_chmap_equals_reordered(&conv->fmt_out.channels, &s->output.channels)))
|
||||
return AF_ERROR;
|
||||
// Also, should be the only one which does auto conversion.
|
||||
for (struct af_instance *af = s->first->next; af != s->last; af = af->next)
|
||||
{
|
||||
if (af != conv && af->auto_inserted &&
|
||||
!mp_chmap_equals_reordered(&af->fmt_in.channels, &af->fmt_out.channels))
|
||||
return AF_ERROR;
|
||||
}
|
||||
// And not if it's the only filter.
|
||||
if (conv->prev == s->first && conv->next == s->last)
|
||||
return AF_ERROR;
|
||||
|
||||
*cfg = s->output;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
// Return AF_OK on success or AF_ERROR on failure.
|
||||
static int af_do_reinit(struct af_stream *s, bool second_pass)
|
||||
{
|
||||
struct mp_audio convert_early = {0};
|
||||
if (second_pass) {
|
||||
// If a channel conversion happens, and it is done by an auto-inserted
|
||||
// filter, then insert a filter to convert it early. Otherwise, do
|
||||
// nothing and return immediately.
|
||||
if (af_find_output_conversion(s, &convert_early) != AF_OK)
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
remove_auto_inserted_filters(s);
|
||||
af_chain_forget_frames(s);
|
||||
reset_formats(s);
|
||||
s->first->fmt_in = s->first->fmt_out = s->input;
|
||||
|
||||
if (mp_audio_config_valid(&convert_early)) {
|
||||
struct af_instance *new = af_prepend(s, s->first, "lavrresample", NULL);
|
||||
if (!new)
|
||||
return AF_ERROR;
|
||||
new->auto_inserted = true;
|
||||
mp_audio_copy_config(new->data, &convert_early);
|
||||
int rv = filter_reinit(new);
|
||||
if (rv != AF_DETACH && rv != AF_OK)
|
||||
return AF_ERROR;
|
||||
MP_VERBOSE(s, "Moving up output conversion.\n");
|
||||
}
|
||||
|
||||
// Start with the second filter, as the first filter is the special input
|
||||
// filter which needs no initialization.
|
||||
struct af_instance *af = s->first->next;
|
||||
while (af) {
|
||||
int rv = filter_reinit_with_conversion(s, af);
|
||||
|
||||
switch (rv) {
|
||||
case AF_OK:
|
||||
af = af->next;
|
||||
break;
|
||||
case AF_FALSE: {
|
||||
// If the format conversion is (probably) caused by spdif, then
|
||||
// (as a feature) drop the filter, instead of failing hard.
|
||||
int fmt_in1 = af->prev->data->format;
|
||||
int fmt_in2 = af->fmt_in.format;
|
||||
if (af_fmt_is_valid(fmt_in1) && af_fmt_is_valid(fmt_in2)) {
|
||||
bool spd1 = af_fmt_is_spdif(fmt_in1);
|
||||
bool spd2 = af_fmt_is_spdif(fmt_in2);
|
||||
if (spd1 != spd2 && af->next) {
|
||||
MP_WARN(af, "Filter %s apparently cannot be used due to "
|
||||
"spdif passthrough - removing it.\n",
|
||||
af->info->name);
|
||||
struct af_instance *aft = af->prev;
|
||||
af_remove(s, af);
|
||||
af = aft->next;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
goto negotiate_error;
|
||||
}
|
||||
case AF_DETACH: { // Filter is redundant and wants to be unloaded
|
||||
struct af_instance *aft = af->prev; // never NULL
|
||||
af_remove(s, af);
|
||||
af = aft->next;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
MP_ERR(s, "Reinitialization did not work, "
|
||||
"audio filter '%s' returned error code %i\n",
|
||||
af->info->name, rv);
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
/* Set previously unset fields in s->output to those of the filter chain
|
||||
* output. This is used to make the output format fixed, and even if you
|
||||
* insert new filters or change the input format, the output format won't
|
||||
* change. (Audio outputs generally can't change format at runtime.) */
|
||||
af_copy_unset_fields(&s->output, &s->filter_output);
|
||||
if (mp_audio_config_equals(&s->output, &s->filter_output)) {
|
||||
s->initialized = 1;
|
||||
af_print_filter_chain(s, NULL, MSGL_V);
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
goto error;
|
||||
|
||||
negotiate_error:
|
||||
MP_ERR(s, "Unable to convert audio input format to output format.\n");
|
||||
error:
|
||||
s->initialized = -1;
|
||||
af_print_filter_chain(s, af, MSGL_ERR);
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
static int af_reinit(struct af_stream *s)
|
||||
{
|
||||
int r = af_do_reinit(s, false);
|
||||
if (r == AF_OK && mp_audio_config_valid(&s->output)) {
|
||||
r = af_do_reinit(s, true);
|
||||
if (r != AF_OK) {
|
||||
MP_ERR(s, "Failed second pass filter negotiation.\n");
|
||||
r = af_do_reinit(s, false);
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
// Uninit and remove all filters
|
||||
void af_uninit(struct af_stream *s)
|
||||
{
|
||||
while (s->first->next && s->first->next != s->last)
|
||||
af_remove(s, s->first->next);
|
||||
af_chain_forget_frames(s);
|
||||
s->initialized = 0;
|
||||
}
|
||||
|
||||
struct af_stream *af_new(struct mpv_global *global)
|
||||
{
|
||||
struct af_stream *s = talloc_zero(NULL, struct af_stream);
|
||||
s->log = mp_log_new(s, global->log, "!af");
|
||||
|
||||
static const struct af_info in = { .name = "in" };
|
||||
s->first = talloc(s, struct af_instance);
|
||||
*s->first = (struct af_instance) {
|
||||
.full_name = "in",
|
||||
.info = &in,
|
||||
.log = s->log,
|
||||
.control = input_control,
|
||||
.filter_frame = dummy_filter,
|
||||
.priv = s,
|
||||
.data = &s->input,
|
||||
};
|
||||
|
||||
static const struct af_info out = { .name = "out" };
|
||||
s->last = talloc(s, struct af_instance);
|
||||
*s->last = (struct af_instance) {
|
||||
.full_name = "out",
|
||||
.info = &out,
|
||||
.log = s->log,
|
||||
.control = output_control,
|
||||
.filter_frame = dummy_filter,
|
||||
.priv = s,
|
||||
.data = &s->filter_output,
|
||||
};
|
||||
|
||||
s->first->next = s->last;
|
||||
s->last->prev = s->first;
|
||||
s->opts = global->opts;
|
||||
return s;
|
||||
}
|
||||
|
||||
void af_destroy(struct af_stream *s)
|
||||
{
|
||||
af_uninit(s);
|
||||
talloc_free(s);
|
||||
}
|
||||
|
||||
/* Initialize the stream "s". This function creates a new filter list
|
||||
if necessary according to the values set in input and output. Input
|
||||
and output should contain the format of the current movie and the
|
||||
format of the preferred output respectively. The function is
|
||||
reentrant i.e. if called with an already initialized stream the
|
||||
stream will be reinitialized.
|
||||
If one of the preferred output parameters is 0 the one that needs
|
||||
no conversion is used (i.e. the output format in the last filter).
|
||||
The return value is 0 if success and -1 if failure */
|
||||
int af_init(struct af_stream *s)
|
||||
{
|
||||
// Precaution in case caller is misbehaving
|
||||
mp_audio_set_null_data(&s->input);
|
||||
mp_audio_set_null_data(&s->output);
|
||||
|
||||
// Check if this is the first call
|
||||
if (s->first->next == s->last) {
|
||||
// Add all filters in the list (if there are any)
|
||||
struct m_obj_settings *list = s->opts->af_settings;
|
||||
for (int i = 0; list && list[i].name; i++) {
|
||||
if (!list[i].enabled)
|
||||
continue;
|
||||
struct af_instance *af =
|
||||
af_prepend(s, s->last, list[i].name, list[i].attribs);
|
||||
if (!af) {
|
||||
af_uninit(s);
|
||||
s->initialized = -1;
|
||||
return -1;
|
||||
}
|
||||
af->label = talloc_strdup(af, list[i].label);
|
||||
}
|
||||
}
|
||||
|
||||
if (af_reinit(s) != AF_OK) {
|
||||
// Something is stuffed audio out will not work
|
||||
MP_ERR(s, "Could not create audio filter chain.\n");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Add filter during execution. This function adds the filter "name"
|
||||
to the stream s. The filter will be inserted somewhere nice in the
|
||||
list of filters. The return value is a pointer to the new filter,
|
||||
If the filter couldn't be added the return value is NULL. */
|
||||
struct af_instance *af_add(struct af_stream *s, char *name, char *label,
|
||||
char **args)
|
||||
{
|
||||
assert(label);
|
||||
|
||||
if (af_find_by_label(s, label))
|
||||
return NULL;
|
||||
|
||||
struct af_instance *new = af_prepend(s, s->last, name, args);
|
||||
if (!new)
|
||||
return NULL;
|
||||
new->label = talloc_strdup(new, label);
|
||||
|
||||
// Reinitialize the filter list
|
||||
if (af_reinit(s) != AF_OK) {
|
||||
af_remove_by_label(s, label);
|
||||
return NULL;
|
||||
}
|
||||
return af_find_by_label(s, label);
|
||||
}
|
||||
|
||||
struct af_instance *af_find_by_label(struct af_stream *s, char *label)
|
||||
{
|
||||
for (struct af_instance *af = s->first; af; af = af->next) {
|
||||
if (af->label && strcmp(af->label, label) == 0)
|
||||
return af;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Remove the first filter that matches this name. Return number of filters
|
||||
* removed (0, 1), or a negative error code if reinit after removing failed.
|
||||
*/
|
||||
int af_remove_by_label(struct af_stream *s, char *label)
|
||||
{
|
||||
struct af_instance *af = af_find_by_label(s, label);
|
||||
if (!af)
|
||||
return 0;
|
||||
af_remove(s, af);
|
||||
if (af_reinit(s) != AF_OK) {
|
||||
af_uninit(s);
|
||||
af_init(s);
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Calculate the total delay [seconds of output] caused by the filters */
|
||||
double af_calc_delay(struct af_stream *s)
|
||||
{
|
||||
struct af_instance *af = s->first;
|
||||
double delay = 0.0;
|
||||
while (af) {
|
||||
delay += af->delay;
|
||||
for (int n = 0; n < af->num_out_queued; n++)
|
||||
delay += af->out_queued[n]->samples / (double)af->data->rate;
|
||||
af = af->next;
|
||||
}
|
||||
return delay;
|
||||
}
|
||||
|
||||
/* Send control to all filters, starting with the last until one accepts the
|
||||
* command with AF_OK. Return the accepting filter. */
|
||||
struct af_instance *af_control_any_rev(struct af_stream *s, int cmd, void *arg)
|
||||
{
|
||||
int res = AF_UNKNOWN;
|
||||
struct af_instance *filt = s->last;
|
||||
while (filt) {
|
||||
res = filt->control(filt, cmd, arg);
|
||||
if (res == AF_OK)
|
||||
return filt;
|
||||
filt = filt->prev;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Send control to all filters. Never stop, even if a filter returns AF_OK. */
|
||||
void af_control_all(struct af_stream *s, int cmd, void *arg)
|
||||
{
|
||||
for (struct af_instance *af = s->first; af; af = af->next)
|
||||
af->control(af, cmd, arg);
|
||||
}
|
||||
|
||||
int af_control_by_label(struct af_stream *s, int cmd, void *arg, bstr label)
|
||||
{
|
||||
char *label_str = bstrdup0(NULL, label);
|
||||
struct af_instance *cur = af_find_by_label(s, label_str);
|
||||
talloc_free(label_str);
|
||||
if (cur) {
|
||||
return cur->control ? cur->control(cur, cmd, arg) : CONTROL_NA;
|
||||
} else {
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
int af_send_command(struct af_stream *s, char *label, char *cmd, char *arg)
|
||||
{
|
||||
char *args[2] = {cmd, arg};
|
||||
if (strcmp(label, "all") == 0) {
|
||||
af_control_all(s, AF_CONTROL_COMMAND, args);
|
||||
return 0;
|
||||
} else {
|
||||
return af_control_by_label(s, AF_CONTROL_COMMAND, args, bstr0(label));
|
||||
}
|
||||
}
|
||||
|
||||
// Used by filters to add a filtered frame to the output queue.
|
||||
// Ownership of frame is transferred from caller to the filter chain.
|
||||
void af_add_output_frame(struct af_instance *af, struct mp_audio *frame)
|
||||
{
|
||||
if (frame) {
|
||||
assert(mp_audio_config_equals(&af->fmt_out, frame));
|
||||
MP_TARRAY_APPEND(af, af->out_queued, af->num_out_queued, frame);
|
||||
}
|
||||
}
|
||||
|
||||
static bool af_has_output_frame(struct af_instance *af)
|
||||
{
|
||||
if (!af->num_out_queued && af->filter_out) {
|
||||
if (af->filter_out(af) < 0)
|
||||
MP_ERR(af, "Error filtering frame.\n");
|
||||
}
|
||||
return af->num_out_queued > 0;
|
||||
}
|
||||
|
||||
static struct mp_audio *af_dequeue_output_frame(struct af_instance *af)
|
||||
{
|
||||
struct mp_audio *res = NULL;
|
||||
if (af_has_output_frame(af)) {
|
||||
res = af->out_queued[0];
|
||||
MP_TARRAY_REMOVE_AT(af->out_queued, af->num_out_queued, 0);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static void read_remaining(struct af_instance *af)
|
||||
{
|
||||
int num_frames;
|
||||
do {
|
||||
num_frames = af->num_out_queued;
|
||||
if (!af->filter_out || af->filter_out(af) < 0)
|
||||
break;
|
||||
} while (num_frames != af->num_out_queued);
|
||||
}
|
||||
|
||||
static int af_do_filter(struct af_instance *af, struct mp_audio *frame)
|
||||
{
|
||||
if (frame)
|
||||
assert(mp_audio_config_equals(&af->fmt_in, frame));
|
||||
int r = af->filter_frame(af, frame);
|
||||
if (r < 0)
|
||||
MP_ERR(af, "Error filtering frame.\n");
|
||||
return r;
|
||||
}
|
||||
|
||||
// Input a frame into the filter chain. Ownership of frame is transferred.
|
||||
// Return >= 0 on success, < 0 on failure (even if output frames were produced)
|
||||
int af_filter_frame(struct af_stream *s, struct mp_audio *frame)
|
||||
{
|
||||
assert(frame);
|
||||
if (s->initialized < 1) {
|
||||
talloc_free(frame);
|
||||
return -1;
|
||||
}
|
||||
return af_do_filter(s->first, frame);
|
||||
}
|
||||
|
||||
// Output the next queued frame (if any) from the full filter chain.
|
||||
// The frame can be retrieved with af_read_output_frame().
|
||||
// eof: if set, assume there's no more input i.e. af_filter_frame() will
|
||||
// not be called (until reset) - flush all internally delayed frames
|
||||
// returns: -1: error, 0: no output, 1: output available
|
||||
int af_output_frame(struct af_stream *s, bool eof)
|
||||
{
|
||||
if (s->last->num_out_queued)
|
||||
return 1;
|
||||
if (s->initialized < 1)
|
||||
return -1;
|
||||
while (1) {
|
||||
struct af_instance *last = NULL;
|
||||
for (struct af_instance * cur = s->first; cur; cur = cur->next) {
|
||||
// Flush remaining frames on EOF, but do that only if the previous
|
||||
// filters have been flushed (i.e. they have no more output).
|
||||
if (eof && !last) {
|
||||
read_remaining(cur);
|
||||
int r = af_do_filter(cur, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
if (af_has_output_frame(cur))
|
||||
last = cur;
|
||||
}
|
||||
if (!last)
|
||||
return 0;
|
||||
if (!last->next)
|
||||
return 1;
|
||||
int r = af_do_filter(last->next, af_dequeue_output_frame(last));
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
struct mp_audio *af_read_output_frame(struct af_stream *s)
|
||||
{
|
||||
if (!s->last->num_out_queued)
|
||||
af_output_frame(s, false);
|
||||
return af_dequeue_output_frame(s->last);
|
||||
}
|
||||
|
||||
void af_unread_output_frame(struct af_stream *s, struct mp_audio *frame)
|
||||
{
|
||||
struct af_instance *af = s->last;
|
||||
MP_TARRAY_INSERT_AT(af, af->out_queued, af->num_out_queued, 0, frame);
|
||||
}
|
||||
|
||||
// Make sure the caller can change data referenced by the frame.
|
||||
// Return negative error code on failure (i.e. you can't write).
|
||||
int af_make_writeable(struct af_instance *af, struct mp_audio *frame)
|
||||
{
|
||||
return mp_audio_pool_make_writeable(af->out_pool, frame);
|
||||
}
|
||||
|
||||
void af_seek_reset(struct af_stream *s)
|
||||
{
|
||||
af_control_all(s, AF_CONTROL_RESET, NULL);
|
||||
af_chain_forget_frames(s);
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MPLAYER_AF_H
|
||||
#define MPLAYER_AF_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "options/options.h"
|
||||
#include "audio/format.h"
|
||||
#include "audio/chmap.h"
|
||||
#include "audio/audio.h"
|
||||
#include "common/msg.h"
|
||||
#include "common/common.h"
|
||||
|
||||
struct af_instance;
|
||||
struct mpv_global;
|
||||
|
||||
// Number of channels
|
||||
#define AF_NCH MP_NUM_CHANNELS
|
||||
|
||||
// Flags for af->filter()
|
||||
#define AF_FILTER_FLAG_EOF 1
|
||||
|
||||
/* Audio filter information not specific for current instance, but for
|
||||
a specific filter */
|
||||
struct af_info {
|
||||
const char *info;
|
||||
const char *name;
|
||||
int (*open)(struct af_instance *vf);
|
||||
int priv_size;
|
||||
const void *priv_defaults;
|
||||
const struct m_option *options;
|
||||
};
|
||||
|
||||
// Linked list of audio filters
|
||||
struct af_instance {
|
||||
const struct af_info *info;
|
||||
char *full_name;
|
||||
struct mp_log *log;
|
||||
struct MPOpts *opts;
|
||||
struct replaygain_data *replaygain_data;
|
||||
int (*control)(struct af_instance *af, int cmd, void *arg);
|
||||
void (*uninit)(struct af_instance *af);
|
||||
/* Feed a frame. The frame is NULL if EOF was reached, and the filter
|
||||
* should drain all remaining buffered data.
|
||||
* Use af_add_output_frame() to output data. The optional filter_out
|
||||
* callback can be set to produce output frames gradually.
|
||||
*/
|
||||
int (*filter_frame)(struct af_instance *af, struct mp_audio *frame);
|
||||
int (*filter_out)(struct af_instance *af);
|
||||
void *priv;
|
||||
struct mp_audio *data; // configuration and buffer for outgoing data stream
|
||||
|
||||
struct af_instance *next;
|
||||
struct af_instance *prev;
|
||||
double delay; /* Delay caused by the filter, in seconds of audio consumed
|
||||
* without corresponding output */
|
||||
bool auto_inserted; // inserted by af.c, such as conversion filters
|
||||
char *label;
|
||||
|
||||
struct mp_audio fmt_in, fmt_out;
|
||||
|
||||
struct mp_audio **out_queued;
|
||||
int num_out_queued;
|
||||
|
||||
struct mp_audio_pool *out_pool;
|
||||
};
|
||||
|
||||
// Current audio stream
|
||||
struct af_stream {
|
||||
int initialized; // 0: no, 1: yes, -1: attempted to, but failed
|
||||
|
||||
// The first and last filter in the list
|
||||
struct af_instance *first;
|
||||
struct af_instance *last;
|
||||
// The user sets the input format (what the decoder outputs), and sets some
|
||||
// or all fields in output to the output format the AO accepts.
|
||||
struct mp_audio input;
|
||||
struct mp_audio output;
|
||||
struct mp_audio filter_output;
|
||||
|
||||
struct mp_log *log;
|
||||
struct MPOpts *opts;
|
||||
struct replaygain_data *replaygain_data;
|
||||
};
|
||||
|
||||
// Return values
|
||||
#define AF_DETACH (CONTROL_OK + 1)
|
||||
#define AF_OK CONTROL_OK
|
||||
#define AF_TRUE CONTROL_TRUE
|
||||
#define AF_FALSE CONTROL_FALSE
|
||||
#define AF_UNKNOWN CONTROL_UNKNOWN
|
||||
#define AF_ERROR CONTROL_ERROR
|
||||
|
||||
// Parameters for af_control_*
|
||||
enum af_control {
|
||||
AF_CONTROL_REINIT = 1,
|
||||
AF_CONTROL_RESET,
|
||||
AF_CONTROL_SET_VOLUME,
|
||||
AF_CONTROL_SET_PAN_LEVEL,
|
||||
AF_CONTROL_SET_PAN_NOUT,
|
||||
AF_CONTROL_SET_PAN_BALANCE,
|
||||
AF_CONTROL_GET_PAN_BALANCE,
|
||||
AF_CONTROL_SET_PLAYBACK_SPEED,
|
||||
AF_CONTROL_SET_PLAYBACK_SPEED_RESAMPLE,
|
||||
AF_CONTROL_GET_METADATA,
|
||||
AF_CONTROL_COMMAND,
|
||||
};
|
||||
|
||||
// Argument for AF_CONTROL_SET_PAN_LEVEL
|
||||
typedef struct af_control_ext_s {
|
||||
void* arg; // Argument
|
||||
int ch; // Chanel number
|
||||
} af_control_ext_t;
|
||||
|
||||
struct af_stream *af_new(struct mpv_global *global);
|
||||
void af_destroy(struct af_stream *s);
|
||||
int af_init(struct af_stream *s);
|
||||
void af_uninit(struct af_stream *s);
|
||||
struct af_instance *af_add(struct af_stream *s, char *name, char *label,
|
||||
char **args);
|
||||
int af_remove_by_label(struct af_stream *s, char *label);
|
||||
struct af_instance *af_find_by_label(struct af_stream *s, char *label);
|
||||
struct af_instance *af_control_any_rev(struct af_stream *s, int cmd, void *arg);
|
||||
void af_control_all(struct af_stream *s, int cmd, void *arg);
|
||||
int af_control_by_label(struct af_stream *s, int cmd, void *arg, bstr label);
|
||||
void af_seek_reset(struct af_stream *s);
|
||||
int af_send_command(struct af_stream *s, char *label, char *cmd, char *arg);
|
||||
|
||||
void af_add_output_frame(struct af_instance *af, struct mp_audio *frame);
|
||||
int af_filter_frame(struct af_stream *s, struct mp_audio *frame);
|
||||
int af_output_frame(struct af_stream *s, bool eof);
|
||||
struct mp_audio *af_read_output_frame(struct af_stream *s);
|
||||
void af_unread_output_frame(struct af_stream *s, struct mp_audio *frame);
|
||||
int af_make_writeable(struct af_instance *af, struct mp_audio *frame);
|
||||
|
||||
double af_calc_delay(struct af_stream *s);
|
||||
|
||||
int af_test_output(struct af_instance *af, struct mp_audio *out);
|
||||
|
||||
int af_from_ms(int n, float *in, int *out, int rate, float mi, float ma);
|
||||
float af_softclip(float a);
|
||||
|
||||
#endif /* MPLAYER_AF_H */
|
||||
@@ -0,0 +1,255 @@
|
||||
/*
|
||||
* Audio filter that adds and removes channels, according to the
|
||||
* command line parameter channels. It is stupid and can only add
|
||||
* silence or copy channels, not mix or filter.
|
||||
*
|
||||
* Original author: Anders
|
||||
*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "af.h"
|
||||
|
||||
#define FR 0
|
||||
#define TO 1
|
||||
|
||||
typedef struct af_channels_s{
|
||||
int route[AF_NCH][2];
|
||||
int nch, nr;
|
||||
int router;
|
||||
char *routes;
|
||||
}af_channels_t;
|
||||
|
||||
// Local function for copying data
|
||||
static void copy(struct af_instance *af, void* in, void* out,
|
||||
int ins, int inos,int outs, int outos, int len, int bps)
|
||||
{
|
||||
switch(bps){
|
||||
case 1:{
|
||||
int8_t* tin = (int8_t*)in;
|
||||
int8_t* tout = (int8_t*)out;
|
||||
tin += inos;
|
||||
tout += outos;
|
||||
len = len/ins;
|
||||
while(len--){
|
||||
*tout=*tin;
|
||||
tin +=ins;
|
||||
tout+=outs;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 2:{
|
||||
int16_t* tin = (int16_t*)in;
|
||||
int16_t* tout = (int16_t*)out;
|
||||
tin += inos;
|
||||
tout += outos;
|
||||
len = len/(2*ins);
|
||||
while(len--){
|
||||
*tout=*tin;
|
||||
tin +=ins;
|
||||
tout+=outs;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 3:{
|
||||
int8_t* tin = (int8_t*)in;
|
||||
int8_t* tout = (int8_t*)out;
|
||||
tin += 3 * inos;
|
||||
tout += 3 * outos;
|
||||
len = len / ( 3 * ins);
|
||||
while (len--) {
|
||||
tout[0] = tin[0];
|
||||
tout[1] = tin[1];
|
||||
tout[2] = tin[2];
|
||||
tin += 3 * ins;
|
||||
tout += 3 * outs;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 4:{
|
||||
int32_t* tin = (int32_t*)in;
|
||||
int32_t* tout = (int32_t*)out;
|
||||
tin += inos;
|
||||
tout += outos;
|
||||
len = len/(4*ins);
|
||||
while(len--){
|
||||
*tout=*tin;
|
||||
tin +=ins;
|
||||
tout+=outs;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 8:{
|
||||
int64_t* tin = (int64_t*)in;
|
||||
int64_t* tout = (int64_t*)out;
|
||||
tin += inos;
|
||||
tout += outos;
|
||||
len = len/(8*ins);
|
||||
while(len--){
|
||||
*tout=*tin;
|
||||
tin +=ins;
|
||||
tout+=outs;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
MP_ERR(af, "Unsupported number of bytes/sample: %i"
|
||||
" please report this error on the MPlayer mailing list. \n",bps);
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the routes are sane
|
||||
static int check_routes(struct af_instance *af, int nin, int nout)
|
||||
{
|
||||
af_channels_t* s = af->priv;
|
||||
int i;
|
||||
if((s->nr < 1) || (s->nr > AF_NCH)){
|
||||
MP_ERR(af, "The number of routing pairs must be"
|
||||
" between 1 and %i. Current value is %i\n",AF_NCH,s->nr);
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
for(i=0;i<s->nr;i++){
|
||||
if((s->route[i][FR] >= nin) || (s->route[i][TO] >= nout)){
|
||||
MP_ERR(af, "Invalid routing in pair nr. %i.\n", i);
|
||||
return AF_ERROR;
|
||||
}
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
// Initialization and runtime control
|
||||
static int control(struct af_instance* af, int cmd, void* arg)
|
||||
{
|
||||
af_channels_t* s = af->priv;
|
||||
switch(cmd){
|
||||
case AF_CONTROL_REINIT: ;
|
||||
|
||||
struct mp_chmap chmap;
|
||||
mp_chmap_set_unknown(&chmap, s->nch);
|
||||
mp_audio_set_channels(af->data, &chmap);
|
||||
|
||||
// Set default channel assignment
|
||||
if(!s->router){
|
||||
int i;
|
||||
// Make sure this filter isn't redundant
|
||||
if(af->data->nch == ((struct mp_audio*)arg)->nch)
|
||||
return AF_DETACH;
|
||||
|
||||
// If mono: fake stereo
|
||||
if(((struct mp_audio*)arg)->nch == 1){
|
||||
s->nr = MPMIN(af->data->nch,2);
|
||||
for(i=0;i<s->nr;i++){
|
||||
s->route[i][FR] = 0;
|
||||
s->route[i][TO] = i;
|
||||
}
|
||||
}
|
||||
else{
|
||||
s->nr = MPMIN(af->data->nch, ((struct mp_audio*)arg)->nch);
|
||||
for(i=0;i<s->nr;i++){
|
||||
s->route[i][FR] = i;
|
||||
s->route[i][TO] = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
af->data->rate = ((struct mp_audio*)arg)->rate;
|
||||
mp_audio_force_interleaved_format((struct mp_audio*)arg);
|
||||
mp_audio_set_format(af->data, ((struct mp_audio*)arg)->format);
|
||||
return check_routes(af,((struct mp_audio*)arg)->nch,af->data->nch);
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static int filter_frame(struct af_instance *af, struct mp_audio *c)
|
||||
{
|
||||
af_channels_t* s = af->priv;
|
||||
int i;
|
||||
|
||||
if (!c)
|
||||
return 0;
|
||||
|
||||
struct mp_audio *l = mp_audio_pool_get(af->out_pool, &af->fmt_out, c->samples);
|
||||
if (!l) {
|
||||
talloc_free(c);
|
||||
return -1;
|
||||
}
|
||||
mp_audio_copy_attributes(l, c);
|
||||
|
||||
// Reset unused channels
|
||||
memset(l->planes[0],0,mp_audio_psize(c) / c->nch * l->nch);
|
||||
|
||||
if(AF_OK == check_routes(af,c->nch,l->nch))
|
||||
for(i=0;i<s->nr;i++)
|
||||
copy(af, c->planes[0],l->planes[0],c->nch,s->route[i][FR],
|
||||
l->nch,s->route[i][TO],mp_audio_psize(c),c->bps);
|
||||
|
||||
talloc_free(c);
|
||||
af_add_output_frame(af, l);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Allocate memory and set function pointers
|
||||
static int af_open(struct af_instance* af){
|
||||
af->control=control;
|
||||
af->filter_frame = filter_frame;
|
||||
af_channels_t *s = af->priv;
|
||||
|
||||
MP_WARN(af, "This filter is deprecated (no replacement).\n");
|
||||
|
||||
// If router scan commandline for routing pairs
|
||||
if(s->routes && s->routes[0]){
|
||||
char* cp = s->routes;
|
||||
int ch = 0;
|
||||
// Scan for pairs on commandline
|
||||
do {
|
||||
int n = 0;
|
||||
if (ch >= AF_NCH) {
|
||||
MP_FATAL(af, "Can't have more than %d routes.\n", AF_NCH);
|
||||
return AF_ERROR;
|
||||
}
|
||||
sscanf(cp, "%i-%i%n" ,&s->route[ch][FR], &s->route[ch][TO], &n);
|
||||
MP_VERBOSE(af, "Routing from channel %i to"
|
||||
" channel %i\n",s->route[ch][FR],s->route[ch][TO]);
|
||||
cp = &cp[n];
|
||||
ch++;
|
||||
} while(*cp == ',' && *(cp++));
|
||||
s->nr = ch;
|
||||
if (s->nr > 0)
|
||||
s->router = 1;
|
||||
}
|
||||
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT af_channels_t
|
||||
const struct af_info af_info_channels = {
|
||||
.info = "Insert or remove channels",
|
||||
.name = "channels",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(af_channels_t),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_INTRANGE("nch", nch, 0, 1, AF_NCH, OPTDEF_INT(2)),
|
||||
OPT_STRING("routes", routes, 0),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
@@ -1,114 +0,0 @@
|
||||
#include "audio/aframe.h"
|
||||
#include "audio/format.h"
|
||||
#include "common/common.h"
|
||||
#include "filters/f_autoconvert.h"
|
||||
#include "filters/filter_internal.h"
|
||||
#include "filters/user_filters.h"
|
||||
|
||||
struct priv {
|
||||
double speed;
|
||||
double diff; // amount of too many additional samples in normal speed
|
||||
struct mp_aframe *last; // for repeating
|
||||
};
|
||||
|
||||
static void process(struct mp_filter *f)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
|
||||
if (!mp_pin_in_needs_data(f->ppins[1]))
|
||||
return;
|
||||
|
||||
struct mp_frame frame = {0};
|
||||
|
||||
double last_dur = p->last ? mp_aframe_duration(p->last) : 0;
|
||||
if (p->last && p->diff < 0 && -p->diff > last_dur / 2) {
|
||||
MP_VERBOSE(f, "repeat\n");
|
||||
frame = MAKE_FRAME(MP_FRAME_AUDIO, p->last);
|
||||
p->last = NULL;
|
||||
} else {
|
||||
frame = mp_pin_out_read(f->ppins[0]);
|
||||
|
||||
if (frame.type == MP_FRAME_AUDIO) {
|
||||
last_dur = mp_aframe_duration(frame.data);
|
||||
p->diff -= last_dur;
|
||||
if (p->diff > last_dur / 2) {
|
||||
MP_VERBOSE(f, "drop\n");
|
||||
mp_frame_unref(&frame);
|
||||
mp_filter_internal_mark_progress(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (frame.type == MP_FRAME_AUDIO) {
|
||||
struct mp_aframe *fr = frame.data;
|
||||
talloc_free(p->last);
|
||||
p->last = mp_aframe_new_ref(fr);
|
||||
mp_aframe_mul_speed(fr, p->speed);
|
||||
p->diff += mp_aframe_duration(fr);
|
||||
mp_aframe_set_pts(p->last, mp_aframe_end_pts(fr));
|
||||
} else if (frame.type == MP_FRAME_EOF) {
|
||||
TA_FREEP(&p->last);
|
||||
}
|
||||
mp_pin_in_write(f->ppins[1], frame);
|
||||
}
|
||||
|
||||
static bool command(struct mp_filter *f, struct mp_filter_command *cmd)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
|
||||
switch (cmd->type) {
|
||||
case MP_FILTER_COMMAND_SET_SPEED:
|
||||
p->speed = cmd->speed;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void reset(struct mp_filter *f)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
|
||||
TA_FREEP(&p->last);
|
||||
p->diff = 0;
|
||||
}
|
||||
|
||||
static void destroy(struct mp_filter *f)
|
||||
{
|
||||
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,
|
||||
};
|
||||
|
||||
static struct mp_filter *af_drop_create(struct mp_filter *parent, void *options)
|
||||
{
|
||||
struct mp_filter *f = mp_filter_create(parent, &af_drop_filter);
|
||||
if (!f) {
|
||||
talloc_free(options);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
mp_filter_add_pin(f, MP_PIN_IN, "in");
|
||||
mp_filter_add_pin(f, MP_PIN_OUT, "out");
|
||||
|
||||
struct priv *p = f->priv;
|
||||
p->speed = 1.0;
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
const struct mp_user_filter_entry af_drop = {
|
||||
.desc = {
|
||||
.description = "Change audio speed by dropping/repeating frames",
|
||||
.name = "drop",
|
||||
.priv_size = sizeof(struct priv),
|
||||
},
|
||||
.create = af_drop_create,
|
||||
};
|
||||
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* Equalizer filter, implementation of a 10 band time domain graphic
|
||||
* equalizer using IIR filters. The IIR filters are implemented using a
|
||||
* Direct Form II approach, but has been modified (b1 == 0 always) to
|
||||
* save computation.
|
||||
*
|
||||
* Copyright (C) 2001 Anders Johansson ajh@atri.curtin.edu.au
|
||||
*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "af.h"
|
||||
|
||||
#define L 2 // Storage for filter taps
|
||||
#define KM 10 // Max number of bands
|
||||
|
||||
#define Q 1.2247449 /* Q value for band-pass filters 1.2247=(3/2)^(1/2)
|
||||
gives 4dB suppression @ Fc*2 and Fc/2 */
|
||||
|
||||
/* Center frequencies for band-pass filters
|
||||
The different frequency bands are:
|
||||
nr. center frequency
|
||||
0 31.25 Hz
|
||||
1 62.50 Hz
|
||||
2 125.0 Hz
|
||||
3 250.0 Hz
|
||||
4 500.0 Hz
|
||||
5 1.000 kHz
|
||||
6 2.000 kHz
|
||||
7 4.000 kHz
|
||||
8 8.000 kHz
|
||||
9 16.00 kHz
|
||||
*/
|
||||
#define CF {31.25,62.5,125,250,500,1000,2000,4000,8000,16000}
|
||||
|
||||
// Maximum and minimum gain for the bands
|
||||
#define G_MAX +12.0
|
||||
#define G_MIN -12.0
|
||||
|
||||
// Data for specific instances of this filter
|
||||
typedef struct af_equalizer_s
|
||||
{
|
||||
float a[KM][L]; // A weights
|
||||
float b[KM][L]; // B weights
|
||||
float wq[AF_NCH][KM][L]; // Circular buffer for W data
|
||||
float g[AF_NCH][KM]; // Gain factor for each channel and band
|
||||
int K; // Number of used eq bands
|
||||
int channels; // Number of channels
|
||||
float gain_factor; // applied at output to avoid clipping
|
||||
double p[KM];
|
||||
} af_equalizer_t;
|
||||
|
||||
// 2nd order Band-pass Filter design
|
||||
static void bp2(float* a, float* b, float fc, float q){
|
||||
double th= 2.0 * M_PI * fc;
|
||||
double C = (1.0 - tan(th*q/2.0))/(1.0 + tan(th*q/2.0));
|
||||
|
||||
a[0] = (1.0 + C) * cos(th);
|
||||
a[1] = -1 * C;
|
||||
|
||||
b[0] = (1.0 - C)/2.0;
|
||||
b[1] = -1.0050;
|
||||
}
|
||||
|
||||
// Initialization and runtime control
|
||||
static int control(struct af_instance* af, int cmd, void* arg)
|
||||
{
|
||||
af_equalizer_t* s = (af_equalizer_t*)af->priv;
|
||||
|
||||
switch(cmd){
|
||||
case AF_CONTROL_REINIT:{
|
||||
int k =0, i =0;
|
||||
float F[KM] = CF;
|
||||
|
||||
s->gain_factor=0.0;
|
||||
|
||||
// Sanity check
|
||||
if(!arg) return AF_ERROR;
|
||||
|
||||
mp_audio_copy_config(af->data, (struct mp_audio*)arg);
|
||||
mp_audio_set_format(af->data, AF_FORMAT_FLOAT);
|
||||
|
||||
// Calculate number of active filters
|
||||
s->K=KM;
|
||||
while(F[s->K-1] > (float)af->data->rate/2.2)
|
||||
s->K--;
|
||||
|
||||
if(s->K != KM)
|
||||
MP_INFO(af, "Limiting the number of filters to"
|
||||
" %i due to low sample rate.\n",s->K);
|
||||
|
||||
// Generate filter taps
|
||||
for(k=0;k<s->K;k++)
|
||||
bp2(s->a[k],s->b[k],F[k]/((float)af->data->rate),Q);
|
||||
|
||||
// Calculate how much this plugin adds to the overall time delay
|
||||
af->delay = 2.0 / (double)af->data->rate;
|
||||
|
||||
// Calculate gain factor to prevent clipping at output
|
||||
for(k=0;k<AF_NCH;k++)
|
||||
{
|
||||
for(i=0;i<KM;i++)
|
||||
{
|
||||
if(s->gain_factor < s->g[k][i]) s->gain_factor=s->g[k][i];
|
||||
}
|
||||
}
|
||||
|
||||
s->gain_factor=log10(s->gain_factor + 1.0) * 20.0;
|
||||
|
||||
if(s->gain_factor > 0.0)
|
||||
{
|
||||
s->gain_factor=0.1+(s->gain_factor/12.0);
|
||||
}else{
|
||||
s->gain_factor=1;
|
||||
}
|
||||
|
||||
return af_test_output(af,arg);
|
||||
}
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static int filter(struct af_instance* af, struct mp_audio* data)
|
||||
{
|
||||
struct mp_audio* c = data; // Current working data
|
||||
if (!c)
|
||||
return 0;
|
||||
af_equalizer_t* s = (af_equalizer_t*)af->priv; // Setup
|
||||
uint32_t ci = af->data->nch; // Index for channels
|
||||
uint32_t nch = af->data->nch; // Number of channels
|
||||
|
||||
if (af_make_writeable(af, data) < 0) {
|
||||
talloc_free(data);
|
||||
return -1;
|
||||
}
|
||||
|
||||
while(ci--){
|
||||
float* g = s->g[ci]; // Gain factor
|
||||
float* in = ((float*)c->planes[0])+ci;
|
||||
float* out = ((float*)c->planes[0])+ci;
|
||||
float* end = in + c->samples*c->nch; // Block loop end
|
||||
|
||||
while(in < end){
|
||||
register int k = 0; // Frequency band index
|
||||
register float yt = *in; // Current input sample
|
||||
in+=nch;
|
||||
|
||||
// Run the filters
|
||||
for(;k<s->K;k++){
|
||||
// Pointer to circular buffer wq
|
||||
register float* wq = s->wq[ci][k];
|
||||
// Calculate output from AR part of current filter
|
||||
register float w=yt*s->b[k][0] + wq[0]*s->a[k][0] + wq[1]*s->a[k][1];
|
||||
// Calculate output form MA part of current filter
|
||||
yt+=(w + wq[1]*s->b[k][1])*g[k];
|
||||
// Update circular buffer
|
||||
wq[1] = wq[0];
|
||||
wq[0] = w;
|
||||
}
|
||||
// Calculate output
|
||||
*out=yt*s->gain_factor;
|
||||
out+=nch;
|
||||
}
|
||||
}
|
||||
af_add_output_frame(af, data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Allocate memory and set function pointers
|
||||
static int af_open(struct af_instance* af){
|
||||
MP_WARN(af, "This filter is deprecated. Use 'anequalizer' or 'firequalizer' instead.\n");
|
||||
af->control=control;
|
||||
af->filter_frame = filter;
|
||||
af_equalizer_t *priv = af->priv;
|
||||
for(int i=0;i<AF_NCH;i++){
|
||||
for(int j=0;j<KM;j++){
|
||||
priv->g[i][j] = pow(10.0,MPCLAMP(priv->p[j],G_MIN,G_MAX)/20.0)-1.0;
|
||||
}
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT af_equalizer_t
|
||||
const struct af_info af_info_equalizer = {
|
||||
.info = "Equalizer audio filter",
|
||||
.name = "equalizer",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(af_equalizer_t),
|
||||
.options = (const struct m_option[]) {
|
||||
#define BAND(n) OPT_DOUBLE("e" #n, p[n], 0)
|
||||
BAND(0), BAND(1), BAND(2), BAND(3), BAND(4),
|
||||
BAND(5), BAND(6), BAND(7), BAND(8), BAND(9),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
+87
-96
@@ -15,14 +15,18 @@
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "audio/aframe.h"
|
||||
#include "audio/format.h"
|
||||
#include "filters/f_autoconvert.h"
|
||||
#include "filters/filter_internal.h"
|
||||
#include "filters/user_filters.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <libavutil/common.h>
|
||||
|
||||
#include "options/m_option.h"
|
||||
|
||||
struct f_opts {
|
||||
#include "audio/format.h"
|
||||
#include "af.h"
|
||||
|
||||
struct priv {
|
||||
struct m_config *config;
|
||||
|
||||
int in_format;
|
||||
int in_srate;
|
||||
struct m_channels in_channels;
|
||||
@@ -33,111 +37,98 @@ struct f_opts {
|
||||
int fail;
|
||||
};
|
||||
|
||||
struct priv {
|
||||
struct f_opts *opts;
|
||||
struct mp_pin *in_pin;
|
||||
};
|
||||
|
||||
static void process(struct mp_filter *f)
|
||||
static void force_in_params(struct af_instance *af, struct mp_audio *in)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
struct priv *priv = af->priv;
|
||||
|
||||
if (!mp_pin_can_transfer_data(f->ppins[1], p->in_pin))
|
||||
return;
|
||||
if (priv->in_format != AF_FORMAT_UNKNOWN)
|
||||
mp_audio_set_format(in, priv->in_format);
|
||||
|
||||
struct mp_frame frame = mp_pin_out_read(p->in_pin);
|
||||
if (priv->in_channels.num_chmaps > 0)
|
||||
mp_audio_set_channels(in, &priv->in_channels.chmaps[0]);
|
||||
|
||||
if (p->opts->fail) {
|
||||
MP_ERR(f, "Failing on purpose.\n");
|
||||
goto error;
|
||||
}
|
||||
if (priv->in_srate)
|
||||
in->rate = priv->in_srate;
|
||||
}
|
||||
|
||||
if (frame.type == MP_FRAME_EOF) {
|
||||
mp_pin_in_write(f->ppins[1], frame);
|
||||
return;
|
||||
}
|
||||
static void force_out_params(struct af_instance *af, struct mp_audio *out)
|
||||
{
|
||||
struct priv *priv = af->priv;
|
||||
|
||||
if (frame.type != MP_FRAME_AUDIO) {
|
||||
MP_ERR(f, "audio frame expected\n");
|
||||
goto error;
|
||||
}
|
||||
if (priv->out_format != AF_FORMAT_UNKNOWN)
|
||||
mp_audio_set_format(out, priv->out_format);
|
||||
|
||||
struct mp_aframe *in = frame.data;
|
||||
if (priv->out_channels.num_chmaps > 0)
|
||||
mp_audio_set_channels(out, &priv->out_channels.chmaps[0]);
|
||||
|
||||
if (p->opts->out_channels.num_chmaps > 0) {
|
||||
if (!mp_aframe_set_chmap(in, &p->opts->out_channels.chmaps[0])) {
|
||||
MP_ERR(f, "could not force output channels\n");
|
||||
goto error;
|
||||
if (priv->out_srate)
|
||||
out->rate = priv->out_srate;
|
||||
}
|
||||
|
||||
static int control(struct af_instance *af, int cmd, void *arg)
|
||||
{
|
||||
struct priv *priv = af->priv;
|
||||
|
||||
switch (cmd) {
|
||||
case AF_CONTROL_REINIT: {
|
||||
struct mp_audio *in = arg;
|
||||
struct mp_audio orig_in = *in;
|
||||
struct mp_audio *out = af->data;
|
||||
|
||||
force_in_params(af, in);
|
||||
mp_audio_copy_config(out, in);
|
||||
force_out_params(af, out);
|
||||
|
||||
if (in->nch != out->nch || in->bps != out->bps) {
|
||||
MP_ERR(af, "Forced input/output formats are incompatible.\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
if (priv->fail) {
|
||||
MP_ERR(af, "Failing on purpose.\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
return mp_audio_config_equals(in, &orig_in) ? AF_OK : AF_FALSE;
|
||||
}
|
||||
|
||||
if (p->opts->out_srate)
|
||||
mp_aframe_set_rate(in, p->opts->out_srate);
|
||||
|
||||
mp_pin_in_write(f->ppins[1], frame);
|
||||
return;
|
||||
|
||||
error:
|
||||
mp_frame_unref(&frame);
|
||||
mp_filter_internal_mark_failed(f);
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static const struct mp_filter_info af_format_filter = {
|
||||
.name = "format",
|
||||
.priv_size = sizeof(struct priv),
|
||||
.process = process,
|
||||
};
|
||||
|
||||
static struct mp_filter *af_format_create(struct mp_filter *parent,
|
||||
void *options)
|
||||
static int filter(struct af_instance *af, struct mp_audio *data)
|
||||
{
|
||||
struct mp_filter *f = mp_filter_create(parent, &af_format_filter);
|
||||
if (!f) {
|
||||
talloc_free(options);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct priv *p = f->priv;
|
||||
p->opts = talloc_steal(p, options);
|
||||
|
||||
mp_filter_add_pin(f, MP_PIN_IN, "in");
|
||||
mp_filter_add_pin(f, MP_PIN_OUT, "out");
|
||||
|
||||
struct mp_autoconvert *conv = mp_autoconvert_create(f);
|
||||
if (!conv)
|
||||
abort();
|
||||
|
||||
if (p->opts->in_format)
|
||||
mp_autoconvert_add_afmt(conv, p->opts->in_format);
|
||||
if (p->opts->in_srate)
|
||||
mp_autoconvert_add_srate(conv, p->opts->in_srate);
|
||||
if (p->opts->in_channels.num_chmaps > 0)
|
||||
mp_autoconvert_add_chmap(conv, &p->opts->in_channels.chmaps[0]);
|
||||
|
||||
mp_pin_connect(conv->f->pins[0], f->ppins[0]);
|
||||
p->in_pin = conv->f->pins[1];
|
||||
|
||||
return f;
|
||||
if (data)
|
||||
mp_audio_copy_config(data, af->data);
|
||||
af_add_output_frame(af, data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct f_opts
|
||||
static int af_open(struct af_instance *af)
|
||||
{
|
||||
af->control = control;
|
||||
af->filter_frame = filter;
|
||||
|
||||
const struct mp_user_filter_entry af_format = {
|
||||
.desc = {
|
||||
.name = "format",
|
||||
.description = "Force audio format",
|
||||
.priv_size = sizeof(struct f_opts),
|
||||
.options = (const struct m_option[]) {
|
||||
{"format", OPT_AUDIOFORMAT(in_format)},
|
||||
{"srate", OPT_INT(in_srate), M_RANGE(1000, 8*48000)},
|
||||
{"channels", OPT_CHANNELS(in_channels),
|
||||
.flags = M_OPT_CHANNELS_LIMITED},
|
||||
{"out-srate", OPT_INT(out_srate), M_RANGE(1000, 8*48000)},
|
||||
{"out-channels", OPT_CHANNELS(out_channels),
|
||||
.flags = M_OPT_CHANNELS_LIMITED},
|
||||
{"fail", OPT_FLAG(fail)},
|
||||
{0}
|
||||
},
|
||||
force_in_params(af, af->data);
|
||||
force_out_params(af, af->data);
|
||||
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
const struct af_info af_info_format = {
|
||||
.info = "Force audio format",
|
||||
.name = "format",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_AUDIOFORMAT("format", in_format, 0),
|
||||
OPT_INTRANGE("srate", in_srate, 0, 1000, 8*48000),
|
||||
OPT_CHANNELS("channels", in_channels, 0, .min = 1),
|
||||
OPT_AUDIOFORMAT("out-format", out_format, 0),
|
||||
OPT_INTRANGE("out-srate", out_srate, 0, 1000, 8*48000),
|
||||
OPT_CHANNELS("out-channels", out_channels, 0, .min = 1),
|
||||
OPT_FLAG("fail", fail, 0),
|
||||
{0}
|
||||
},
|
||||
.create = af_format_create,
|
||||
};
|
||||
+280
-234
@@ -31,17 +31,14 @@
|
||||
#include <libavutil/bswap.h>
|
||||
#include <libavutil/mem.h>
|
||||
|
||||
#include "audio/aframe.h"
|
||||
#include "audio/chmap_sel.h"
|
||||
#include "audio/fmt-conversion.h"
|
||||
#include "audio/format.h"
|
||||
#include "config.h"
|
||||
|
||||
#include "common/av_common.h"
|
||||
#include "common/common.h"
|
||||
#include "filters/f_autoconvert.h"
|
||||
#include "filters/f_utils.h"
|
||||
#include "filters/filter_internal.h"
|
||||
#include "filters/user_filters.h"
|
||||
#include "options/m_option.h"
|
||||
#include "af.h"
|
||||
#include "audio/audio_buffer.h"
|
||||
#include "audio/chmap_sel.h"
|
||||
#include "audio/fmt-conversion.h"
|
||||
|
||||
|
||||
#define AC3_MAX_CHANNELS 6
|
||||
@@ -52,89 +49,173 @@ const uint16_t ac3_bitrate_tab[19] = {
|
||||
160, 192, 224, 256, 320, 384, 448, 512, 576, 640
|
||||
};
|
||||
|
||||
struct f_opts {
|
||||
int add_iec61937_header;
|
||||
int bit_rate;
|
||||
int min_channel_num;
|
||||
char *encoder;
|
||||
char **avopts;
|
||||
};
|
||||
|
||||
struct priv {
|
||||
struct f_opts *opts;
|
||||
|
||||
struct mp_pin *in_pin;
|
||||
struct mp_aframe *cur_format;
|
||||
struct mp_aframe *in_frame;
|
||||
struct mp_aframe_pool *out_pool;
|
||||
|
||||
// Data for specific instances of this filter
|
||||
typedef struct af_ac3enc_s {
|
||||
struct AVCodec *lavc_acodec;
|
||||
struct AVCodecContext *lavc_actx;
|
||||
int bit_rate;
|
||||
struct mp_audio *input; // frame passed to libavcodec
|
||||
struct mp_audio *pending; // unconsumed input data
|
||||
int in_samples; // samples of input per AC3 frame
|
||||
int out_samples; // upper bound on encoded output per AC3 frame
|
||||
};
|
||||
int64_t encoder_buffered;
|
||||
|
||||
static bool reinit(struct mp_filter *f)
|
||||
int cfg_add_iec61937_header;
|
||||
int cfg_bit_rate;
|
||||
int cfg_min_channel_num;
|
||||
char *cfg_encoder;
|
||||
char **cfg_avopts;
|
||||
} af_ac3enc_t;
|
||||
|
||||
// fmt carries the input format. Change it to the best next-possible format
|
||||
// the encoder likely accepts.
|
||||
static void select_encode_format(AVCodecContext *c, struct mp_audio *fmt)
|
||||
{
|
||||
struct priv *s = f->priv;
|
||||
int formats[AF_FORMAT_COUNT];
|
||||
af_get_best_sample_formats(fmt->format, formats);
|
||||
|
||||
mp_aframe_reset(s->cur_format);
|
||||
for (int n = 0; formats[n]; n++) {
|
||||
const enum AVSampleFormat *lf = c->codec->sample_fmts;
|
||||
for (int i = 0; lf && lf[i] != AV_SAMPLE_FMT_NONE; i++) {
|
||||
int mpfmt = af_from_avformat(lf[i]);
|
||||
if (mpfmt && mpfmt == formats[n]) {
|
||||
mp_audio_set_format(fmt, mpfmt);
|
||||
goto done_fmt;
|
||||
}
|
||||
}
|
||||
}
|
||||
done_fmt: ;
|
||||
|
||||
int rate =
|
||||
af_select_best_samplerate(fmt->rate, c->codec->supported_samplerates);
|
||||
if (rate > 0)
|
||||
fmt->rate = rate;
|
||||
|
||||
struct mp_chmap_sel sel = {0};
|
||||
const uint64_t *lch = c->codec->channel_layouts;
|
||||
for (int n = 0; lch && lch[n]; n++) {
|
||||
struct mp_chmap chmap = {0};
|
||||
mp_chmap_from_lavc(&chmap, lch[n]);
|
||||
mp_chmap_sel_add_map(&sel, &chmap);
|
||||
}
|
||||
struct mp_chmap res = fmt->channels;
|
||||
mp_chmap_sel_adjust(&sel, &res);
|
||||
if (!mp_chmap_is_empty(&res))
|
||||
mp_audio_set_channels(fmt, &res);
|
||||
}
|
||||
|
||||
// Initialization and runtime control
|
||||
static int control(struct af_instance *af, int cmd, void *arg)
|
||||
{
|
||||
af_ac3enc_t *s = af->priv;
|
||||
static const int default_bit_rate[AC3_MAX_CHANNELS+1] = \
|
||||
{0, 96000, 192000, 256000, 384000, 448000, 448000};
|
||||
|
||||
if (s->opts->add_iec61937_header) {
|
||||
s->out_samples = AC3_FRAME_SIZE;
|
||||
} else {
|
||||
s->out_samples = AC3_MAX_CODED_FRAME_SIZE /
|
||||
mp_aframe_get_sstride(s->in_frame);
|
||||
switch (cmd){
|
||||
case AF_CONTROL_REINIT: {
|
||||
struct mp_audio *in = arg;
|
||||
struct mp_audio orig_in = *in;
|
||||
|
||||
if (!af_fmt_is_pcm(in->format) || in->nch < s->cfg_min_channel_num)
|
||||
return AF_DETACH;
|
||||
|
||||
// At least currently, the AC3 encoder doesn't export sample rates.
|
||||
in->rate = 48000;
|
||||
select_encode_format(s->lavc_actx, in);
|
||||
|
||||
af->data->rate = in->rate;
|
||||
mp_audio_set_format(af->data, AF_FORMAT_S_AC3);
|
||||
mp_audio_set_num_channels(af->data, 2);
|
||||
|
||||
if (!mp_audio_config_equals(in, &orig_in))
|
||||
return AF_FALSE;
|
||||
|
||||
if (s->cfg_add_iec61937_header) {
|
||||
s->out_samples = AC3_FRAME_SIZE;
|
||||
} else {
|
||||
s->out_samples = AC3_MAX_CODED_FRAME_SIZE / af->data->sstride;
|
||||
}
|
||||
|
||||
mp_audio_copy_config(s->input, in);
|
||||
|
||||
talloc_free(s->pending);
|
||||
s->pending = NULL;
|
||||
|
||||
MP_DBG(af, "reinit: %d, %d, %d.\n", in->nch, in->rate, s->in_samples);
|
||||
|
||||
int bit_rate = s->bit_rate ? s->bit_rate : default_bit_rate[in->nch];
|
||||
|
||||
if (s->lavc_actx->channels != in->nch ||
|
||||
s->lavc_actx->sample_rate != in->rate ||
|
||||
s->lavc_actx->bit_rate != bit_rate)
|
||||
{
|
||||
avcodec_close(s->lavc_actx);
|
||||
|
||||
// Put sample parameters
|
||||
s->lavc_actx->sample_fmt = af_to_avformat(in->format);
|
||||
s->lavc_actx->channels = in->nch;
|
||||
s->lavc_actx->channel_layout = mp_chmap_to_lavc(&in->channels);
|
||||
s->lavc_actx->sample_rate = in->rate;
|
||||
s->lavc_actx->bit_rate = bit_rate;
|
||||
|
||||
if (avcodec_open2(s->lavc_actx, s->lavc_acodec, NULL) < 0) {
|
||||
MP_ERR(af, "Couldn't open codec %s, br=%d.\n", "ac3", bit_rate);
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
if (s->lavc_actx->frame_size < 1) {
|
||||
MP_ERR(af, "encoder didn't specify input frame size\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
}
|
||||
s->in_samples = s->lavc_actx->frame_size;
|
||||
mp_audio_realloc(s->input, s->in_samples);
|
||||
s->input->samples = 0;
|
||||
s->encoder_buffered = 0;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
int format = mp_aframe_get_format(s->in_frame);
|
||||
int rate = mp_aframe_get_rate(s->in_frame);
|
||||
struct mp_chmap chmap = {0};
|
||||
mp_aframe_get_chmap(s->in_frame, &chmap);
|
||||
|
||||
int bit_rate = s->bit_rate;
|
||||
if (!bit_rate && chmap.num < AC3_MAX_CHANNELS + 1)
|
||||
bit_rate = default_bit_rate[chmap.num];
|
||||
|
||||
avcodec_close(s->lavc_actx);
|
||||
|
||||
// Put sample parameters
|
||||
s->lavc_actx->sample_fmt = af_to_avformat(format);
|
||||
s->lavc_actx->channels = chmap.num;
|
||||
s->lavc_actx->channel_layout = mp_chmap_to_lavc(&chmap);
|
||||
s->lavc_actx->sample_rate = rate;
|
||||
s->lavc_actx->bit_rate = bit_rate;
|
||||
|
||||
if (avcodec_open2(s->lavc_actx, s->lavc_acodec, NULL) < 0) {
|
||||
MP_ERR(f, "Couldn't open codec %s, br=%d.\n", "ac3", bit_rate);
|
||||
return false;
|
||||
case AF_CONTROL_RESET:
|
||||
if (avcodec_is_open(s->lavc_actx))
|
||||
avcodec_flush_buffers(s->lavc_actx);
|
||||
talloc_free(s->pending);
|
||||
s->pending = NULL;
|
||||
s->input->samples = 0;
|
||||
s->encoder_buffered = 0;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
if (s->lavc_actx->frame_size < 1) {
|
||||
MP_ERR(f, "encoder didn't specify input frame size\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
mp_aframe_config_copy(s->cur_format, s->in_frame);
|
||||
return true;
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static void reset(struct mp_filter *f)
|
||||
// Deallocate memory
|
||||
static void uninit(struct af_instance* af)
|
||||
{
|
||||
struct priv *s = f->priv;
|
||||
af_ac3enc_t *s = af->priv;
|
||||
|
||||
TA_FREEP(&s->in_frame);
|
||||
if (s) {
|
||||
avcodec_free_context(&s->lavc_actx);
|
||||
talloc_free(s->pending);
|
||||
}
|
||||
}
|
||||
|
||||
static void destroy(struct mp_filter *f)
|
||||
static void update_delay(struct af_instance *af)
|
||||
{
|
||||
struct priv *s = f->priv;
|
||||
af_ac3enc_t *s = af->priv;
|
||||
af->delay = ((s->pending ? s->pending->samples : 0) + s->input->samples +
|
||||
s->encoder_buffered) / (double)s->input->rate;
|
||||
}
|
||||
|
||||
reset(f);
|
||||
avcodec_free_context(&s->lavc_actx);
|
||||
static int filter_frame(struct af_instance *af, struct mp_audio *audio)
|
||||
{
|
||||
af_ac3enc_t *s = af->priv;
|
||||
|
||||
// filter_output must have been called until no output was produced.
|
||||
if (s->pending && s->pending->samples)
|
||||
MP_ERR(af, "broken data flow\n");
|
||||
|
||||
talloc_free(s->pending);
|
||||
s->pending = audio;
|
||||
update_delay(af);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void swap_16(uint16_t *ptr, size_t size)
|
||||
@@ -143,84 +224,105 @@ static void swap_16(uint16_t *ptr, size_t size)
|
||||
ptr[n] = av_bswap16(ptr[n]);
|
||||
}
|
||||
|
||||
static void process(struct mp_filter *f)
|
||||
// Copy data from input frame to encode frame (because libavcodec wants a full
|
||||
// AC3 frame for encoding, while filter input frames can be smaller or larger).
|
||||
// Return true if the frame is complete.
|
||||
static bool fill_buffer(struct af_instance *af)
|
||||
{
|
||||
struct priv *s = f->priv;
|
||||
af_ac3enc_t *s = af->priv;
|
||||
|
||||
if (!mp_pin_in_needs_data(f->ppins[1]))
|
||||
return;
|
||||
af->delay = 0;
|
||||
|
||||
if (s->pending) {
|
||||
if (!mp_audio_is_writeable(s->input))
|
||||
assert(s->input->samples == 0); // we can't have sent a partial frame
|
||||
mp_audio_realloc_min(s->input, s->in_samples);
|
||||
int copy = MPMIN(s->in_samples - s->input->samples, s->pending->samples);
|
||||
s->input->samples += copy;
|
||||
mp_audio_copy(s->input, s->input->samples - copy, s->pending, 0, copy);
|
||||
mp_audio_skip_samples(s->pending, copy);
|
||||
}
|
||||
update_delay(af);
|
||||
return s->input->samples >= s->in_samples;
|
||||
}
|
||||
|
||||
// Return <0 on error, 0 on need more input, 1 on success (and *frame set).
|
||||
// To actually advance the read pointer, set s->input->samples=0 afterwards.
|
||||
static int read_input_frame(struct af_instance *af, AVFrame *frame)
|
||||
{
|
||||
af_ac3enc_t *s = af->priv;
|
||||
if (!fill_buffer(af))
|
||||
return 0; // need more input
|
||||
|
||||
if (mp_audio_to_avframe(s->input, frame) < 0)
|
||||
return -1;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int filter_out(struct af_instance *af)
|
||||
{
|
||||
af_ac3enc_t *s = af->priv;
|
||||
|
||||
if (!s->pending)
|
||||
return 0;
|
||||
|
||||
AVFrame *frame = av_frame_alloc();
|
||||
if (!frame) {
|
||||
MP_FATAL(af, "Could not allocate memory \n");
|
||||
return -1;
|
||||
}
|
||||
int err = -1;
|
||||
|
||||
bool err = true;
|
||||
struct mp_aframe *out = NULL;
|
||||
AVPacket pkt = {0};
|
||||
av_init_packet(&pkt);
|
||||
|
||||
// Send input as long as it wants.
|
||||
while (1) {
|
||||
if (avcodec_is_open(s->lavc_actx)) {
|
||||
int lavc_ret = avcodec_receive_packet(s->lavc_actx, &pkt);
|
||||
if (lavc_ret >= 0)
|
||||
break;
|
||||
if (lavc_ret < 0 && lavc_ret != AVERROR(EAGAIN)) {
|
||||
MP_FATAL(f, "Encode failed (receive).\n");
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
AVFrame *frame = NULL;
|
||||
struct mp_frame input = mp_pin_out_read(s->in_pin);
|
||||
// The following code assumes no sample data buffering in the encoder.
|
||||
if (input.type == MP_FRAME_EOF) {
|
||||
mp_pin_in_write(f->ppins[1], input);
|
||||
return;
|
||||
} else if (input.type == MP_FRAME_AUDIO) {
|
||||
TA_FREEP(&s->in_frame);
|
||||
s->in_frame = input.data;
|
||||
frame = mp_frame_to_av(input, NULL);
|
||||
if (!frame)
|
||||
goto done;
|
||||
if (mp_aframe_get_channels(s->in_frame) < s->opts->min_channel_num) {
|
||||
// Just pass it through.
|
||||
s->in_frame = NULL;
|
||||
mp_pin_in_write(f->ppins[1], input);
|
||||
return;
|
||||
}
|
||||
if (!mp_aframe_config_equals(s->in_frame, s->cur_format)) {
|
||||
if (!reinit(f))
|
||||
goto done;
|
||||
}
|
||||
} else if (input.type) {
|
||||
err = read_input_frame(af, frame);
|
||||
if (err < 0)
|
||||
goto done;
|
||||
} else {
|
||||
return; // no data yet
|
||||
}
|
||||
if (err == 0)
|
||||
break;
|
||||
err = -1;
|
||||
int lavc_ret = avcodec_send_frame(s->lavc_actx, frame);
|
||||
av_frame_free(&frame);
|
||||
if (lavc_ret < 0 && lavc_ret != AVERROR(EAGAIN)) {
|
||||
MP_FATAL(f, "Encode failed (send).\n");
|
||||
// On EAGAIN, we're supposed to read remaining output.
|
||||
if (lavc_ret == AVERROR(EAGAIN))
|
||||
break;
|
||||
if (lavc_ret < 0) {
|
||||
MP_FATAL(af, "Encode failed.\n");
|
||||
goto done;
|
||||
}
|
||||
s->encoder_buffered += s->input->samples;
|
||||
s->input->samples = 0;
|
||||
}
|
||||
int lavc_ret = avcodec_receive_packet(s->lavc_actx, &pkt);
|
||||
if (lavc_ret == AVERROR(EAGAIN)) {
|
||||
// Need to buffer more input.
|
||||
err = 0;
|
||||
goto done;
|
||||
}
|
||||
if (lavc_ret < 0) {
|
||||
MP_FATAL(af, "Encode failed.\n");
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (!s->in_frame)
|
||||
MP_DBG(af, "avcodec_encode_audio got %d, pending %d.\n",
|
||||
pkt.size, s->pending->samples + s->input->samples);
|
||||
|
||||
s->encoder_buffered -= AC3_FRAME_SIZE;
|
||||
|
||||
struct mp_audio *out =
|
||||
mp_audio_pool_get(af->out_pool, af->data, s->out_samples);
|
||||
if (!out)
|
||||
goto done;
|
||||
|
||||
out = mp_aframe_create();
|
||||
mp_aframe_set_format(out, AF_FORMAT_S_AC3);
|
||||
mp_aframe_set_chmap(out, &(struct mp_chmap)MP_CHMAP_INIT_STEREO);
|
||||
mp_aframe_set_rate(out, 48000);
|
||||
|
||||
if (mp_aframe_pool_allocate(s->out_pool, out, s->out_samples) < 0)
|
||||
goto done;
|
||||
|
||||
int sstride = mp_aframe_get_sstride(out);
|
||||
|
||||
mp_aframe_copy_attributes(out, s->in_frame);
|
||||
mp_audio_copy_attributes(out, s->pending);
|
||||
|
||||
int frame_size = pkt.size;
|
||||
int header_len = 0;
|
||||
char hdr[8];
|
||||
|
||||
if (s->opts->add_iec61937_header && pkt.size > 5) {
|
||||
if (s->cfg_add_iec61937_header && pkt.size > 5) {
|
||||
int bsmod = pkt.data[5] & 0x7;
|
||||
int len = frame_size;
|
||||
|
||||
@@ -234,69 +336,48 @@ static void process(struct mp_filter *f)
|
||||
AV_WL16(hdr + 6, len << 3); // number of bits in payload
|
||||
}
|
||||
|
||||
if (frame_size > s->out_samples * sstride)
|
||||
if (frame_size > out->samples * out->sstride)
|
||||
abort();
|
||||
|
||||
uint8_t **planes = mp_aframe_get_data_rw(out);
|
||||
if (!planes)
|
||||
goto done;
|
||||
char *buf = planes[0];
|
||||
char *buf = (char *)out->planes[0];
|
||||
memcpy(buf, hdr, header_len);
|
||||
memcpy(buf + header_len, pkt.data, pkt.size);
|
||||
memset(buf + header_len + pkt.size, 0,
|
||||
frame_size - (header_len + pkt.size));
|
||||
swap_16((uint16_t *)(buf + header_len), pkt.size / 2);
|
||||
mp_aframe_set_size(out, frame_size / sstride);
|
||||
mp_pin_in_write(f->ppins[1], MAKE_FRAME(MP_FRAME_AUDIO, out));
|
||||
out = NULL;
|
||||
out->samples = frame_size / out->sstride;
|
||||
af_add_output_frame(af, out);
|
||||
|
||||
err = 0;
|
||||
done:
|
||||
av_packet_unref(&pkt);
|
||||
talloc_free(out);
|
||||
if (err)
|
||||
mp_filter_internal_mark_failed(f);
|
||||
av_frame_free(&frame);
|
||||
update_delay(af);
|
||||
return err;
|
||||
}
|
||||
|
||||
static const struct mp_filter_info af_lavcac3enc_filter = {
|
||||
.name = "lavcac3enc",
|
||||
.priv_size = sizeof(struct priv),
|
||||
.process = process,
|
||||
.reset = reset,
|
||||
.destroy = destroy,
|
||||
};
|
||||
static int af_open(struct af_instance* af){
|
||||
|
||||
static struct mp_filter *af_lavcac3enc_create(struct mp_filter *parent,
|
||||
void *options)
|
||||
{
|
||||
struct mp_filter *f = mp_filter_create(parent, &af_lavcac3enc_filter);
|
||||
if (!f) {
|
||||
talloc_free(options);
|
||||
return NULL;
|
||||
}
|
||||
af_ac3enc_t *s = af->priv;
|
||||
af->control=control;
|
||||
af->uninit=uninit;
|
||||
af->filter_frame = filter_frame;
|
||||
af->filter_out = filter_out;
|
||||
|
||||
mp_filter_add_pin(f, MP_PIN_IN, "in");
|
||||
mp_filter_add_pin(f, MP_PIN_OUT, "out");
|
||||
|
||||
struct priv *s = f->priv;
|
||||
s->opts = talloc_steal(s, options);
|
||||
s->cur_format = talloc_steal(s, mp_aframe_create());
|
||||
s->out_pool = mp_aframe_pool_create(s);
|
||||
|
||||
s->lavc_acodec = avcodec_find_encoder_by_name(s->opts->encoder);
|
||||
s->lavc_acodec = avcodec_find_encoder_by_name(s->cfg_encoder);
|
||||
if (!s->lavc_acodec) {
|
||||
MP_ERR(f, "Couldn't find encoder %s.\n", s->opts->encoder);
|
||||
goto error;
|
||||
MP_ERR(af, "Couldn't find encoder %s.\n", s->cfg_encoder);
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
s->lavc_actx = avcodec_alloc_context3(s->lavc_acodec);
|
||||
if (!s->lavc_actx) {
|
||||
MP_ERR(f, "Audio LAVC, couldn't allocate context!\n");
|
||||
goto error;
|
||||
MP_ERR(af, "Audio LAVC, couldn't allocate context!\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
if (mp_set_avopts(f->log, s->lavc_actx, s->opts->avopts) < 0)
|
||||
goto error;
|
||||
if (mp_set_avopts(af->log, s->lavc_actx, s->cfg_avopts) < 0)
|
||||
return AF_ERROR;
|
||||
|
||||
// For this one, we require the decoder to expert lists of all supported
|
||||
// parameters. (Not all decoders do that, but the ones we're interested
|
||||
@@ -304,85 +385,50 @@ static struct mp_filter *af_lavcac3enc_create(struct mp_filter *parent,
|
||||
if (!s->lavc_acodec->sample_fmts ||
|
||||
!s->lavc_acodec->channel_layouts)
|
||||
{
|
||||
MP_ERR(f, "Audio encoder doesn't list supported parameters.\n");
|
||||
goto error;
|
||||
MP_ERR(af, "Audio encoder doesn't list supported parameters.\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
if (s->opts->bit_rate) {
|
||||
s->input = talloc_zero(s, struct mp_audio);
|
||||
|
||||
if (s->cfg_bit_rate) {
|
||||
int i;
|
||||
for (i = 0; i < 19; i++) {
|
||||
if (ac3_bitrate_tab[i] == s->opts->bit_rate) {
|
||||
if (ac3_bitrate_tab[i] == s->cfg_bit_rate) {
|
||||
s->bit_rate = ac3_bitrate_tab[i] * 1000;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i >= 19) {
|
||||
MP_WARN(f, "unable set unsupported bitrate %d, using default "
|
||||
MP_WARN(af, "unable set unsupported bitrate %d, use default "
|
||||
"bitrate (check manpage to see supported bitrates).\n",
|
||||
s->opts->bit_rate);
|
||||
s->cfg_bit_rate);
|
||||
}
|
||||
}
|
||||
|
||||
struct mp_autoconvert *conv = mp_autoconvert_create(f);
|
||||
if (!conv)
|
||||
abort();
|
||||
|
||||
const enum AVSampleFormat *lf = s->lavc_acodec->sample_fmts;
|
||||
for (int i = 0; lf && lf[i] != AV_SAMPLE_FMT_NONE; i++) {
|
||||
int mpfmt = af_from_avformat(lf[i]);
|
||||
if (mpfmt)
|
||||
mp_autoconvert_add_afmt(conv, mpfmt);
|
||||
}
|
||||
|
||||
const uint64_t *lch = s->lavc_acodec->channel_layouts;
|
||||
for (int n = 0; lch && lch[n]; n++) {
|
||||
struct mp_chmap chmap = {0};
|
||||
mp_chmap_from_lavc(&chmap, lch[n]);
|
||||
if (mp_chmap_is_valid(&chmap))
|
||||
mp_autoconvert_add_chmap(conv, &chmap);
|
||||
}
|
||||
|
||||
// At least currently, the AC3 encoder doesn't export sample rates.
|
||||
mp_autoconvert_add_srate(conv, 48000);
|
||||
|
||||
mp_pin_connect(conv->f->pins[0], f->ppins[0]);
|
||||
|
||||
struct mp_filter *fs = mp_fixed_aframe_size_create(f, AC3_FRAME_SIZE, true);
|
||||
if (!fs)
|
||||
abort();
|
||||
|
||||
mp_pin_connect(fs->pins[0], conv->f->pins[1]);
|
||||
s->in_pin = fs->pins[1];
|
||||
|
||||
return f;
|
||||
|
||||
error:
|
||||
talloc_free(f);
|
||||
return NULL;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct f_opts
|
||||
#define OPT_BASE_STRUCT struct af_ac3enc_s
|
||||
|
||||
const struct mp_user_filter_entry af_lavcac3enc = {
|
||||
.desc = {
|
||||
.description = "runtime encode to ac3 using libavcodec",
|
||||
.name = "lavcac3enc",
|
||||
.priv_size = sizeof(OPT_BASE_STRUCT),
|
||||
.priv_defaults = &(const OPT_BASE_STRUCT) {
|
||||
.add_iec61937_header = 1,
|
||||
.bit_rate = 640,
|
||||
.min_channel_num = 3,
|
||||
.encoder = "ac3",
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
{"tospdif", OPT_FLAG(add_iec61937_header)},
|
||||
{"bitrate", OPT_CHOICE(bit_rate,
|
||||
{"auto", 0}, {"default", 0}), M_RANGE(32, 640)},
|
||||
{"minch", OPT_INT(min_channel_num), M_RANGE(2, 6)},
|
||||
{"encoder", OPT_STRING(encoder)},
|
||||
{"o", OPT_KEYVALUELIST(avopts)},
|
||||
{0}
|
||||
},
|
||||
const struct af_info af_info_lavcac3enc = {
|
||||
.info = "runtime encode to ac3 using libavcodec",
|
||||
.name = "lavcac3enc",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(struct af_ac3enc_s),
|
||||
.priv_defaults = &(const struct af_ac3enc_s){
|
||||
.cfg_add_iec61937_header = 1,
|
||||
.cfg_bit_rate = 640,
|
||||
.cfg_min_channel_num = 3,
|
||||
.cfg_encoder = "ac3",
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_FLAG("tospdif", cfg_add_iec61937_header, 0),
|
||||
OPT_CHOICE_OR_INT("bitrate", cfg_bit_rate, 0, 32, 640,
|
||||
({"auto", 0}, {"default", 0})),
|
||||
OPT_INTRANGE("minch", cfg_min_channel_num, 0, 2, 6),
|
||||
OPT_STRING("encoder", cfg_encoder, 0),
|
||||
OPT_KEYVALUELIST("o", cfg_avopts, 0),
|
||||
{0}
|
||||
},
|
||||
.create = af_lavcac3enc_create,
|
||||
};
|
||||
@@ -0,0 +1,409 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* Filter graph creation code taken from FFmpeg ffplay.c (LGPL 2.1 or later)
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <libavutil/avstring.h>
|
||||
#include <libavutil/mem.h>
|
||||
#include <libavutil/mathematics.h>
|
||||
#include <libavutil/rational.h>
|
||||
#include <libavutil/samplefmt.h>
|
||||
#include <libavutil/time.h>
|
||||
#include <libavutil/opt.h>
|
||||
#include <libavfilter/avfilter.h>
|
||||
#include <libavfilter/buffersink.h>
|
||||
#include <libavfilter/buffersrc.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "audio/format.h"
|
||||
#include "audio/fmt-conversion.h"
|
||||
#include "af.h"
|
||||
|
||||
#include "common/av_common.h"
|
||||
#include "common/tags.h"
|
||||
|
||||
#include "options/m_option.h"
|
||||
|
||||
// FFmpeg and Libav have slightly different APIs, just enough to cause us
|
||||
// unnecessary pain. <Expletive deleted.>
|
||||
#if LIBAVFILTER_VERSION_MICRO < 100
|
||||
#define graph_parse(graph, filters, inputs, outputs, log_ctx) \
|
||||
avfilter_graph_parse(graph, filters, inputs, outputs, log_ctx)
|
||||
#define avfilter_graph_send_command(a, b, c, d, e, f, g) -1
|
||||
#else
|
||||
#define graph_parse(graph, filters, inputs, outputs, log_ctx) \
|
||||
avfilter_graph_parse_ptr(graph, filters, &(inputs), &(outputs), log_ctx)
|
||||
#endif
|
||||
|
||||
struct priv {
|
||||
// Single filter bridge, instead of a graph.
|
||||
bool is_bridge;
|
||||
|
||||
AVFilterGraph *graph;
|
||||
AVFilterContext *in;
|
||||
AVFilterContext *out;
|
||||
|
||||
int64_t samples_in;
|
||||
|
||||
AVRational timebase_out;
|
||||
|
||||
bool eof;
|
||||
|
||||
struct mp_tags *metadata;
|
||||
|
||||
// options
|
||||
char *cfg_graph;
|
||||
char **cfg_avopts;
|
||||
char *cfg_filter_name;
|
||||
char **cfg_filter_opts;
|
||||
};
|
||||
|
||||
static void destroy_graph(struct af_instance *af)
|
||||
{
|
||||
struct priv *p = af->priv;
|
||||
avfilter_graph_free(&p->graph);
|
||||
p->in = p->out = NULL;
|
||||
p->samples_in = 0;
|
||||
p->eof = false;
|
||||
}
|
||||
|
||||
static bool recreate_graph(struct af_instance *af, struct mp_audio *config)
|
||||
{
|
||||
void *tmp = talloc_new(NULL);
|
||||
struct priv *p = af->priv;
|
||||
AVFilterContext *in = NULL, *out = NULL;
|
||||
|
||||
if (!p->is_bridge && bstr0(p->cfg_graph).len == 0) {
|
||||
MP_FATAL(af, "lavfi: no filter graph set\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
destroy_graph(af);
|
||||
|
||||
AVFilterGraph *graph = avfilter_graph_alloc();
|
||||
if (!graph)
|
||||
goto error;
|
||||
|
||||
if (mp_set_avopts(af->log, graph, p->cfg_avopts) < 0)
|
||||
goto error;
|
||||
|
||||
AVFilterInOut *outputs = avfilter_inout_alloc();
|
||||
AVFilterInOut *inputs = avfilter_inout_alloc();
|
||||
if (!outputs || !inputs)
|
||||
goto error;
|
||||
|
||||
char *src_args = talloc_asprintf(tmp,
|
||||
"sample_rate=%d:sample_fmt=%s:time_base=%d/%d:"
|
||||
"channel_layout=0x%"PRIx64, config->rate,
|
||||
av_get_sample_fmt_name(af_to_avformat(config->format)),
|
||||
1, config->rate, mp_chmap_to_lavc(&config->channels));
|
||||
|
||||
if (avfilter_graph_create_filter(&in, avfilter_get_by_name("abuffer"),
|
||||
"src", src_args, NULL, graph) < 0)
|
||||
goto error;
|
||||
|
||||
if (avfilter_graph_create_filter(&out, avfilter_get_by_name("abuffersink"),
|
||||
"out", NULL, NULL, graph) < 0)
|
||||
goto error;
|
||||
|
||||
if (p->is_bridge) {
|
||||
AVFilterContext *filter = avfilter_graph_alloc_filter(graph,
|
||||
avfilter_get_by_name(p->cfg_filter_name), "filter");
|
||||
if (!filter)
|
||||
goto error;
|
||||
|
||||
if (mp_set_avopts(af->log, filter->priv, p->cfg_filter_opts) < 0)
|
||||
goto error;
|
||||
|
||||
if (avfilter_init_str(filter, NULL) < 0)
|
||||
goto error;
|
||||
|
||||
// Yep, we have to manually link those filters.
|
||||
if (filter->nb_inputs != 1 ||
|
||||
avfilter_pad_get_type(filter->input_pads, 0) != AVMEDIA_TYPE_AUDIO ||
|
||||
filter->nb_outputs != 1 ||
|
||||
avfilter_pad_get_type(filter->output_pads, 0) != AVMEDIA_TYPE_AUDIO)
|
||||
{
|
||||
MP_ERR(af, "The filter is required to have 1 audio input pad and "
|
||||
"1 audio output pad.\n");
|
||||
goto error;
|
||||
}
|
||||
if (avfilter_link(in, 0, filter, 0) < 0 ||
|
||||
avfilter_link(filter, 0, out, 0) < 0)
|
||||
{
|
||||
MP_ERR(af, "Failed to link filter.\n");
|
||||
goto error;
|
||||
}
|
||||
} else {
|
||||
MP_VERBOSE(af, "lavfi: create graph: '%s'\n", p->cfg_graph);
|
||||
|
||||
outputs->name = av_strdup("in");
|
||||
outputs->filter_ctx = in;
|
||||
|
||||
inputs->name = av_strdup("out");
|
||||
inputs->filter_ctx = out;
|
||||
|
||||
if (graph_parse(graph, p->cfg_graph, inputs, outputs, NULL) < 0)
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (avfilter_graph_config(graph, NULL) < 0)
|
||||
goto error;
|
||||
|
||||
p->in = in;
|
||||
p->out = out;
|
||||
p->graph = graph;
|
||||
|
||||
assert(out->nb_inputs == 1);
|
||||
assert(in->nb_outputs == 1);
|
||||
|
||||
talloc_free(tmp);
|
||||
return true;
|
||||
|
||||
error:
|
||||
MP_FATAL(af, "Can't configure libavfilter graph.\n");
|
||||
avfilter_graph_free(&graph);
|
||||
talloc_free(tmp);
|
||||
return false;
|
||||
}
|
||||
|
||||
static void reset(struct af_instance *af)
|
||||
{
|
||||
if (!recreate_graph(af, &af->fmt_in))
|
||||
MP_FATAL(af, "Can't recreate libavfilter filter after a seek reset.\n");
|
||||
}
|
||||
|
||||
static int control(struct af_instance *af, int cmd, void *arg)
|
||||
{
|
||||
struct priv *p = af->priv;
|
||||
|
||||
switch (cmd) {
|
||||
case AF_CONTROL_REINIT: {
|
||||
struct mp_audio *in = arg;
|
||||
struct mp_audio orig_in = *in;
|
||||
struct mp_audio *out = af->data;
|
||||
|
||||
if (af_to_avformat(in->format) == AV_SAMPLE_FMT_NONE)
|
||||
mp_audio_set_format(in, AF_FORMAT_FLOAT);
|
||||
|
||||
// Removing this requires fixing AVFrame.data vs. AVFrame.extended_data
|
||||
if (in->channels.num > AV_NUM_DATA_POINTERS)
|
||||
return AF_ERROR;
|
||||
|
||||
if (!mp_chmap_is_lavc(&in->channels))
|
||||
mp_chmap_reorder_to_lavc(&in->channels); // will always work
|
||||
|
||||
if (!recreate_graph(af, in))
|
||||
return AF_ERROR;
|
||||
|
||||
AVFilterLink *l_out = p->out->inputs[0];
|
||||
|
||||
out->rate = l_out->sample_rate;
|
||||
|
||||
mp_audio_set_format(out, af_from_avformat(l_out->format));
|
||||
|
||||
struct mp_chmap out_cm;
|
||||
mp_chmap_from_lavc(&out_cm, l_out->channel_layout);
|
||||
mp_audio_set_channels(out, &out_cm);
|
||||
|
||||
if (!mp_audio_config_valid(out) || out->channels.num > AV_NUM_DATA_POINTERS)
|
||||
return AF_ERROR;
|
||||
|
||||
p->timebase_out = l_out->time_base;
|
||||
|
||||
return mp_audio_config_equals(in, &orig_in) ? AF_OK : AF_FALSE;
|
||||
}
|
||||
case AF_CONTROL_COMMAND: {
|
||||
if (!p->graph)
|
||||
break;
|
||||
char **args = arg;
|
||||
return avfilter_graph_send_command(p->graph, "all",
|
||||
args[0], args[1], &(char){0}, 0, 0)
|
||||
>= 0 ? CONTROL_OK : CONTROL_ERROR;
|
||||
}
|
||||
case AF_CONTROL_GET_METADATA:
|
||||
if (p->metadata) {
|
||||
*(struct mp_tags *)arg = *p->metadata;
|
||||
return CONTROL_OK;
|
||||
}
|
||||
return CONTROL_NA;
|
||||
case AF_CONTROL_RESET:
|
||||
reset(af);
|
||||
return AF_OK;
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static void get_metadata_from_av_frame(struct af_instance *af, AVFrame *frame)
|
||||
{
|
||||
#if LIBAVUTIL_VERSION_MICRO >= 100
|
||||
struct priv *p = af->priv;
|
||||
if (!p->metadata)
|
||||
p->metadata = talloc_zero(p, struct mp_tags);
|
||||
|
||||
mp_tags_copy_from_av_dictionary(p->metadata, av_frame_get_metadata(frame));
|
||||
#endif
|
||||
}
|
||||
|
||||
static int filter_frame(struct af_instance *af, struct mp_audio *data)
|
||||
{
|
||||
struct priv *p = af->priv;
|
||||
AVFrame *frame = NULL;
|
||||
|
||||
if (p->eof && data)
|
||||
reset(af);
|
||||
|
||||
if (!p->graph)
|
||||
goto error;
|
||||
|
||||
if (!data) {
|
||||
if (p->eof)
|
||||
return 0;
|
||||
p->eof = true;
|
||||
}
|
||||
|
||||
if (data) {
|
||||
frame = mp_audio_to_avframe_and_unref(data);
|
||||
data = NULL;
|
||||
if (!frame)
|
||||
goto error;
|
||||
|
||||
// Timebase is 1/sample_rate
|
||||
frame->pts = p->samples_in;
|
||||
p->samples_in += frame->nb_samples;
|
||||
}
|
||||
|
||||
if (av_buffersrc_add_frame(p->in, frame) < 0)
|
||||
goto error;
|
||||
|
||||
av_frame_free(&frame);
|
||||
talloc_free(data);
|
||||
return 0;
|
||||
error:
|
||||
av_frame_free(&frame);
|
||||
talloc_free(data);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int filter_out(struct af_instance *af)
|
||||
{
|
||||
struct priv *p = af->priv;
|
||||
|
||||
if (!p->graph)
|
||||
goto error;
|
||||
|
||||
AVFrame *frame = av_frame_alloc();
|
||||
if (!frame)
|
||||
goto error;
|
||||
|
||||
int err = av_buffersink_get_frame(p->out, frame);
|
||||
if (err == AVERROR(EAGAIN) || err == AVERROR_EOF) {
|
||||
// Not an error situation - no more output buffers in queue.
|
||||
// AVERROR_EOF means we shouldn't even give the filter more
|
||||
// input, but we don't handle that completely correctly.
|
||||
av_frame_free(&frame);
|
||||
p->eof |= err == AVERROR_EOF;
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct mp_audio *out = mp_audio_from_avframe(frame);
|
||||
if (!out)
|
||||
goto error;
|
||||
|
||||
mp_audio_copy_config(out, af->data);
|
||||
|
||||
if (frame->pts != AV_NOPTS_VALUE) {
|
||||
double in_time = p->samples_in / (double)af->fmt_in.rate;
|
||||
double out_time = frame->pts * av_q2d(p->timebase_out);
|
||||
// Need pts past the last output sample.
|
||||
out_time += out->samples / (double)out->rate;
|
||||
|
||||
af->delay = in_time - out_time;
|
||||
}
|
||||
|
||||
get_metadata_from_av_frame(af, frame);
|
||||
af_add_output_frame(af, out);
|
||||
av_frame_free(&frame);
|
||||
return 0;
|
||||
error:
|
||||
av_frame_free(&frame);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void uninit(struct af_instance *af)
|
||||
{
|
||||
destroy_graph(af);
|
||||
}
|
||||
|
||||
static int af_open(struct af_instance *af)
|
||||
{
|
||||
struct priv *p = af->priv;
|
||||
|
||||
af->control = control;
|
||||
af->uninit = uninit;
|
||||
af->filter_frame = filter_frame;
|
||||
af->filter_out = filter_out;
|
||||
|
||||
if (p->is_bridge) {
|
||||
if (!p->cfg_filter_name) {
|
||||
MP_ERR(af, "Filter name not set!\n");
|
||||
return 0;
|
||||
}
|
||||
if (!avfilter_get_by_name(p->cfg_filter_name)) {
|
||||
MP_ERR(af, "libavfilter filter '%s' not found!\n", p->cfg_filter_name);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
const struct af_info af_info_lavfi = {
|
||||
.info = "libavfilter bridge",
|
||||
.name = "lavfi",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_STRING("graph", cfg_graph, 0),
|
||||
OPT_KEYVALUELIST("o", cfg_avopts, 0),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
|
||||
const struct af_info af_info_lavfi_bridge = {
|
||||
.info = "libavfilter bridge (explicit options)",
|
||||
.name = "lavfi-bridge",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.priv_defaults = &(const struct priv){
|
||||
.is_bridge = true,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_STRING("name", cfg_filter_name, M_OPT_MIN, .min = 1),
|
||||
OPT_KEYVALUELIST("opts", cfg_filter_opts, 0),
|
||||
OPT_STRING("graph", cfg_graph, 0),
|
||||
OPT_KEYVALUELIST("o", cfg_avopts, 0),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,597 @@
|
||||
/*
|
||||
* Copyright (c) 2004 Michael Niedermayer <michaelni@gmx.at>
|
||||
* Copyright (c) 2013 Stefano Pigozzi <stefano.pigozzi@gmail.com>
|
||||
*
|
||||
* Based on Michael Niedermayer's lavcresample.
|
||||
*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
#include <math.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <libavutil/opt.h>
|
||||
#include <libavutil/common.h>
|
||||
#include <libavutil/samplefmt.h>
|
||||
#include <libavutil/channel_layout.h>
|
||||
#include <libavutil/mathematics.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "config.h"
|
||||
|
||||
#define HAVE_LIBSWRESAMPLE HAVE_IS_FFMPEG
|
||||
#define HAVE_LIBAVRESAMPLE HAVE_IS_LIBAV
|
||||
|
||||
#if HAVE_LIBAVRESAMPLE
|
||||
#include <libavresample/avresample.h>
|
||||
#elif HAVE_LIBSWRESAMPLE
|
||||
#include <libswresample/swresample.h>
|
||||
#define AVAudioResampleContext SwrContext
|
||||
#define avresample_alloc_context swr_alloc
|
||||
#define avresample_open swr_init
|
||||
#define avresample_close(x) do { } while(0)
|
||||
#define avresample_free swr_free
|
||||
#define avresample_available(x) 0
|
||||
#define avresample_convert(ctx, out, out_planesize, out_samples, in, in_planesize, in_samples) \
|
||||
swr_convert(ctx, out, out_samples, (const uint8_t**)(in), in_samples)
|
||||
#define avresample_set_channel_mapping swr_set_channel_mapping
|
||||
#define avresample_set_compensation swr_set_compensation
|
||||
#else
|
||||
#error "config.h broken or no resampler found"
|
||||
#endif
|
||||
|
||||
#include "common/av_common.h"
|
||||
#include "common/msg.h"
|
||||
#include "options/m_option.h"
|
||||
#include "audio/filter/af.h"
|
||||
#include "audio/fmt-conversion.h"
|
||||
#include "osdep/endian.h"
|
||||
|
||||
struct af_resample_opts {
|
||||
int filter_size;
|
||||
int phase_shift;
|
||||
int linear;
|
||||
double cutoff;
|
||||
int normalize;
|
||||
};
|
||||
|
||||
struct af_resample {
|
||||
int allow_detach;
|
||||
char **avopts;
|
||||
double playback_speed;
|
||||
bool is_resampling;
|
||||
struct AVAudioResampleContext *avrctx;
|
||||
struct mp_audio avrctx_fmt; // output format of avrctx
|
||||
struct mp_audio pool_fmt; // format used to allocate frames for avrctx output
|
||||
struct mp_audio pre_out_fmt; // format before final conversion (S24)
|
||||
struct AVAudioResampleContext *avrctx_out; // for output channel reordering
|
||||
struct af_resample_opts opts; // opts requested by the user
|
||||
// At least libswresample keeps a pointer around for this:
|
||||
int reorder_in[MP_NUM_CHANNELS];
|
||||
int reorder_out[MP_NUM_CHANNELS];
|
||||
struct mp_audio_pool *reorder_buffer;
|
||||
|
||||
int in_rate_af; // filter input sample rate
|
||||
int in_rate; // actual rate (used by lavr), adjusted for playback speed
|
||||
int in_format;
|
||||
struct mp_chmap in_channels;
|
||||
int out_rate;
|
||||
int out_format;
|
||||
struct mp_chmap out_channels;
|
||||
};
|
||||
|
||||
#if HAVE_LIBAVRESAMPLE
|
||||
static double get_delay(struct af_resample *s)
|
||||
{
|
||||
return avresample_get_delay(s->avrctx) / (double)s->in_rate +
|
||||
avresample_available(s->avrctx) / (double)s->out_rate;
|
||||
}
|
||||
static int get_out_samples(struct af_resample *s, int in_samples)
|
||||
{
|
||||
return avresample_get_out_samples(s->avrctx, in_samples);
|
||||
}
|
||||
#else
|
||||
static double get_delay(struct af_resample *s)
|
||||
{
|
||||
int64_t base = s->in_rate * (int64_t)s->out_rate;
|
||||
return swr_get_delay(s->avrctx, base) / (double)base;
|
||||
}
|
||||
static int get_out_samples(struct af_resample *s, int in_samples)
|
||||
{
|
||||
return swr_get_out_samples(s->avrctx, in_samples);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void close_lavrr(struct af_instance *af)
|
||||
{
|
||||
struct af_resample *s = af->priv;
|
||||
|
||||
if (s->avrctx)
|
||||
avresample_close(s->avrctx);
|
||||
avresample_free(&s->avrctx);
|
||||
if (s->avrctx_out)
|
||||
avresample_close(s->avrctx_out);
|
||||
avresample_free(&s->avrctx_out);
|
||||
}
|
||||
|
||||
static int resample_frame(struct AVAudioResampleContext *r,
|
||||
struct mp_audio *out, struct mp_audio *in)
|
||||
{
|
||||
return avresample_convert(r,
|
||||
out ? (uint8_t **)out->planes : NULL,
|
||||
out ? mp_audio_get_allocated_size(out) : 0,
|
||||
out ? out->samples : 0,
|
||||
in ? (uint8_t **)in->planes : NULL,
|
||||
in ? mp_audio_get_allocated_size(in) : 0,
|
||||
in ? in->samples : 0);
|
||||
}
|
||||
|
||||
static double af_resample_default_cutoff(int filter_size)
|
||||
{
|
||||
return FFMAX(1.0 - 6.5 / (filter_size + 8), 0.80);
|
||||
}
|
||||
|
||||
static int rate_from_speed(int rate, double speed)
|
||||
{
|
||||
return lrint(rate * speed);
|
||||
}
|
||||
|
||||
static struct mp_chmap fudge_pairs[][2] = {
|
||||
{MP_CHMAP2(BL, BR), MP_CHMAP2(SL, SR)},
|
||||
{MP_CHMAP2(SL, SR), MP_CHMAP2(BL, BR)},
|
||||
{MP_CHMAP2(SDL, SDR), MP_CHMAP2(SL, SR)},
|
||||
{MP_CHMAP2(SL, SR), MP_CHMAP2(SDL, SDR)},
|
||||
};
|
||||
|
||||
// Modify out_layout and return the new value. The intention is reducing the
|
||||
// loss libswresample's rematrixing will cause by exchanging similar, but
|
||||
// strictly speaking incompatible channel pairs. For example, 7.1 should be
|
||||
// changed to 7.1(wide) without dropping the SL/SR channels. (We still leave
|
||||
// it to libswresample to create the remix matrix.)
|
||||
static uint64_t fudge_layout_conversion(struct af_instance *af,
|
||||
uint64_t in, uint64_t out)
|
||||
{
|
||||
for (int n = 0; n < MP_ARRAY_SIZE(fudge_pairs); n++) {
|
||||
uint64_t a = mp_chmap_to_lavc(&fudge_pairs[n][0]);
|
||||
uint64_t b = mp_chmap_to_lavc(&fudge_pairs[n][1]);
|
||||
if ((in & a) == a && (in & b) == 0 &&
|
||||
(out & a) == 0 && (out & b) == b)
|
||||
{
|
||||
out = (out & ~b) | a;
|
||||
|
||||
MP_VERBOSE(af, "Fudge: %s -> %s\n",
|
||||
mp_chmap_to_str(&fudge_pairs[n][0]),
|
||||
mp_chmap_to_str(&fudge_pairs[n][1]));
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// mp_chmap_get_reorder() performs:
|
||||
// to->speaker[n] = from->speaker[src[n]]
|
||||
// but libavresample does:
|
||||
// to->speaker[dst[n]] = from->speaker[n]
|
||||
static void transpose_order(int *map, int num)
|
||||
{
|
||||
int nmap[MP_NUM_CHANNELS] = {0};
|
||||
for (int n = 0; n < num; n++) {
|
||||
for (int i = 0; i < num; i++) {
|
||||
if (map[n] == i)
|
||||
nmap[i] = n;
|
||||
}
|
||||
}
|
||||
memcpy(map, nmap, sizeof(nmap));
|
||||
}
|
||||
|
||||
static int configure_lavrr(struct af_instance *af, struct mp_audio *in,
|
||||
struct mp_audio *out, bool verbose)
|
||||
{
|
||||
struct af_resample *s = af->priv;
|
||||
|
||||
close_lavrr(af);
|
||||
|
||||
s->avrctx = avresample_alloc_context();
|
||||
s->avrctx_out = avresample_alloc_context();
|
||||
if (!s->avrctx || !s->avrctx_out)
|
||||
goto error;
|
||||
|
||||
enum AVSampleFormat in_samplefmt = af_to_avformat(in->format);
|
||||
enum AVSampleFormat out_samplefmt = af_to_avformat(out->format);
|
||||
enum AVSampleFormat out_samplefmtp = av_get_planar_sample_fmt(out_samplefmt);
|
||||
|
||||
if (in_samplefmt == AV_SAMPLE_FMT_NONE ||
|
||||
out_samplefmt == AV_SAMPLE_FMT_NONE ||
|
||||
out_samplefmtp == AV_SAMPLE_FMT_NONE)
|
||||
goto error;
|
||||
|
||||
s->out_rate = out->rate;
|
||||
s->in_rate_af = in->rate;
|
||||
s->in_rate = rate_from_speed(in->rate, s->playback_speed);
|
||||
s->out_format = out->format;
|
||||
s->in_format = in->format;
|
||||
s->out_channels= out->channels;
|
||||
s->in_channels = in->channels;
|
||||
|
||||
av_opt_set_int(s->avrctx, "filter_size", s->opts.filter_size, 0);
|
||||
av_opt_set_int(s->avrctx, "phase_shift", s->opts.phase_shift, 0);
|
||||
av_opt_set_int(s->avrctx, "linear_interp", s->opts.linear, 0);
|
||||
|
||||
av_opt_set_double(s->avrctx, "cutoff", s->opts.cutoff, 0);
|
||||
|
||||
int normalize = s->opts.normalize;
|
||||
if (normalize < 0)
|
||||
normalize = af->opts->audio_normalize;
|
||||
#if HAVE_LIBSWRESAMPLE
|
||||
av_opt_set_double(s->avrctx, "rematrix_maxval", normalize ? 1 : 1000, 0);
|
||||
#else
|
||||
av_opt_set_int(s->avrctx, "normalize_mix_level", !!normalize, 0);
|
||||
#endif
|
||||
|
||||
if (mp_set_avopts(af->log, s->avrctx, s->avopts) < 0)
|
||||
goto error;
|
||||
|
||||
struct mp_chmap map_in = in->channels;
|
||||
struct mp_chmap map_out = out->channels;
|
||||
|
||||
// Try not to do any remixing if at least one is "unknown".
|
||||
if (mp_chmap_is_unknown(&map_in) || mp_chmap_is_unknown(&map_out)) {
|
||||
mp_chmap_set_unknown(&map_in, map_in.num);
|
||||
mp_chmap_set_unknown(&map_out, map_out.num);
|
||||
}
|
||||
|
||||
// unchecked: don't take any channel reordering into account
|
||||
uint64_t in_ch_layout = mp_chmap_to_lavc_unchecked(&map_in);
|
||||
uint64_t out_ch_layout = mp_chmap_to_lavc_unchecked(&map_out);
|
||||
|
||||
struct mp_chmap in_lavc, out_lavc;
|
||||
mp_chmap_from_lavc(&in_lavc, in_ch_layout);
|
||||
mp_chmap_from_lavc(&out_lavc, out_ch_layout);
|
||||
|
||||
if (verbose && !mp_chmap_equals(&in_lavc, &out_lavc)) {
|
||||
MP_VERBOSE(af, "Remix: %s -> %s\n", mp_chmap_to_str(&in_lavc),
|
||||
mp_chmap_to_str(&out_lavc));
|
||||
}
|
||||
|
||||
if (in_lavc.num != map_in.num) {
|
||||
// For handling NA channels, we would have to add a planarization step.
|
||||
MP_FATAL(af, "Unsupported channel remapping.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
mp_chmap_get_reorder(s->reorder_in, &map_in, &in_lavc);
|
||||
transpose_order(s->reorder_in, map_in.num);
|
||||
|
||||
if (mp_chmap_equals(&out_lavc, &map_out)) {
|
||||
// No intermediate step required - output new format directly.
|
||||
out_samplefmtp = out_samplefmt;
|
||||
} else {
|
||||
// Verify that we really just reorder and/or insert NA channels.
|
||||
struct mp_chmap withna = out_lavc;
|
||||
mp_chmap_fill_na(&withna, map_out.num);
|
||||
if (withna.num != map_out.num)
|
||||
goto error;
|
||||
}
|
||||
mp_chmap_get_reorder(s->reorder_out, &out_lavc, &map_out);
|
||||
|
||||
s->avrctx_fmt = *out;
|
||||
mp_audio_set_channels(&s->avrctx_fmt, &out_lavc);
|
||||
mp_audio_set_format(&s->avrctx_fmt, af_from_avformat(out_samplefmtp));
|
||||
|
||||
s->pre_out_fmt = *out;
|
||||
|
||||
// If there are NA channels, the final output will have more channels than
|
||||
// the avrctx output. Also, avrctx will output planar (out_samplefmtp was
|
||||
// not overwritten). Allocate the output frame with more channels, so the
|
||||
// NA channels can be trivially added.
|
||||
s->pool_fmt = s->avrctx_fmt;
|
||||
if (map_out.num > out_lavc.num)
|
||||
mp_audio_set_channels(&s->pool_fmt, &map_out);
|
||||
|
||||
out_ch_layout = fudge_layout_conversion(af, in_ch_layout, out_ch_layout);
|
||||
|
||||
// Real conversion; output is input to avrctx_out.
|
||||
av_opt_set_int(s->avrctx, "in_channel_layout", in_ch_layout, 0);
|
||||
av_opt_set_int(s->avrctx, "out_channel_layout", out_ch_layout, 0);
|
||||
av_opt_set_int(s->avrctx, "in_sample_rate", s->in_rate, 0);
|
||||
av_opt_set_int(s->avrctx, "out_sample_rate", s->out_rate, 0);
|
||||
av_opt_set_int(s->avrctx, "in_sample_fmt", in_samplefmt, 0);
|
||||
av_opt_set_int(s->avrctx, "out_sample_fmt", out_samplefmtp, 0);
|
||||
|
||||
// Just needs the correct number of channels for deplanarization.
|
||||
struct mp_chmap fake_chmap;
|
||||
mp_chmap_set_unknown(&fake_chmap, map_out.num);
|
||||
uint64_t fake_out_ch_layout = mp_chmap_to_lavc_unchecked(&fake_chmap);
|
||||
if (!fake_out_ch_layout)
|
||||
goto error;
|
||||
av_opt_set_int(s->avrctx_out, "in_channel_layout", fake_out_ch_layout, 0);
|
||||
av_opt_set_int(s->avrctx_out, "out_channel_layout", fake_out_ch_layout, 0);
|
||||
|
||||
av_opt_set_int(s->avrctx_out, "in_sample_fmt", out_samplefmtp, 0);
|
||||
av_opt_set_int(s->avrctx_out, "out_sample_fmt", out_samplefmt, 0);
|
||||
av_opt_set_int(s->avrctx_out, "in_sample_rate", s->out_rate, 0);
|
||||
av_opt_set_int(s->avrctx_out, "out_sample_rate", s->out_rate, 0);
|
||||
|
||||
// API has weird requirements, quoting avresample.h:
|
||||
// * This function can only be called when the allocated context is not open.
|
||||
// * Also, the input channel layout must have already been set.
|
||||
avresample_set_channel_mapping(s->avrctx, s->reorder_in);
|
||||
|
||||
s->is_resampling = false;
|
||||
|
||||
if (avresample_open(s->avrctx) < 0 || avresample_open(s->avrctx_out) < 0) {
|
||||
MP_ERR(af, "Cannot open Libavresample Context. \n");
|
||||
goto error;
|
||||
}
|
||||
return AF_OK;
|
||||
|
||||
error:
|
||||
close_lavrr(af);
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
|
||||
static int control(struct af_instance *af, int cmd, void *arg)
|
||||
{
|
||||
struct af_resample *s = af->priv;
|
||||
|
||||
switch (cmd) {
|
||||
case AF_CONTROL_REINIT: {
|
||||
struct mp_audio *in = arg;
|
||||
struct mp_audio *out = af->data;
|
||||
struct mp_audio orig_in = *in;
|
||||
|
||||
if (((out->rate == in->rate) || (out->rate == 0)) &&
|
||||
(out->format == in->format) &&
|
||||
(mp_chmap_equals(&out->channels, &in->channels) || out->nch == 0) &&
|
||||
s->allow_detach && s->playback_speed == 1.0)
|
||||
return AF_DETACH;
|
||||
|
||||
if (out->rate == 0)
|
||||
out->rate = in->rate;
|
||||
|
||||
if (mp_chmap_is_empty(&out->channels))
|
||||
mp_audio_set_channels(out, &in->channels);
|
||||
|
||||
if (af_to_avformat(in->format) == AV_SAMPLE_FMT_NONE)
|
||||
mp_audio_set_format(in, AF_FORMAT_FLOAT);
|
||||
if (af_to_avformat(out->format) == AV_SAMPLE_FMT_NONE)
|
||||
mp_audio_set_format(out, in->format);
|
||||
|
||||
int r = ((in->format == orig_in.format) &&
|
||||
mp_chmap_equals(&in->channels, &orig_in.channels))
|
||||
? AF_OK : AF_FALSE;
|
||||
|
||||
if (r == AF_OK)
|
||||
r = configure_lavrr(af, in, out, true);
|
||||
return r;
|
||||
}
|
||||
case AF_CONTROL_SET_PLAYBACK_SPEED_RESAMPLE: {
|
||||
s->playback_speed = *(double *)arg;
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_RESET:
|
||||
if (s->avrctx) {
|
||||
#if HAVE_LIBSWRESAMPLE
|
||||
swr_close(s->avrctx);
|
||||
if (swr_init(s->avrctx) < 0) {
|
||||
close_lavrr(af);
|
||||
return AF_ERROR;
|
||||
}
|
||||
#else
|
||||
while (avresample_read(s->avrctx, NULL, 1000) > 0) {}
|
||||
#endif
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static void uninit(struct af_instance *af)
|
||||
{
|
||||
close_lavrr(af);
|
||||
}
|
||||
|
||||
// The LSB is always ignored.
|
||||
#if BYTE_ORDER == BIG_ENDIAN
|
||||
#define SHIFT24(x) ((3-(x))*8)
|
||||
#else
|
||||
#define SHIFT24(x) (((x)+1)*8)
|
||||
#endif
|
||||
|
||||
static void extra_output_conversion(struct af_instance *af, struct mp_audio *mpa)
|
||||
{
|
||||
for (int p = 0; p < mpa->num_planes; p++) {
|
||||
void *ptr = mpa->planes[p];
|
||||
int total = mpa->samples * mpa->spf;
|
||||
if (af_fmt_from_planar(mpa->format) == AF_FORMAT_FLOAT) {
|
||||
for (int s = 0; s < total; s++)
|
||||
((float *)ptr)[s] = av_clipf(((float *)ptr)[s], -1.0f, 1.0f);
|
||||
} else if (af_fmt_from_planar(mpa->format) == AF_FORMAT_DOUBLE) {
|
||||
for (int s = 0; s < total; s++)
|
||||
((double *)ptr)[s] = MPCLAMP(((double *)ptr)[s], -1.0, 1.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This relies on the tricky way mpa was allocated.
|
||||
static void reorder_planes(struct mp_audio *mpa, int *reorder,
|
||||
struct mp_chmap *newmap)
|
||||
{
|
||||
struct mp_audio prev = *mpa;
|
||||
mp_audio_set_channels(mpa, newmap);
|
||||
|
||||
// The trailing planes were never written by avrctx, they're the NA channels.
|
||||
int next_na = prev.num_planes;
|
||||
|
||||
for (int n = 0; n < mpa->num_planes; n++) {
|
||||
int src = reorder[n];
|
||||
assert(src >= -1 && src < prev.num_planes);
|
||||
if (src >= 0) {
|
||||
mpa->planes[n] = prev.planes[src];
|
||||
} else {
|
||||
assert(next_na < mpa->num_planes);
|
||||
mpa->planes[n] = prev.planes[next_na++];
|
||||
af_fill_silence(mpa->planes[n], mpa->sstride * mpa->samples,
|
||||
mpa->format);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int filter_resample(struct af_instance *af, struct mp_audio *in)
|
||||
{
|
||||
struct af_resample *s = af->priv;
|
||||
struct mp_audio *out = NULL;
|
||||
|
||||
if (!s->avrctx)
|
||||
goto error;
|
||||
|
||||
int samples = get_out_samples(s, in ? in->samples : 0);
|
||||
|
||||
struct mp_audio out_format = s->pool_fmt;
|
||||
out = mp_audio_pool_get(af->out_pool, &out_format, samples);
|
||||
if (!out)
|
||||
goto error;
|
||||
if (in)
|
||||
mp_audio_copy_attributes(out, in);
|
||||
|
||||
if (out->samples) {
|
||||
out->samples = resample_frame(s->avrctx, out, in);
|
||||
if (out->samples < 0)
|
||||
goto error;
|
||||
}
|
||||
|
||||
struct mp_audio real_out = *out;
|
||||
mp_audio_copy_config(out, &s->avrctx_fmt);
|
||||
|
||||
if (out->samples && !mp_audio_config_equals(out, &s->pre_out_fmt)) {
|
||||
assert(af_fmt_is_planar(out->format) && out->format == real_out.format);
|
||||
reorder_planes(out, s->reorder_out, &s->pool_fmt.channels);
|
||||
if (!mp_audio_config_equals(out, &s->pre_out_fmt)) {
|
||||
struct mp_audio *new = mp_audio_pool_get(s->reorder_buffer,
|
||||
&s->pre_out_fmt,
|
||||
out->samples);
|
||||
if (!new)
|
||||
goto error;
|
||||
mp_audio_copy_attributes(new, out);
|
||||
int out_samples = resample_frame(s->avrctx_out, new, out);
|
||||
talloc_free(out);
|
||||
out = new;
|
||||
if (out_samples != new->samples)
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
extra_output_conversion(af, out);
|
||||
|
||||
talloc_free(in);
|
||||
if (out->samples) {
|
||||
af_add_output_frame(af, out);
|
||||
} else {
|
||||
talloc_free(out);
|
||||
}
|
||||
|
||||
af->delay = get_delay(s);
|
||||
|
||||
return 0;
|
||||
error:
|
||||
talloc_free(in);
|
||||
talloc_free(out);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int filter(struct af_instance *af, struct mp_audio *in)
|
||||
{
|
||||
struct af_resample *s = af->priv;
|
||||
|
||||
int new_rate = rate_from_speed(s->in_rate_af, s->playback_speed);
|
||||
if (s->avrctx && !(!s->is_resampling && new_rate == s->in_rate)) {
|
||||
AVRational r = av_d2q(s->playback_speed * s->in_rate_af / s->in_rate,
|
||||
INT_MAX / 2);
|
||||
// Essentially, swr/avresample_set_compensation() does 2 things:
|
||||
// - adjust output sample rate by sample_delta/compensation_distance
|
||||
// - reset the adjustment after compensation_distance output samples
|
||||
// Increase the compensation_distance to avoid undesired reset
|
||||
// semantics - we want to keep the ratio for the whole frame we're
|
||||
// feeding it, until the next filter() call.
|
||||
int mult = INT_MAX / 2 / MPMAX(MPMAX(abs(r.num), abs(r.den)), 1);
|
||||
r = (AVRational){ r.num * mult, r.den * mult };
|
||||
if (avresample_set_compensation(s->avrctx, r.den - r.num, r.den) >= 0) {
|
||||
new_rate = s->in_rate;
|
||||
s->is_resampling = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool need_reinit = fabs(new_rate / (double)s->in_rate - 1) > 0.01;
|
||||
if (need_reinit && new_rate != s->in_rate) {
|
||||
// Before reconfiguring, drain the audio that is still buffered
|
||||
// in the resampler.
|
||||
filter_resample(af, NULL);
|
||||
// Reinitialize resampler.
|
||||
configure_lavrr(af, &af->fmt_in, &af->fmt_out, false);
|
||||
}
|
||||
|
||||
return filter_resample(af, in);
|
||||
}
|
||||
|
||||
static int af_open(struct af_instance *af)
|
||||
{
|
||||
struct af_resample *s = af->priv;
|
||||
|
||||
af->control = control;
|
||||
af->uninit = uninit;
|
||||
af->filter_frame = filter;
|
||||
|
||||
if (s->opts.cutoff <= 0.0)
|
||||
s->opts.cutoff = af_resample_default_cutoff(s->opts.filter_size);
|
||||
|
||||
s->reorder_buffer = mp_audio_pool_create(s);
|
||||
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct af_resample
|
||||
|
||||
const struct af_info af_info_lavrresample = {
|
||||
.info = "Sample frequency conversion using libavresample",
|
||||
.name = "lavrresample",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(struct af_resample),
|
||||
.priv_defaults = &(const struct af_resample) {
|
||||
.opts = {
|
||||
.filter_size = 16,
|
||||
.cutoff = 0.0,
|
||||
.phase_shift = 10,
|
||||
.normalize = -1,
|
||||
},
|
||||
.playback_speed = 1.0,
|
||||
.allow_detach = 1,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_INTRANGE("filter-size", opts.filter_size, 0, 0, 32),
|
||||
OPT_INTRANGE("phase-shift", opts.phase_shift, 0, 0, 30),
|
||||
OPT_FLAG("linear", opts.linear, 0),
|
||||
OPT_DOUBLE("cutoff", opts.cutoff, M_OPT_RANGE, .min = 0, .max = 1),
|
||||
OPT_FLAG("detach", allow_detach, 0),
|
||||
OPT_CHOICE("normalize", opts.normalize, 0,
|
||||
({"no", 0}, {"yes", 1}, {"auto", -1})),
|
||||
OPT_KEYVALUELIST("o", avopts, 0),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
* Copyright (C) 2002 Anders Johansson ajh@atri.curtin.edu.au
|
||||
*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <math.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "af.h"
|
||||
|
||||
// Data for specific instances of this filter
|
||||
typedef struct af_pan_s {
|
||||
int nch; // Number of output channels; zero means same as input
|
||||
float level[AF_NCH][AF_NCH]; // Gain level for each channel
|
||||
char *matrixstr;
|
||||
} af_pan_t;
|
||||
|
||||
static void set_channels(struct mp_audio *mpa, int num)
|
||||
{
|
||||
struct mp_chmap map;
|
||||
// "unknown" channel layouts make it easier to pass through audio data,
|
||||
// without triggering remixing.
|
||||
mp_chmap_set_unknown(&map, num);
|
||||
mp_audio_set_channels(mpa, &map);
|
||||
}
|
||||
|
||||
static void parse_matrix(struct af_instance *af, const char *cp)
|
||||
{
|
||||
af_pan_t *s = af->priv;
|
||||
int j = 0, k = 0, n;
|
||||
while (*cp && k < AF_NCH) {
|
||||
sscanf(cp, "%f%n" , &s->level[j][k], &n);
|
||||
MP_VERBOSE(af, "Pan level from channel %i to"
|
||||
" channel %i = %f\n", k, j, s->level[j][k]);
|
||||
cp = &cp[n];
|
||||
j++;
|
||||
if (j >= s->nch) {
|
||||
j = 0;
|
||||
k++;
|
||||
}
|
||||
if (*cp != ',')
|
||||
break;
|
||||
cp++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Initialization and runtime control
|
||||
static int control(struct af_instance *af, int cmd, void *arg)
|
||||
{
|
||||
af_pan_t* s = af->priv;
|
||||
|
||||
switch(cmd){
|
||||
case AF_CONTROL_REINIT:
|
||||
// Sanity check
|
||||
if (!arg)
|
||||
return AF_ERROR;
|
||||
|
||||
af->data->rate = ((struct mp_audio*)arg)->rate;
|
||||
mp_audio_set_format(af->data, AF_FORMAT_FLOAT);
|
||||
set_channels(af->data, s->nch ? s->nch : ((struct mp_audio*)arg)->nch);
|
||||
|
||||
if ((af->data->format != ((struct mp_audio*)arg)->format) ||
|
||||
(af->data->bps != ((struct mp_audio*)arg)->bps)) {
|
||||
mp_audio_set_format((struct mp_audio*)arg, af->data->format);
|
||||
return AF_FALSE;
|
||||
}
|
||||
return AF_OK;
|
||||
case AF_CONTROL_SET_PAN_LEVEL: {
|
||||
int i;
|
||||
int ch = ((af_control_ext_t*)arg)->ch;
|
||||
float *level = ((af_control_ext_t*)arg)->arg;
|
||||
if (ch >= AF_NCH)
|
||||
return AF_FALSE;
|
||||
for (i = 0; i < AF_NCH; i++)
|
||||
s->level[ch][i] = level[i];
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_SET_PAN_NOUT:
|
||||
// Reinit must be called after this function has been called
|
||||
// Sanity check
|
||||
if (((int*)arg)[0] <= 0 || ((int*)arg)[0] > AF_NCH) {
|
||||
MP_ERR(af, "The number of output channels must be"
|
||||
" between 1 and %i. Current value is %i\n",
|
||||
AF_NCH, ((int*)arg)[0]);
|
||||
return AF_ERROR;
|
||||
}
|
||||
s->nch = ((int*)arg)[0];
|
||||
return AF_OK;
|
||||
case AF_CONTROL_SET_PAN_BALANCE: {
|
||||
float val = *(float*)arg;
|
||||
if (s->nch)
|
||||
return AF_ERROR;
|
||||
if (af->data->nch >= 2) {
|
||||
s->level[0][0] = MPMIN(1.f, 1.f - val);
|
||||
s->level[0][1] = MPMAX(0.f, val);
|
||||
s->level[1][0] = MPMAX(0.f, -val);
|
||||
s->level[1][1] = MPMIN(1.f, 1.f + val);
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_GET_PAN_BALANCE:
|
||||
if (s->nch)
|
||||
return AF_ERROR;
|
||||
*(float*)arg = s->level[0][1] - s->level[1][0];
|
||||
return AF_OK;
|
||||
case AF_CONTROL_COMMAND: {
|
||||
char **args = arg;
|
||||
if (!strcmp(args[0], "set-matrix")) {
|
||||
parse_matrix(af, args[1]);
|
||||
return CONTROL_OK;
|
||||
} else {
|
||||
return CONTROL_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static int filter_frame(struct af_instance *af, struct mp_audio *c)
|
||||
{
|
||||
if (!c)
|
||||
return 0;
|
||||
struct mp_audio *l = mp_audio_pool_get(af->out_pool, &af->fmt_out, c->samples);
|
||||
if (!l) {
|
||||
talloc_free(c);
|
||||
return -1;
|
||||
}
|
||||
mp_audio_copy_attributes(l, c);
|
||||
|
||||
af_pan_t* s = af->priv; // Setup for this instance
|
||||
float *in = c->planes[0]; // Input audio data
|
||||
float *out = NULL; // Output audio data
|
||||
float *end = in+c->samples * c->nch; // End of loop
|
||||
int nchi = c->nch; // Number of input channels
|
||||
int ncho = l->nch; // Number of output channels
|
||||
register int j, k;
|
||||
|
||||
out = l->planes[0];
|
||||
// Execute panning
|
||||
// FIXME: Too slow
|
||||
while (in < end) {
|
||||
for (j = 0; j < ncho; j++) {
|
||||
register float x = 0.0;
|
||||
register float *tin = in;
|
||||
for (k = 0; k < nchi; k++)
|
||||
x += tin[k] * s->level[j][k];
|
||||
out[j] = x;
|
||||
}
|
||||
out += ncho;
|
||||
in += nchi;
|
||||
}
|
||||
|
||||
talloc_free(c);
|
||||
af_add_output_frame(af, l);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Allocate memory and set function pointers
|
||||
static int af_open(struct af_instance *af)
|
||||
{
|
||||
af->control = control;
|
||||
af->filter_frame = filter_frame;
|
||||
MP_WARN(af, "This filter is deprecated. Use lavfi pan instead.\n");
|
||||
af_pan_t *s = af->priv;
|
||||
int nch = s->nch;
|
||||
if (nch && AF_OK != control(af, AF_CONTROL_SET_PAN_NOUT, &nch))
|
||||
return AF_ERROR;
|
||||
|
||||
// Read pan values
|
||||
if (s->matrixstr)
|
||||
parse_matrix(af, s->matrixstr);
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT af_pan_t
|
||||
const struct af_info af_info_pan = {
|
||||
.info = "Panning audio filter",
|
||||
.name = "pan",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(af_pan_t),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_INTRANGE("channels", nch, 0, 0, AF_NCH),
|
||||
OPT_STRING("matrix", matrixstr, 0),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
+169
-287
@@ -20,350 +20,232 @@
|
||||
|
||||
#include <rubberband/rubberband-c.h>
|
||||
|
||||
#include "audio/aframe.h"
|
||||
#include "audio/format.h"
|
||||
#include "common/common.h"
|
||||
#include "filters/f_autoconvert.h"
|
||||
#include "filters/filter_internal.h"
|
||||
#include "filters/user_filters.h"
|
||||
#include "options/m_option.h"
|
||||
|
||||
// command line options
|
||||
struct f_opts {
|
||||
int transients, detector, phase, window,
|
||||
smoothing, formant, pitch, channels;
|
||||
double scale;
|
||||
};
|
||||
#include "af.h"
|
||||
|
||||
struct priv {
|
||||
struct f_opts *opts;
|
||||
|
||||
struct mp_pin *in_pin;
|
||||
struct mp_aframe *cur_format;
|
||||
struct mp_aframe_pool *out_pool;
|
||||
bool sent_final;
|
||||
RubberBandState rubber;
|
||||
double speed;
|
||||
double pitch;
|
||||
struct mp_aframe *pending;
|
||||
struct mp_audio *pending;
|
||||
bool needs_reset;
|
||||
// Estimate how much librubberband has buffered internally.
|
||||
// I could not find a way to do this with the librubberband API.
|
||||
double rubber_delay;
|
||||
// command line options
|
||||
int opt_transients, opt_detector, opt_phase, opt_window,
|
||||
opt_smoothing, opt_formant, opt_pitch, opt_channels;
|
||||
};
|
||||
|
||||
static void update_speed(struct priv *p, double new_speed)
|
||||
static void update_speed(struct af_instance *af, double new_speed)
|
||||
{
|
||||
struct priv *p = af->priv;
|
||||
|
||||
p->speed = new_speed;
|
||||
if (p->rubber)
|
||||
rubberband_set_time_ratio(p->rubber, 1.0 / p->speed);
|
||||
rubberband_set_time_ratio(p->rubber, 1.0 / p->speed);
|
||||
}
|
||||
|
||||
static bool update_pitch(struct priv *p, double new_pitch)
|
||||
static void update_pitch(struct af_instance *af, double new_pitch)
|
||||
{
|
||||
if (new_pitch < 0.01 || new_pitch > 100.0)
|
||||
return false;
|
||||
struct priv *p = af->priv;
|
||||
|
||||
p->pitch = new_pitch;
|
||||
if (p->rubber)
|
||||
rubberband_set_pitch_scale(p->rubber, p->pitch);
|
||||
return true;
|
||||
rubberband_set_pitch_scale(p->rubber, p->pitch);
|
||||
}
|
||||
|
||||
static bool init_rubberband(struct mp_filter *f)
|
||||
static int control(struct af_instance *af, int cmd, void *arg)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
struct priv *p = af->priv;
|
||||
|
||||
assert(!p->rubber);
|
||||
assert(p->pending);
|
||||
switch (cmd) {
|
||||
case AF_CONTROL_REINIT: {
|
||||
struct mp_audio *in = arg;
|
||||
struct mp_audio orig_in = *in;
|
||||
struct mp_audio *out = af->data;
|
||||
|
||||
int opts = p->opts->transients | p->opts->detector | p->opts->phase |
|
||||
p->opts->window | p->opts->smoothing | p->opts->formant |
|
||||
p->opts->pitch | p-> opts->channels |
|
||||
RubberBandOptionProcessRealTime;
|
||||
in->format = AF_FORMAT_FLOATP;
|
||||
mp_audio_copy_config(out, in);
|
||||
|
||||
int rate = mp_aframe_get_rate(p->pending);
|
||||
int channels = mp_aframe_get_channels(p->pending);
|
||||
if (mp_aframe_get_format(p->pending) != AF_FORMAT_FLOATP)
|
||||
return false;
|
||||
if (p->rubber)
|
||||
rubberband_delete(p->rubber);
|
||||
|
||||
p->rubber = rubberband_new(rate, channels, opts, 1.0, 1.0);
|
||||
if (!p->rubber) {
|
||||
MP_FATAL(f, "librubberband initialization failed.\n");
|
||||
return false;
|
||||
int opts = p->opt_transients | p->opt_detector | p->opt_phase |
|
||||
p->opt_window | p->opt_smoothing | p->opt_formant |
|
||||
p->opt_pitch | p-> opt_channels |
|
||||
RubberBandOptionProcessRealTime;
|
||||
|
||||
p->rubber = rubberband_new(in->rate, in->channels.num, opts, 1.0, 1.0);
|
||||
if (!p->rubber) {
|
||||
MP_FATAL(af, "librubberband initialization failed.\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
update_speed(af, p->speed);
|
||||
update_pitch(af, p->pitch);
|
||||
control(af, AF_CONTROL_RESET, NULL);
|
||||
|
||||
return mp_audio_config_equals(in, &orig_in) ? AF_OK : AF_FALSE;
|
||||
}
|
||||
|
||||
mp_aframe_config_copy(p->cur_format, p->pending);
|
||||
|
||||
update_speed(p, p->speed);
|
||||
update_pitch(p, p->pitch);
|
||||
|
||||
return true;
|
||||
case AF_CONTROL_SET_PLAYBACK_SPEED: {
|
||||
update_speed(af, *(double *)arg);
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_RESET:
|
||||
if (p->rubber)
|
||||
rubberband_reset(p->rubber);
|
||||
talloc_free(p->pending);
|
||||
p->pending = NULL;
|
||||
p->rubber_delay = 0;
|
||||
return AF_OK;
|
||||
case AF_CONTROL_COMMAND: {
|
||||
char **args = arg;
|
||||
if (!strcmp(args[0], "set-pitch")) {
|
||||
char *endptr;
|
||||
double pitch = strtod(args[1], &endptr);
|
||||
if (*endptr || pitch < 0.01 || pitch > 100.0)
|
||||
return CONTROL_ERROR;
|
||||
update_pitch(af, pitch);
|
||||
return CONTROL_OK;
|
||||
} else {
|
||||
return CONTROL_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static void process(struct mp_filter *f)
|
||||
static int filter_frame(struct af_instance *af, struct mp_audio *data)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
struct priv *p = af->priv;
|
||||
|
||||
if (!mp_pin_in_needs_data(f->ppins[1]))
|
||||
return;
|
||||
talloc_free(p->pending);
|
||||
p->pending = data;
|
||||
|
||||
while (!p->rubber || !p->pending || rubberband_available(p->rubber) <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int filter_out(struct af_instance *af)
|
||||
{
|
||||
struct priv *p = af->priv;
|
||||
|
||||
while (rubberband_available(p->rubber) <= 0) {
|
||||
const float *dummy[MP_NUM_CHANNELS] = {0};
|
||||
const float **in_data = dummy;
|
||||
size_t in_samples = 0;
|
||||
if (p->pending) {
|
||||
if (!p->pending->samples)
|
||||
break;
|
||||
|
||||
bool eof = false;
|
||||
if (!p->pending || !mp_aframe_get_size(p->pending)) {
|
||||
struct mp_frame frame = mp_pin_out_read(p->in_pin);
|
||||
if (frame.type == MP_FRAME_AUDIO) {
|
||||
TA_FREEP(&p->pending);
|
||||
p->pending = frame.data;
|
||||
} else if (frame.type == MP_FRAME_EOF) {
|
||||
eof = true;
|
||||
} else if (frame.type) {
|
||||
MP_ERR(f, "unexpected frame type\n");
|
||||
goto error;
|
||||
} else {
|
||||
return; // no new data yet
|
||||
}
|
||||
}
|
||||
assert(p->pending || eof);
|
||||
|
||||
if (!p->rubber) {
|
||||
if (!p->pending) {
|
||||
mp_pin_in_write(f->ppins[1], MP_EOF_FRAME);
|
||||
return;
|
||||
}
|
||||
if (!init_rubberband(f))
|
||||
goto error;
|
||||
}
|
||||
|
||||
bool format_change =
|
||||
p->pending && !mp_aframe_config_equals(p->pending, p->cur_format);
|
||||
|
||||
if (p->pending && !format_change) {
|
||||
size_t needs = rubberband_get_samples_required(p->rubber);
|
||||
uint8_t **planes = mp_aframe_get_data_ro(p->pending);
|
||||
int num_planes = mp_aframe_get_planes(p->pending);
|
||||
for (int n = 0; n < num_planes; n++)
|
||||
in_data[n] = (void *)planes[n];
|
||||
in_samples = MPMIN(mp_aframe_get_size(p->pending), needs);
|
||||
}
|
||||
|
||||
bool final = format_change || eof;
|
||||
if (!p->sent_final)
|
||||
rubberband_process(p->rubber, in_data, in_samples, final);
|
||||
p->sent_final |= final;
|
||||
|
||||
p->rubber_delay += in_samples;
|
||||
|
||||
if (p->pending && !format_change)
|
||||
mp_aframe_skip_samples(p->pending, in_samples);
|
||||
|
||||
if (rubberband_available(p->rubber) > 0) {
|
||||
if (eof)
|
||||
mp_pin_out_repeat_eof(p->in_pin); // drain more next time
|
||||
} else {
|
||||
if (eof) {
|
||||
mp_pin_in_write(f->ppins[1], MP_EOF_FRAME);
|
||||
// recover from previous EOF
|
||||
if (p->needs_reset) {
|
||||
rubberband_reset(p->rubber);
|
||||
p->rubber_delay = 0;
|
||||
TA_FREEP(&p->pending);
|
||||
p->sent_final = false;
|
||||
return;
|
||||
} else if (format_change) {
|
||||
// go on with proper reinit on the next iteration
|
||||
rubberband_delete(p->rubber);
|
||||
p->sent_final = false;
|
||||
p->rubber = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
p->needs_reset = false;
|
||||
|
||||
assert(p->pending);
|
||||
size_t needs = rubberband_get_samples_required(p->rubber);
|
||||
in_data = (void *)&p->pending->planes;
|
||||
in_samples = MPMIN(p->pending->samples, needs);
|
||||
}
|
||||
|
||||
if (p->needs_reset)
|
||||
break; // previous EOF
|
||||
p->needs_reset = !p->pending; // EOF
|
||||
|
||||
rubberband_process(p->rubber, in_data, in_samples, p->needs_reset);
|
||||
p->rubber_delay += in_samples;
|
||||
|
||||
if (!p->pending)
|
||||
break;
|
||||
mp_audio_skip_samples(p->pending, in_samples);
|
||||
}
|
||||
|
||||
int out_samples = rubberband_available(p->rubber);
|
||||
if (out_samples > 0) {
|
||||
struct mp_aframe *out = mp_aframe_new_ref(p->cur_format);
|
||||
if (mp_aframe_pool_allocate(p->out_pool, out, out_samples) < 0) {
|
||||
talloc_free(out);
|
||||
goto error;
|
||||
}
|
||||
struct mp_audio *out =
|
||||
mp_audio_pool_get(af->out_pool, af->data, out_samples);
|
||||
if (!out)
|
||||
return -1;
|
||||
if (p->pending)
|
||||
mp_audio_copy_config(out, p->pending);
|
||||
|
||||
mp_aframe_copy_attributes(out, p->pending);
|
||||
float **out_data = (void *)&out->planes;
|
||||
out->samples = rubberband_retrieve(p->rubber, out_data, out->samples);
|
||||
p->rubber_delay -= out->samples * p->speed;
|
||||
|
||||
float *out_data[MP_NUM_CHANNELS] = {0};
|
||||
uint8_t **planes = mp_aframe_get_data_rw(out);
|
||||
assert(planes);
|
||||
int num_planes = mp_aframe_get_planes(out);
|
||||
for (int n = 0; n < num_planes; n++)
|
||||
out_data[n] = (void *)planes[n];
|
||||
|
||||
out_samples = rubberband_retrieve(p->rubber, out_data, out_samples);
|
||||
|
||||
if (!out_samples) {
|
||||
mp_filter_internal_mark_progress(f); // unexpected, just try again
|
||||
talloc_free(out);
|
||||
return;
|
||||
}
|
||||
|
||||
mp_aframe_set_size(out, out_samples);
|
||||
|
||||
p->rubber_delay -= out_samples * p->speed;
|
||||
|
||||
double pts = mp_aframe_get_pts(p->pending);
|
||||
if (pts != MP_NOPTS_VALUE) {
|
||||
// Note: rubberband_get_latency() does not do what you'd expect.
|
||||
double delay = p->rubber_delay / mp_aframe_get_effective_rate(out);
|
||||
mp_aframe_set_pts(out, pts - delay);
|
||||
}
|
||||
|
||||
mp_aframe_mul_speed(out, p->speed);
|
||||
|
||||
mp_pin_in_write(f->ppins[1], MAKE_FRAME(MP_FRAME_AUDIO, out));
|
||||
af_add_output_frame(af, out);
|
||||
}
|
||||
|
||||
return;
|
||||
error:
|
||||
mp_filter_internal_mark_failed(f);
|
||||
int delay_samples = p->rubber_delay;
|
||||
if (p->pending)
|
||||
delay_samples += p->pending->samples;
|
||||
af->delay = delay_samples / (af->data->rate * p->speed);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool command(struct mp_filter *f, struct mp_filter_command *cmd)
|
||||
static void uninit(struct af_instance *af)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
|
||||
switch (cmd->type) {
|
||||
case MP_FILTER_COMMAND_TEXT: {
|
||||
char *endptr = NULL;
|
||||
double pitch = p->pitch;
|
||||
if (!strcmp(cmd->cmd, "set-pitch")) {
|
||||
pitch = strtod(cmd->arg, &endptr);
|
||||
if (*endptr)
|
||||
return false;
|
||||
return update_pitch(p, pitch);
|
||||
} else if (!strcmp(cmd->cmd, "multiply-pitch")) {
|
||||
double mult = strtod(cmd->arg, &endptr);
|
||||
if (*endptr || mult <= 0)
|
||||
return false;
|
||||
pitch *= mult;
|
||||
return update_pitch(p, pitch);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case MP_FILTER_COMMAND_SET_SPEED:
|
||||
update_speed(p, cmd->speed);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void reset(struct mp_filter *f)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
|
||||
if (p->rubber)
|
||||
rubberband_reset(p->rubber);
|
||||
p->rubber_delay = 0;
|
||||
p->sent_final = false;
|
||||
TA_FREEP(&p->pending);
|
||||
}
|
||||
|
||||
static void destroy(struct mp_filter *f)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
struct priv *p = af->priv;
|
||||
|
||||
if (p->rubber)
|
||||
rubberband_delete(p->rubber);
|
||||
talloc_free(p->pending);
|
||||
}
|
||||
|
||||
static const struct mp_filter_info af_rubberband_filter = {
|
||||
.name = "rubberband",
|
||||
.priv_size = sizeof(struct priv),
|
||||
.process = process,
|
||||
.command = command,
|
||||
.reset = reset,
|
||||
.destroy = destroy,
|
||||
};
|
||||
|
||||
static struct mp_filter *af_rubberband_create(struct mp_filter *parent,
|
||||
void *options)
|
||||
static int af_open(struct af_instance *af)
|
||||
{
|
||||
struct mp_filter *f = mp_filter_create(parent, &af_rubberband_filter);
|
||||
if (!f) {
|
||||
talloc_free(options);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
mp_filter_add_pin(f, MP_PIN_IN, "in");
|
||||
mp_filter_add_pin(f, MP_PIN_OUT, "out");
|
||||
|
||||
struct priv *p = f->priv;
|
||||
p->opts = talloc_steal(p, options);
|
||||
p->speed = 1.0;
|
||||
p->pitch = p->opts->scale;
|
||||
p->cur_format = talloc_steal(p, mp_aframe_create());
|
||||
p->out_pool = mp_aframe_pool_create(p);
|
||||
|
||||
struct mp_autoconvert *conv = mp_autoconvert_create(f);
|
||||
if (!conv)
|
||||
abort();
|
||||
|
||||
mp_autoconvert_add_afmt(conv, AF_FORMAT_FLOATP);
|
||||
|
||||
mp_pin_connect(conv->f->pins[0], f->ppins[0]);
|
||||
p->in_pin = conv->f->pins[1];
|
||||
|
||||
return f;
|
||||
af->control = control;
|
||||
af->filter_frame = filter_frame;
|
||||
af->filter_out = filter_out;
|
||||
af->uninit = uninit;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct f_opts
|
||||
|
||||
const struct mp_user_filter_entry af_rubberband = {
|
||||
.desc = {
|
||||
.description = "Pitch conversion with librubberband",
|
||||
.name = "rubberband",
|
||||
.priv_size = sizeof(OPT_BASE_STRUCT),
|
||||
.priv_defaults = &(const OPT_BASE_STRUCT) {
|
||||
.scale = 1.0,
|
||||
.pitch = RubberBandOptionPitchHighConsistency,
|
||||
.transients = RubberBandOptionTransientsMixed,
|
||||
.formant = RubberBandOptionFormantPreserved,
|
||||
.channels = RubberBandOptionChannelsTogether,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
{"transients", OPT_CHOICE(transients,
|
||||
{"crisp", RubberBandOptionTransientsCrisp},
|
||||
{"mixed", RubberBandOptionTransientsMixed},
|
||||
{"smooth", RubberBandOptionTransientsSmooth})},
|
||||
{"detector", OPT_CHOICE(detector,
|
||||
{"compound", RubberBandOptionDetectorCompound},
|
||||
{"percussive", RubberBandOptionDetectorPercussive},
|
||||
{"soft", RubberBandOptionDetectorSoft})},
|
||||
{"phase", OPT_CHOICE(phase,
|
||||
{"laminar", RubberBandOptionPhaseLaminar},
|
||||
{"independent", RubberBandOptionPhaseIndependent})},
|
||||
{"window", OPT_CHOICE(window,
|
||||
{"standard", RubberBandOptionWindowStandard},
|
||||
{"short", RubberBandOptionWindowShort},
|
||||
{"long", RubberBandOptionWindowLong})},
|
||||
{"smoothing", OPT_CHOICE(smoothing,
|
||||
{"off", RubberBandOptionSmoothingOff},
|
||||
{"on", RubberBandOptionSmoothingOn})},
|
||||
{"formant", OPT_CHOICE(formant,
|
||||
{"shifted", RubberBandOptionFormantShifted},
|
||||
{"preserved", RubberBandOptionFormantPreserved})},
|
||||
{"pitch", OPT_CHOICE(pitch,
|
||||
{"quality", RubberBandOptionPitchHighQuality},
|
||||
{"speed", RubberBandOptionPitchHighSpeed},
|
||||
{"consistency", RubberBandOptionPitchHighConsistency})},
|
||||
{"channels", OPT_CHOICE(channels,
|
||||
{"apart", RubberBandOptionChannelsApart},
|
||||
{"together", RubberBandOptionChannelsTogether})},
|
||||
{"pitch-scale", OPT_DOUBLE(scale), M_RANGE(0.01, 100)},
|
||||
{0}
|
||||
},
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
const struct af_info af_info_rubberband = {
|
||||
.info = "Pitch conversion with librubberband",
|
||||
.name = "rubberband",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.priv_defaults = &(const struct priv) {
|
||||
.speed = 1.0,
|
||||
.pitch = 1.0,
|
||||
.opt_pitch = RubberBandOptionPitchHighConsistency,
|
||||
.opt_transients = RubberBandOptionTransientsMixed,
|
||||
.opt_formant = RubberBandOptionFormantPreserved,
|
||||
.opt_channels = RubberBandOptionChannelsTogether,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_CHOICE("transients", opt_transients, 0,
|
||||
({"crisp", RubberBandOptionTransientsCrisp},
|
||||
{"mixed", RubberBandOptionTransientsMixed},
|
||||
{"smooth", RubberBandOptionTransientsSmooth})),
|
||||
OPT_CHOICE("detector", opt_detector, 0,
|
||||
({"compound", RubberBandOptionDetectorCompound},
|
||||
{"percussive", RubberBandOptionDetectorPercussive},
|
||||
{"soft", RubberBandOptionDetectorSoft})),
|
||||
OPT_CHOICE("phase", opt_phase, 0,
|
||||
({"laminar", RubberBandOptionPhaseLaminar},
|
||||
{"independent", RubberBandOptionPhaseIndependent})),
|
||||
OPT_CHOICE("window", opt_window, 0,
|
||||
({"standard", RubberBandOptionWindowStandard},
|
||||
{"short", RubberBandOptionWindowShort},
|
||||
{"long", RubberBandOptionWindowLong})),
|
||||
OPT_CHOICE("smoothing", opt_smoothing, 0,
|
||||
({"off", RubberBandOptionSmoothingOff},
|
||||
{"on", RubberBandOptionSmoothingOn})),
|
||||
OPT_CHOICE("formant", opt_formant, 0,
|
||||
({"shifted", RubberBandOptionFormantShifted},
|
||||
{"preserved", RubberBandOptionFormantPreserved})),
|
||||
OPT_CHOICE("pitch", opt_pitch, 0,
|
||||
({"quality", RubberBandOptionPitchHighQuality},
|
||||
{"speed", RubberBandOptionPitchHighSpeed},
|
||||
{"consistency", RubberBandOptionPitchHighConsistency})),
|
||||
OPT_CHOICE("channels", opt_channels, 0,
|
||||
({"apart", RubberBandOptionChannelsApart},
|
||||
{"together", RubberBandOptionChannelsTogether})),
|
||||
OPT_DOUBLE("pitch-scale", pitch, M_OPT_RANGE, .min = 0.01, .max = 100),
|
||||
{0}
|
||||
},
|
||||
.create = af_rubberband_create,
|
||||
};
|
||||
+244
-376
@@ -30,39 +30,19 @@
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <float.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <limits.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "audio/aframe.h"
|
||||
#include "audio/format.h"
|
||||
#include "common/common.h"
|
||||
#include "filters/f_autoconvert.h"
|
||||
#include "filters/filter_internal.h"
|
||||
#include "filters/user_filters.h"
|
||||
|
||||
#include "af.h"
|
||||
#include "options/m_option.h"
|
||||
|
||||
struct f_opts {
|
||||
float scale_nominal;
|
||||
float ms_stride;
|
||||
float ms_search;
|
||||
float percent_overlap;
|
||||
#define SCALE_TEMPO 1
|
||||
#define SCALE_PITCH 2
|
||||
int speed_opt;
|
||||
};
|
||||
|
||||
struct priv {
|
||||
struct f_opts *opts;
|
||||
|
||||
struct mp_pin *in_pin;
|
||||
struct mp_aframe *cur_format;
|
||||
struct mp_aframe_pool *out_pool;
|
||||
double current_pts;
|
||||
struct mp_aframe *in;
|
||||
|
||||
// Data for specific instances of this filter
|
||||
typedef struct af_scaletempo_s
|
||||
{
|
||||
// stride
|
||||
float scale;
|
||||
float speed;
|
||||
@@ -82,23 +62,29 @@ struct priv {
|
||||
int bytes_standing;
|
||||
void *buf_overlap;
|
||||
void *table_blend;
|
||||
void (*output_overlap)(struct priv *s, void *out_buf,
|
||||
void (*output_overlap)(struct af_scaletempo_s *s, void *out_buf,
|
||||
int bytes_off);
|
||||
// best overlap
|
||||
int frames_search;
|
||||
int num_channels;
|
||||
void *buf_pre_corr;
|
||||
void *table_window;
|
||||
int (*best_overlap_offset)(struct priv *s);
|
||||
};
|
||||
int (*best_overlap_offset)(struct af_scaletempo_s *s);
|
||||
// command line
|
||||
float scale_nominal;
|
||||
float ms_stride;
|
||||
float percent_overlap;
|
||||
float ms_search;
|
||||
#define SCALE_TEMPO 1
|
||||
#define SCALE_PITCH 2
|
||||
int speed_opt;
|
||||
} af_scaletempo_t;
|
||||
|
||||
static bool reinit(struct mp_filter *f);
|
||||
|
||||
// Return whether it got enough data for filtering.
|
||||
static bool fill_queue(struct priv *s)
|
||||
static int fill_queue(struct af_instance *af, struct mp_audio *data, int offset)
|
||||
{
|
||||
int bytes_in = s->in ? mp_aframe_get_size(s->in) * s->bytes_per_frame : 0;
|
||||
int offset = 0;
|
||||
af_scaletempo_t *s = af->priv;
|
||||
int bytes_in = (data ? mp_audio_psize(data) : 0) - offset;
|
||||
int offset_unchanged = offset;
|
||||
|
||||
if (s->bytes_to_slide > 0) {
|
||||
if (s->bytes_to_slide < s->bytes_queued) {
|
||||
@@ -117,27 +103,21 @@ static bool fill_queue(struct priv *s)
|
||||
}
|
||||
}
|
||||
|
||||
int bytes_needed = s->bytes_queue - s->bytes_queued;
|
||||
assert(bytes_needed >= 0);
|
||||
|
||||
int bytes_copy = MPMIN(bytes_needed, bytes_in);
|
||||
if (bytes_copy > 0) {
|
||||
uint8_t **planes = mp_aframe_get_data_ro(s->in);
|
||||
memcpy(s->buf_queue + s->bytes_queued, planes[0] + offset, bytes_copy);
|
||||
if (bytes_in > 0) {
|
||||
int bytes_copy = MPMIN(s->bytes_queue - s->bytes_queued, bytes_in);
|
||||
assert(bytes_copy >= 0);
|
||||
memcpy(s->buf_queue + s->bytes_queued,
|
||||
(int8_t *)data->planes[0] + offset, bytes_copy);
|
||||
s->bytes_queued += bytes_copy;
|
||||
offset += bytes_copy;
|
||||
bytes_needed -= bytes_copy;
|
||||
}
|
||||
|
||||
if (s->in)
|
||||
mp_aframe_skip_samples(s->in, offset / s->bytes_per_frame);
|
||||
|
||||
return bytes_needed == 0;
|
||||
return offset - offset_unchanged;
|
||||
}
|
||||
|
||||
#define UNROLL_PADDING (4 * 4)
|
||||
|
||||
static int best_overlap_offset_float(struct priv *s)
|
||||
static int best_overlap_offset_float(af_scaletempo_t *s)
|
||||
{
|
||||
float best_corr = INT_MIN;
|
||||
int best_off = 0;
|
||||
@@ -166,7 +146,7 @@ static int best_overlap_offset_float(struct priv *s)
|
||||
return best_off * 4 * s->num_channels;
|
||||
}
|
||||
|
||||
static int best_overlap_offset_s16(struct priv *s)
|
||||
static int best_overlap_offset_s16(af_scaletempo_t *s)
|
||||
{
|
||||
int64_t best_corr = INT64_MIN;
|
||||
int best_off = 0;
|
||||
@@ -187,10 +167,10 @@ static int best_overlap_offset_s16(struct priv *s)
|
||||
ps += s->samples_overlap - s->num_channels;
|
||||
long i = -(s->samples_overlap - s->num_channels);
|
||||
do {
|
||||
corr += ppc[i + 0] * (int64_t)ps[i + 0];
|
||||
corr += ppc[i + 1] * (int64_t)ps[i + 1];
|
||||
corr += ppc[i + 2] * (int64_t)ps[i + 2];
|
||||
corr += ppc[i + 3] * (int64_t)ps[i + 3];
|
||||
corr += ppc[i + 0] * ps[i + 0];
|
||||
corr += ppc[i + 1] * ps[i + 1];
|
||||
corr += ppc[i + 2] * ps[i + 2];
|
||||
corr += ppc[i + 3] * ps[i + 3];
|
||||
i += 4;
|
||||
} while (i < 0);
|
||||
if (corr > best_corr) {
|
||||
@@ -203,7 +183,7 @@ static int best_overlap_offset_s16(struct priv *s)
|
||||
return best_off * 2 * s->num_channels;
|
||||
}
|
||||
|
||||
static void output_overlap_float(struct priv *s, void *buf_out,
|
||||
static void output_overlap_float(af_scaletempo_t *s, void *buf_out,
|
||||
int bytes_off)
|
||||
{
|
||||
float *pout = buf_out;
|
||||
@@ -216,7 +196,7 @@ static void output_overlap_float(struct priv *s, void *buf_out,
|
||||
}
|
||||
}
|
||||
|
||||
static void output_overlap_s16(struct priv *s, void *buf_out,
|
||||
static void output_overlap_s16(af_scaletempo_t *s, void *buf_out,
|
||||
int bytes_off)
|
||||
{
|
||||
int16_t *pout = buf_out;
|
||||
@@ -229,77 +209,29 @@ static void output_overlap_s16(struct priv *s, void *buf_out,
|
||||
}
|
||||
}
|
||||
|
||||
static void process(struct mp_filter *f)
|
||||
static int filter(struct af_instance *af, struct mp_audio *data)
|
||||
{
|
||||
struct priv *s = f->priv;
|
||||
af_scaletempo_t *s = af->priv;
|
||||
|
||||
if (!mp_pin_in_needs_data(f->ppins[1]))
|
||||
return;
|
||||
|
||||
struct mp_aframe *out = NULL;
|
||||
|
||||
bool drain = false;
|
||||
bool is_eof = false;
|
||||
if (!s->in) {
|
||||
struct mp_frame frame = mp_pin_out_read(s->in_pin);
|
||||
if (!frame.type)
|
||||
return; // no input yet
|
||||
if (frame.type != MP_FRAME_AUDIO && frame.type != MP_FRAME_EOF) {
|
||||
MP_ERR(f, "unexpected frame type\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
s->in = frame.type == MP_FRAME_AUDIO ? frame.data : NULL;
|
||||
is_eof = drain = !s->in;
|
||||
|
||||
// EOF before it was even initialized once.
|
||||
if (is_eof && !mp_aframe_config_is_valid(s->cur_format)) {
|
||||
mp_pin_in_write(f->ppins[1], MP_EOF_FRAME);
|
||||
return;
|
||||
}
|
||||
|
||||
if (s->in && !mp_aframe_config_equals(s->in, s->cur_format)) {
|
||||
if (s->bytes_queued) {
|
||||
// Drain remaining data before executing the format change.
|
||||
MP_VERBOSE(f, "draining\n");
|
||||
mp_pin_out_unread(s->in_pin, frame);
|
||||
s->in = NULL;
|
||||
drain = true;
|
||||
} else {
|
||||
if (!reinit(f)) {
|
||||
MP_ERR(f, "initialization failed\n");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (s->in)
|
||||
s->current_pts = mp_aframe_end_pts(s->in);
|
||||
if (s->scale == 1.0) {
|
||||
af->delay = 0;
|
||||
af_add_output_frame(af, data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!fill_queue(s) && !drain) {
|
||||
TA_FREEP(&s->in);
|
||||
mp_pin_out_request_data_next(s->in_pin);
|
||||
return;
|
||||
int in_samples = data ? data->samples : 0;
|
||||
struct mp_audio *out = mp_audio_pool_get(af->out_pool, af->data,
|
||||
((int)(in_samples / s->frames_stride_scaled) + 1) * s->frames_stride);
|
||||
if (!out) {
|
||||
talloc_free(data);
|
||||
return -1;
|
||||
}
|
||||
if (data)
|
||||
mp_audio_copy_attributes(out, data);
|
||||
|
||||
int max_out_samples = s->bytes_stride / s->bytes_per_frame;
|
||||
if (drain)
|
||||
max_out_samples += s->bytes_queued;
|
||||
|
||||
out = mp_aframe_new_ref(s->cur_format);
|
||||
if (mp_aframe_pool_allocate(s->out_pool, out, max_out_samples) < 0)
|
||||
goto error;
|
||||
|
||||
if (s->in)
|
||||
mp_aframe_copy_attributes(out, s->in);
|
||||
|
||||
uint8_t **out_planes = mp_aframe_get_data_rw(out);
|
||||
if (!out_planes)
|
||||
goto error;
|
||||
int8_t *pout = out_planes[0];
|
||||
int out_offset = 0;
|
||||
if (s->bytes_queued >= s->bytes_queue) {
|
||||
int offset_in = fill_queue(af, data, 0);
|
||||
int8_t *pout = out->planes[0];
|
||||
while (s->bytes_queued >= s->bytes_queue) {
|
||||
int ti;
|
||||
float tf;
|
||||
int bytes_off = 0;
|
||||
@@ -308,12 +240,12 @@ static void process(struct mp_filter *f)
|
||||
if (s->output_overlap) {
|
||||
if (s->best_overlap_offset)
|
||||
bytes_off = s->best_overlap_offset(s);
|
||||
s->output_overlap(s, pout + out_offset, bytes_off);
|
||||
s->output_overlap(s, pout, bytes_off);
|
||||
}
|
||||
memcpy(pout + out_offset + s->bytes_overlap,
|
||||
memcpy(pout + s->bytes_overlap,
|
||||
s->buf_queue + bytes_off + s->bytes_overlap,
|
||||
s->bytes_standing);
|
||||
out_offset += s->bytes_stride;
|
||||
pout += s->bytes_stride;
|
||||
|
||||
// input stride
|
||||
memcpy(s->buf_overlap,
|
||||
@@ -323,304 +255,240 @@ static void process(struct mp_filter *f)
|
||||
ti = (int)tf;
|
||||
s->frames_stride_error = tf - ti;
|
||||
s->bytes_to_slide = ti * s->bytes_per_frame;
|
||||
|
||||
offset_in += fill_queue(af, data, offset_in);
|
||||
}
|
||||
// Drain remaining buffered data.
|
||||
if (drain && s->bytes_queued) {
|
||||
memcpy(pout + out_offset, s->buf_queue, s->bytes_queued);
|
||||
out_offset += s->bytes_queued;
|
||||
s->bytes_queued = 0;
|
||||
}
|
||||
mp_aframe_set_size(out, out_offset / s->bytes_per_frame);
|
||||
|
||||
// This filter can have a negative delay when scale > 1:
|
||||
// output corresponding to some length of input can be decided and written
|
||||
// after receiving only a part of that input.
|
||||
float delay = (out_offset * s->speed + s->bytes_queued - s->bytes_to_slide) /
|
||||
s->bytes_per_frame / mp_aframe_get_effective_rate(out)
|
||||
+ (s->in ? mp_aframe_duration(s->in) : 0);
|
||||
af->delay = (s->bytes_queued - s->bytes_to_slide) / s->scale
|
||||
/ out->sstride / out->rate;
|
||||
|
||||
if (s->current_pts != MP_NOPTS_VALUE)
|
||||
mp_aframe_set_pts(out, s->current_pts - delay);
|
||||
|
||||
mp_aframe_mul_speed(out, s->speed);
|
||||
|
||||
if (!mp_aframe_get_size(out))
|
||||
TA_FREEP(&out);
|
||||
|
||||
if (is_eof && out) {
|
||||
mp_pin_out_repeat_eof(s->in_pin);
|
||||
} else if (is_eof && !out) {
|
||||
mp_pin_in_write(f->ppins[1], MP_EOF_FRAME);
|
||||
} else if (!is_eof && !out) {
|
||||
mp_pin_out_request_data_next(s->in_pin);
|
||||
out->samples = (pout - (int8_t *)out->planes[0]) / out->sstride;
|
||||
talloc_free(data);
|
||||
if (out->samples) {
|
||||
af_add_output_frame(af, out);
|
||||
} else {
|
||||
talloc_free(out);
|
||||
}
|
||||
|
||||
if (out)
|
||||
mp_pin_in_write(f->ppins[1], MAKE_FRAME(MP_FRAME_AUDIO, out));
|
||||
|
||||
return;
|
||||
|
||||
error:
|
||||
TA_FREEP(&s->in);
|
||||
talloc_free(out);
|
||||
mp_filter_internal_mark_failed(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void update_speed(struct priv *s, float speed)
|
||||
static void update_speed(struct af_instance *af, float speed)
|
||||
{
|
||||
af_scaletempo_t *s = af->priv;
|
||||
|
||||
s->speed = speed;
|
||||
|
||||
double factor = (s->opts->speed_opt & SCALE_PITCH) ? 1.0 / s->speed : s->speed;
|
||||
s->scale = factor * s->opts->scale_nominal;
|
||||
double factor = (s->speed_opt & SCALE_PITCH) ? 1.0 / s->speed : s->speed;
|
||||
s->scale = factor * s->scale_nominal;
|
||||
|
||||
s->frames_stride_scaled = s->scale * s->frames_stride;
|
||||
s->frames_stride_error = MPMIN(s->frames_stride_error, s->frames_stride_scaled);
|
||||
}
|
||||
|
||||
static bool reinit(struct mp_filter *f)
|
||||
// Initialization and runtime control
|
||||
static int control(struct af_instance *af, int cmd, void *arg)
|
||||
{
|
||||
struct priv *s = f->priv;
|
||||
af_scaletempo_t *s = af->priv;
|
||||
switch (cmd) {
|
||||
case AF_CONTROL_REINIT: {
|
||||
struct mp_audio *data = (struct mp_audio *)arg;
|
||||
float srate = data->rate / 1000.0;
|
||||
int nch = data->nch;
|
||||
int use_int = 0;
|
||||
|
||||
mp_aframe_reset(s->cur_format);
|
||||
mp_audio_force_interleaved_format(data);
|
||||
mp_audio_copy_config(af->data, data);
|
||||
|
||||
float srate = mp_aframe_get_rate(s->in) / 1000.0;
|
||||
int nch = mp_aframe_get_channels(s->in);
|
||||
int format = mp_aframe_get_format(s->in);
|
||||
|
||||
int use_int = 0;
|
||||
if (format == AF_FORMAT_S16) {
|
||||
use_int = 1;
|
||||
} else if (format != AF_FORMAT_FLOAT) {
|
||||
return false;
|
||||
}
|
||||
int bps = use_int ? 2 : 4;
|
||||
|
||||
s->frames_stride = srate * s->opts->ms_stride;
|
||||
s->bytes_stride = s->frames_stride * bps * nch;
|
||||
|
||||
update_speed(s, s->speed);
|
||||
|
||||
int frames_overlap = s->frames_stride * s->opts->percent_overlap;
|
||||
if (frames_overlap <= 0) {
|
||||
s->bytes_standing = s->bytes_stride;
|
||||
s->samples_standing = s->bytes_standing / bps;
|
||||
s->output_overlap = NULL;
|
||||
s->bytes_overlap = 0;
|
||||
} else {
|
||||
s->samples_overlap = frames_overlap * nch;
|
||||
s->bytes_overlap = frames_overlap * nch * bps;
|
||||
s->bytes_standing = s->bytes_stride - s->bytes_overlap;
|
||||
s->samples_standing = s->bytes_standing / bps;
|
||||
s->buf_overlap = realloc(s->buf_overlap, s->bytes_overlap);
|
||||
s->table_blend = realloc(s->table_blend, s->bytes_overlap * 4);
|
||||
if (!s->buf_overlap || !s->table_blend) {
|
||||
MP_FATAL(f, "Out of memory\n");
|
||||
return false;
|
||||
}
|
||||
memset(s->buf_overlap, 0, s->bytes_overlap);
|
||||
if (use_int) {
|
||||
int32_t *pb = s->table_blend;
|
||||
int64_t blend = 0;
|
||||
for (int i = 0; i < frames_overlap; i++) {
|
||||
int32_t v = blend / frames_overlap;
|
||||
for (int j = 0; j < nch; j++)
|
||||
*pb++ = v;
|
||||
blend += 65536; // 2^16
|
||||
}
|
||||
s->output_overlap = output_overlap_s16;
|
||||
if (data->format == AF_FORMAT_S16) {
|
||||
use_int = 1;
|
||||
} else {
|
||||
float *pb = s->table_blend;
|
||||
for (int i = 0; i < frames_overlap; i++) {
|
||||
float v = i / (float)frames_overlap;
|
||||
for (int j = 0; j < nch; j++)
|
||||
*pb++ = v;
|
||||
}
|
||||
s->output_overlap = output_overlap_float;
|
||||
mp_audio_set_format(af->data, AF_FORMAT_FLOAT);
|
||||
}
|
||||
}
|
||||
int bps = af->data->bps;
|
||||
|
||||
s->frames_search = (frames_overlap > 1) ? srate * s->opts->ms_search : 0;
|
||||
if (s->frames_search <= 0)
|
||||
s->best_overlap_offset = NULL;
|
||||
else {
|
||||
if (use_int) {
|
||||
int64_t t = frames_overlap;
|
||||
int32_t n = 8589934588LL / (t * t); // 4 * (2^31 - 1) / t^2
|
||||
s->buf_pre_corr = realloc(s->buf_pre_corr,
|
||||
s->bytes_overlap * 2 + UNROLL_PADDING);
|
||||
s->table_window = realloc(s->table_window,
|
||||
s->bytes_overlap * 2 - nch * bps * 2);
|
||||
if (!s->buf_pre_corr || !s->table_window) {
|
||||
MP_FATAL(f, "Out of memory\n");
|
||||
return false;
|
||||
}
|
||||
memset((char *)s->buf_pre_corr + s->bytes_overlap * 2, 0,
|
||||
UNROLL_PADDING);
|
||||
int32_t *pw = s->table_window;
|
||||
for (int i = 1; i < frames_overlap; i++) {
|
||||
int32_t v = (i * (t - i) * n) >> 15;
|
||||
for (int j = 0; j < nch; j++)
|
||||
*pw++ = v;
|
||||
}
|
||||
s->best_overlap_offset = best_overlap_offset_s16;
|
||||
s->frames_stride = srate * s->ms_stride;
|
||||
s->bytes_stride = s->frames_stride * bps * nch;
|
||||
af->delay = 0;
|
||||
|
||||
update_speed(af, s->speed);
|
||||
|
||||
int frames_overlap = s->frames_stride * s->percent_overlap;
|
||||
if (frames_overlap <= 0) {
|
||||
s->bytes_standing = s->bytes_stride;
|
||||
s->samples_standing = s->bytes_standing / bps;
|
||||
s->output_overlap = NULL;
|
||||
s->bytes_overlap = 0;
|
||||
} else {
|
||||
s->buf_pre_corr = realloc(s->buf_pre_corr, s->bytes_overlap);
|
||||
s->table_window = realloc(s->table_window,
|
||||
s->bytes_overlap - nch * bps);
|
||||
if (!s->buf_pre_corr || !s->table_window) {
|
||||
MP_FATAL(f, "Out of memory\n");
|
||||
return false;
|
||||
s->samples_overlap = frames_overlap * nch;
|
||||
s->bytes_overlap = frames_overlap * nch * bps;
|
||||
s->bytes_standing = s->bytes_stride - s->bytes_overlap;
|
||||
s->samples_standing = s->bytes_standing / bps;
|
||||
s->buf_overlap = realloc(s->buf_overlap, s->bytes_overlap);
|
||||
s->table_blend = realloc(s->table_blend, s->bytes_overlap * 4);
|
||||
if (!s->buf_overlap || !s->table_blend) {
|
||||
MP_FATAL(af, "Out of memory\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
float *pw = s->table_window;
|
||||
for (int i = 1; i < frames_overlap; i++) {
|
||||
float v = i * (frames_overlap - i);
|
||||
for (int j = 0; j < nch; j++)
|
||||
*pw++ = v;
|
||||
memset(s->buf_overlap, 0, s->bytes_overlap);
|
||||
if (use_int) {
|
||||
int32_t *pb = s->table_blend;
|
||||
int64_t blend = 0;
|
||||
for (int i = 0; i < frames_overlap; i++) {
|
||||
int32_t v = blend / frames_overlap;
|
||||
for (int j = 0; j < nch; j++)
|
||||
*pb++ = v;
|
||||
blend += 65536; // 2^16
|
||||
}
|
||||
s->output_overlap = output_overlap_s16;
|
||||
} else {
|
||||
float *pb = s->table_blend;
|
||||
for (int i = 0; i < frames_overlap; i++) {
|
||||
float v = i / (float)frames_overlap;
|
||||
for (int j = 0; j < nch; j++)
|
||||
*pb++ = v;
|
||||
}
|
||||
s->output_overlap = output_overlap_float;
|
||||
}
|
||||
s->best_overlap_offset = best_overlap_offset_float;
|
||||
}
|
||||
}
|
||||
|
||||
s->bytes_per_frame = bps * nch;
|
||||
s->num_channels = nch;
|
||||
|
||||
s->bytes_queue = (s->frames_search + s->frames_stride + frames_overlap)
|
||||
* bps * nch;
|
||||
s->buf_queue = realloc(s->buf_queue, s->bytes_queue + UNROLL_PADDING);
|
||||
if (!s->buf_queue) {
|
||||
MP_FATAL(f, "Out of memory\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
s->bytes_queued = 0;
|
||||
s->bytes_to_slide = 0;
|
||||
|
||||
MP_DBG(f, ""
|
||||
"%.2f stride_in, %i stride_out, %i standing, "
|
||||
"%i overlap, %i search, %i queue, %s mode\n",
|
||||
s->frames_stride_scaled,
|
||||
(int)(s->bytes_stride / nch / bps),
|
||||
(int)(s->bytes_standing / nch / bps),
|
||||
(int)(s->bytes_overlap / nch / bps),
|
||||
s->frames_search,
|
||||
(int)(s->bytes_queue / nch / bps),
|
||||
(use_int ? "s16" : "float"));
|
||||
|
||||
mp_aframe_config_copy(s->cur_format, s->in);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool command(struct mp_filter *f, struct mp_filter_command *cmd)
|
||||
{
|
||||
struct priv *s = f->priv;
|
||||
|
||||
if (cmd->type == MP_FILTER_COMMAND_SET_SPEED) {
|
||||
if (s->opts->speed_opt & SCALE_TEMPO) {
|
||||
if (s->opts->speed_opt & SCALE_PITCH)
|
||||
return false;
|
||||
update_speed(s, cmd->speed);
|
||||
return true;
|
||||
} else if (s->opts->speed_opt & SCALE_PITCH) {
|
||||
update_speed(s, cmd->speed);
|
||||
return false; // do not signal OK
|
||||
s->frames_search = (frames_overlap > 1) ? srate * s->ms_search : 0;
|
||||
if (s->frames_search <= 0)
|
||||
s->best_overlap_offset = NULL;
|
||||
else {
|
||||
if (use_int) {
|
||||
int64_t t = frames_overlap;
|
||||
int32_t n = 8589934588LL / (t * t); // 4 * (2^31 - 1) / t^2
|
||||
s->buf_pre_corr = realloc(s->buf_pre_corr,
|
||||
s->bytes_overlap * 2 + UNROLL_PADDING);
|
||||
s->table_window = realloc(s->table_window,
|
||||
s->bytes_overlap * 2 - nch * bps * 2);
|
||||
if (!s->buf_pre_corr || !s->table_window) {
|
||||
MP_FATAL(af, "Out of memory\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
memset((char *)s->buf_pre_corr + s->bytes_overlap * 2, 0,
|
||||
UNROLL_PADDING);
|
||||
int32_t *pw = s->table_window;
|
||||
for (int i = 1; i < frames_overlap; i++) {
|
||||
int32_t v = (i * (t - i) * n) >> 15;
|
||||
for (int j = 0; j < nch; j++)
|
||||
*pw++ = v;
|
||||
}
|
||||
s->best_overlap_offset = best_overlap_offset_s16;
|
||||
} else {
|
||||
s->buf_pre_corr = realloc(s->buf_pre_corr, s->bytes_overlap);
|
||||
s->table_window = realloc(s->table_window,
|
||||
s->bytes_overlap - nch * bps);
|
||||
if (!s->buf_pre_corr || !s->table_window) {
|
||||
MP_FATAL(af, "Out of memory\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
float *pw = s->table_window;
|
||||
for (int i = 1; i < frames_overlap; i++) {
|
||||
float v = i * (frames_overlap - i);
|
||||
for (int j = 0; j < nch; j++)
|
||||
*pw++ = v;
|
||||
}
|
||||
s->best_overlap_offset = best_overlap_offset_float;
|
||||
}
|
||||
}
|
||||
|
||||
s->bytes_per_frame = bps * nch;
|
||||
s->num_channels = nch;
|
||||
|
||||
s->bytes_queue = (s->frames_search + s->frames_stride + frames_overlap)
|
||||
* bps * nch;
|
||||
s->buf_queue = realloc(s->buf_queue, s->bytes_queue + UNROLL_PADDING);
|
||||
if (!s->buf_queue) {
|
||||
MP_FATAL(af, "Out of memory\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
s->bytes_queued = 0;
|
||||
s->bytes_to_slide = 0;
|
||||
|
||||
MP_DBG(af, ""
|
||||
"%.2f stride_in, %i stride_out, %i standing, "
|
||||
"%i overlap, %i search, %i queue, %s mode\n",
|
||||
s->frames_stride_scaled,
|
||||
(int)(s->bytes_stride / nch / bps),
|
||||
(int)(s->bytes_standing / nch / bps),
|
||||
(int)(s->bytes_overlap / nch / bps),
|
||||
s->frames_search,
|
||||
(int)(s->bytes_queue / nch / bps),
|
||||
(use_int ? "s16" : "float"));
|
||||
|
||||
return af_test_output(af, (struct mp_audio *)arg);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void reset(struct mp_filter *f)
|
||||
{
|
||||
struct priv *s = f->priv;
|
||||
|
||||
s->current_pts = MP_NOPTS_VALUE;
|
||||
s->bytes_queued = 0;
|
||||
s->bytes_to_slide = 0;
|
||||
s->frames_stride_error = 0;
|
||||
if (s->buf_overlap && s->bytes_overlap)
|
||||
case AF_CONTROL_SET_PLAYBACK_SPEED: {
|
||||
double speed = *(double *)arg;
|
||||
if (s->speed_opt & SCALE_TEMPO) {
|
||||
if (s->speed_opt & SCALE_PITCH)
|
||||
break;
|
||||
update_speed(af, speed);
|
||||
} else if (s->speed_opt & SCALE_PITCH) {
|
||||
update_speed(af, speed);
|
||||
break; // do not signal OK
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_RESET:
|
||||
s->bytes_queued = 0;
|
||||
s->bytes_to_slide = 0;
|
||||
s->frames_stride_error = 0;
|
||||
memset(s->buf_overlap, 0, s->bytes_overlap);
|
||||
TA_FREEP(&s->in);
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static void destroy(struct mp_filter *f)
|
||||
// Deallocate memory
|
||||
static void uninit(struct af_instance *af)
|
||||
{
|
||||
struct priv *s = f->priv;
|
||||
af_scaletempo_t *s = af->priv;
|
||||
free(s->buf_queue);
|
||||
free(s->buf_overlap);
|
||||
free(s->buf_pre_corr);
|
||||
free(s->table_blend);
|
||||
free(s->table_window);
|
||||
TA_FREEP(&s->in);
|
||||
mp_filter_free_children(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,
|
||||
};
|
||||
|
||||
static struct mp_filter *af_scaletempo_create(struct mp_filter *parent,
|
||||
void *options)
|
||||
// Allocate memory and set function pointers
|
||||
static int af_open(struct af_instance *af)
|
||||
{
|
||||
struct mp_filter *f = mp_filter_create(parent, &af_scaletempo_filter);
|
||||
if (!f) {
|
||||
talloc_free(options);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
mp_filter_add_pin(f, MP_PIN_IN, "in");
|
||||
mp_filter_add_pin(f, MP_PIN_OUT, "out");
|
||||
|
||||
struct priv *s = f->priv;
|
||||
s->opts = talloc_steal(s, options);
|
||||
s->speed = 1.0;
|
||||
s->cur_format = talloc_steal(s, mp_aframe_create());
|
||||
s->out_pool = mp_aframe_pool_create(s);
|
||||
|
||||
struct mp_autoconvert *conv = mp_autoconvert_create(f);
|
||||
if (!conv)
|
||||
abort();
|
||||
|
||||
mp_autoconvert_add_afmt(conv, AF_FORMAT_S16);
|
||||
mp_autoconvert_add_afmt(conv, AF_FORMAT_FLOAT);
|
||||
|
||||
mp_pin_connect(conv->f->pins[0], f->ppins[0]);
|
||||
s->in_pin = conv->f->pins[1];
|
||||
|
||||
return f;
|
||||
af->control = control;
|
||||
af->uninit = uninit;
|
||||
af->filter_frame = filter;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct f_opts
|
||||
#define OPT_BASE_STRUCT af_scaletempo_t
|
||||
|
||||
const struct mp_user_filter_entry af_scaletempo = {
|
||||
.desc = {
|
||||
.description = "Scale audio tempo while maintaining pitch",
|
||||
.name = "scaletempo",
|
||||
.priv_size = sizeof(OPT_BASE_STRUCT),
|
||||
.priv_defaults = &(const OPT_BASE_STRUCT) {
|
||||
.ms_stride = 60,
|
||||
.percent_overlap = .20,
|
||||
.ms_search = 14,
|
||||
.speed_opt = SCALE_TEMPO,
|
||||
.scale_nominal = 1.0,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
{"scale", OPT_FLOAT(scale_nominal), M_RANGE(0.01, DBL_MAX)},
|
||||
{"stride", OPT_FLOAT(ms_stride), M_RANGE(0.01, DBL_MAX)},
|
||||
{"overlap", OPT_FLOAT(percent_overlap), M_RANGE(0, 1)},
|
||||
{"search", OPT_FLOAT(ms_search), M_RANGE(0, DBL_MAX)},
|
||||
{"speed", OPT_CHOICE(speed_opt,
|
||||
{"pitch", SCALE_PITCH},
|
||||
{"tempo", SCALE_TEMPO},
|
||||
{"none", 0},
|
||||
{"both", SCALE_TEMPO | SCALE_PITCH})},
|
||||
{0}
|
||||
},
|
||||
const struct af_info af_info_scaletempo = {
|
||||
.info = "Scale audio tempo while maintaining pitch",
|
||||
.name = "scaletempo",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(af_scaletempo_t),
|
||||
.priv_defaults = &(const af_scaletempo_t) {
|
||||
.ms_stride = 60,
|
||||
.percent_overlap = .20,
|
||||
.ms_search = 14,
|
||||
.speed_opt = SCALE_TEMPO,
|
||||
.speed = 1.0,
|
||||
.scale_nominal = 1.0,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_FLOAT("scale", scale_nominal, M_OPT_MIN, .min = 0.01),
|
||||
OPT_FLOAT("stride", ms_stride, M_OPT_MIN, .min = 0.01),
|
||||
OPT_FLOAT("overlap", percent_overlap, M_OPT_RANGE, .min = 0, .max = 1),
|
||||
OPT_FLOAT("search", ms_search, M_OPT_MIN, .min = 0),
|
||||
OPT_CHOICE("speed", speed_opt, 0,
|
||||
({"pitch", SCALE_PITCH},
|
||||
{"tempo", SCALE_TEMPO},
|
||||
{"none", 0},
|
||||
{"both", SCALE_TEMPO | SCALE_PITCH})),
|
||||
{0}
|
||||
},
|
||||
.create = af_scaletempo_create,
|
||||
};
|
||||
@@ -1,245 +0,0 @@
|
||||
#include "audio/aframe.h"
|
||||
#include "audio/filter/af_scaletempo2_internals.h"
|
||||
#include "audio/format.h"
|
||||
#include "common/common.h"
|
||||
#include "filters/f_autoconvert.h"
|
||||
#include "filters/filter_internal.h"
|
||||
#include "filters/user_filters.h"
|
||||
#include "options/m_option.h"
|
||||
|
||||
struct priv {
|
||||
struct mp_scaletempo2 data;
|
||||
struct mp_pin *in_pin;
|
||||
struct mp_aframe *cur_format;
|
||||
struct mp_aframe_pool *out_pool;
|
||||
bool sent_final;
|
||||
struct mp_aframe *pending;
|
||||
bool initialized;
|
||||
double frame_delay;
|
||||
float speed;
|
||||
};
|
||||
|
||||
static bool init_scaletempo2(struct mp_filter *f);
|
||||
static void reset(struct mp_filter *f);
|
||||
|
||||
static void process(struct mp_filter *f)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
|
||||
if (!mp_pin_in_needs_data(f->ppins[1]))
|
||||
return;
|
||||
|
||||
while (!p->initialized || !p->pending ||
|
||||
!mp_scaletempo2_frames_available(&p->data))
|
||||
{
|
||||
bool eof = false;
|
||||
if (!p->pending || !mp_aframe_get_size(p->pending)) {
|
||||
struct mp_frame frame = mp_pin_out_read(p->in_pin);
|
||||
if (frame.type == MP_FRAME_AUDIO) {
|
||||
TA_FREEP(&p->pending);
|
||||
p->pending = frame.data;
|
||||
} else if (frame.type == MP_FRAME_EOF) {
|
||||
eof = true;
|
||||
} else if (frame.type) {
|
||||
MP_ERR(f, "unexpected frame type\n");
|
||||
goto error;
|
||||
} else {
|
||||
return; // no new data yet
|
||||
}
|
||||
}
|
||||
assert(p->pending || eof);
|
||||
|
||||
if (!p->initialized) {
|
||||
if (!p->pending) {
|
||||
mp_pin_in_write(f->ppins[1], MP_EOF_FRAME);
|
||||
return;
|
||||
}
|
||||
if (!init_scaletempo2(f))
|
||||
goto error;
|
||||
}
|
||||
|
||||
bool format_change =
|
||||
p->pending && !mp_aframe_config_equals(p->pending, p->cur_format);
|
||||
|
||||
bool final = format_change || eof;
|
||||
if (p->pending && !format_change && !p->sent_final) {
|
||||
int frame_size = mp_aframe_get_size(p->pending);
|
||||
uint8_t **planes = mp_aframe_get_data_ro(p->pending);
|
||||
int read = mp_scaletempo2_fill_input_buffer(&p->data,
|
||||
planes, frame_size, final);
|
||||
p->frame_delay += read;
|
||||
mp_aframe_skip_samples(p->pending, read);
|
||||
}
|
||||
p->sent_final |= final;
|
||||
|
||||
if (mp_scaletempo2_frames_available(&p->data)) {
|
||||
if (eof) {
|
||||
mp_pin_out_repeat_eof(p->in_pin); // drain more next time
|
||||
}
|
||||
} else if (final) {
|
||||
p->initialized = false;
|
||||
p->sent_final = false;
|
||||
if (eof) {
|
||||
mp_pin_in_write(f->ppins[1], MP_EOF_FRAME);
|
||||
return;
|
||||
} else if (format_change) {
|
||||
// go on with proper reinit on the next iteration
|
||||
p->initialized = false;
|
||||
p->sent_final = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert(p->pending);
|
||||
if (mp_scaletempo2_frames_available(&p->data)) {
|
||||
struct mp_aframe *out = mp_aframe_new_ref(p->cur_format);
|
||||
int out_samples = p->data.ola_hop_size;
|
||||
if (mp_aframe_pool_allocate(p->out_pool, out, out_samples) < 0) {
|
||||
talloc_free(out);
|
||||
goto error;
|
||||
}
|
||||
|
||||
mp_aframe_copy_attributes(out, p->pending);
|
||||
|
||||
uint8_t **planes = mp_aframe_get_data_rw(out);
|
||||
assert(planes);
|
||||
assert(mp_aframe_get_planes(out) == p->data.channels);
|
||||
|
||||
out_samples = mp_scaletempo2_fill_buffer(&p->data,
|
||||
(float**)planes, out_samples, p->speed);
|
||||
|
||||
double pts = mp_aframe_get_pts(p->pending);
|
||||
p->frame_delay -= out_samples * p->speed;
|
||||
|
||||
if (pts != MP_NOPTS_VALUE) {
|
||||
double delay = p->frame_delay / mp_aframe_get_effective_rate(out);
|
||||
mp_aframe_set_pts(out, pts - delay);
|
||||
}
|
||||
|
||||
mp_aframe_set_size(out, out_samples);
|
||||
mp_aframe_mul_speed(out, p->speed);
|
||||
mp_pin_in_write(f->ppins[1], MAKE_FRAME(MP_FRAME_AUDIO, out));
|
||||
}
|
||||
|
||||
return;
|
||||
error:
|
||||
mp_filter_internal_mark_failed(f);
|
||||
}
|
||||
|
||||
static bool init_scaletempo2(struct mp_filter *f)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
assert(p->pending);
|
||||
|
||||
if (mp_aframe_get_format(p->pending) != AF_FORMAT_FLOATP)
|
||||
return false;
|
||||
|
||||
mp_aframe_reset(p->cur_format);
|
||||
p->initialized = true;
|
||||
p->sent_final = false;
|
||||
p->frame_delay = 0;
|
||||
mp_aframe_config_copy(p->cur_format, p->pending);
|
||||
|
||||
mp_scaletempo2_init(&p->data, mp_aframe_get_channels(p->pending),
|
||||
mp_aframe_get_rate(p->pending));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool command(struct mp_filter *f, struct mp_filter_command *cmd)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
|
||||
switch (cmd->type) {
|
||||
case MP_FILTER_COMMAND_SET_SPEED:
|
||||
p->speed = cmd->speed;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void reset(struct mp_filter *f)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
mp_scaletempo2_reset(&p->data);
|
||||
p->frame_delay = 0;
|
||||
p->initialized = false;
|
||||
TA_FREEP(&p->pending);
|
||||
}
|
||||
|
||||
static void destroy(struct mp_filter *f)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
mp_scaletempo2_destroy(&p->data);
|
||||
talloc_free(p->pending);
|
||||
}
|
||||
|
||||
static const struct mp_filter_info af_scaletempo2_filter = {
|
||||
.name = "scaletempo2",
|
||||
.priv_size = sizeof(struct priv),
|
||||
.process = process,
|
||||
.command = command,
|
||||
.reset = reset,
|
||||
.destroy = destroy,
|
||||
};
|
||||
|
||||
static struct mp_filter *af_scaletempo2_create(
|
||||
struct mp_filter *parent, void *options)
|
||||
{
|
||||
struct mp_filter *f = mp_filter_create(parent, &af_scaletempo2_filter);
|
||||
if (!f) {
|
||||
talloc_free(options);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
mp_filter_add_pin(f, MP_PIN_IN, "in");
|
||||
mp_filter_add_pin(f, MP_PIN_OUT, "out");
|
||||
|
||||
struct priv *p = f->priv;
|
||||
p->data.opts = talloc_steal(p, options);
|
||||
p->speed = 1.0;
|
||||
p->cur_format = talloc_steal(p, mp_aframe_create());
|
||||
p->out_pool = mp_aframe_pool_create(p);
|
||||
p->pending = NULL;
|
||||
p->initialized = false;
|
||||
|
||||
struct mp_autoconvert *conv = mp_autoconvert_create(f);
|
||||
if (!conv)
|
||||
abort();
|
||||
|
||||
mp_autoconvert_add_afmt(conv, AF_FORMAT_FLOATP);
|
||||
|
||||
mp_pin_connect(conv->f->pins[0], f->ppins[0]);
|
||||
p->in_pin = conv->f->pins[1];
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct mp_scaletempo2_opts
|
||||
const struct mp_user_filter_entry af_scaletempo2 = {
|
||||
.desc = {
|
||||
.description = "Scale audio tempo while maintaining pitch"
|
||||
" (filter ported from chromium)",
|
||||
.name = "scaletempo2",
|
||||
.priv_size = sizeof(OPT_BASE_STRUCT),
|
||||
.priv_defaults = &(const OPT_BASE_STRUCT) {
|
||||
.min_playback_rate = 0.25,
|
||||
.max_playback_rate = 4.0,
|
||||
.ola_window_size_ms = 20,
|
||||
.wsola_search_interval_ms = 30,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
{"search-interval",
|
||||
OPT_FLOAT(wsola_search_interval_ms), M_RANGE(1, 1000)},
|
||||
{"window-size",
|
||||
OPT_FLOAT(ola_window_size_ms), M_RANGE(1, 1000)},
|
||||
{"min-speed",
|
||||
OPT_FLOAT(min_playback_rate), M_RANGE(0, FLT_MAX)},
|
||||
{"max-speed",
|
||||
OPT_FLOAT(max_playback_rate), M_RANGE(0, FLT_MAX)},
|
||||
{0}
|
||||
}
|
||||
},
|
||||
.create = af_scaletempo2_create,
|
||||
};
|
||||
@@ -1,724 +0,0 @@
|
||||
#include <float.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "audio/chmap.h"
|
||||
#include "audio/filter/af_scaletempo2_internals.h"
|
||||
|
||||
// Algorithm overview (from chromium):
|
||||
// Waveform Similarity Overlap-and-add (WSOLA).
|
||||
//
|
||||
// One WSOLA iteration
|
||||
//
|
||||
// 1) Extract |target_block| as input frames at indices
|
||||
// [|target_block_index|, |target_block_index| + |ola_window_size|).
|
||||
// Note that |target_block| is the "natural" continuation of the output.
|
||||
//
|
||||
// 2) Extract |search_block| as input frames at indices
|
||||
// [|search_block_index|,
|
||||
// |search_block_index| + |num_candidate_blocks| + |ola_window_size|).
|
||||
//
|
||||
// 3) Find a block within the |search_block| that is most similar
|
||||
// to |target_block|. Let |optimal_index| be the index of such block and
|
||||
// write it to |optimal_block|.
|
||||
//
|
||||
// 4) Update:
|
||||
// |optimal_block| = |transition_window| * |target_block| +
|
||||
// (1 - |transition_window|) * |optimal_block|.
|
||||
//
|
||||
// 5) Overlap-and-add |optimal_block| to the |wsola_output|.
|
||||
//
|
||||
// 6) Update:write
|
||||
|
||||
struct interval {
|
||||
int lo;
|
||||
int hi;
|
||||
};
|
||||
|
||||
static bool in_interval(int n, struct interval q)
|
||||
{
|
||||
return n >= q.lo && n <= q.hi;
|
||||
}
|
||||
|
||||
static float **realloc_2d(float **p, int x, int y)
|
||||
{
|
||||
float **array = realloc(p, sizeof(float*) * x + sizeof(float) * x * y);
|
||||
float* data = (float*) (array + x);
|
||||
for (int i = 0; i < x; ++i) {
|
||||
array[i] = data + i * y;
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
static void zero_2d(float **a, int x, int y)
|
||||
{
|
||||
memset(a + x, 0, sizeof(float) * x * y);
|
||||
}
|
||||
|
||||
static void zero_2d_partial(float **a, int x, int y)
|
||||
{
|
||||
for (int i = 0; i < x; ++i) {
|
||||
memset(a[i], 0, sizeof(float) * y);
|
||||
}
|
||||
}
|
||||
|
||||
// Energies of sliding windows of channels are interleaved.
|
||||
// The number windows is |input_frames| - (|frames_per_window| - 1), hence,
|
||||
// the method assumes |energy| must be, at least, of size
|
||||
// (|input_frames| - (|frames_per_window| - 1)) * |channels|.
|
||||
static void multi_channel_moving_block_energies(
|
||||
float **input, int input_frames, int channels,
|
||||
int frames_per_block, float *energy)
|
||||
{
|
||||
int num_blocks = input_frames - (frames_per_block - 1);
|
||||
|
||||
for (int k = 0; k < channels; ++k) {
|
||||
const float* input_channel = input[k];
|
||||
|
||||
energy[k] = 0;
|
||||
|
||||
// First block of channel |k|.
|
||||
for (int m = 0; m < frames_per_block; ++m) {
|
||||
energy[k] += input_channel[m] * input_channel[m];
|
||||
}
|
||||
|
||||
const float* slide_out = input_channel;
|
||||
const float* slide_in = input_channel + frames_per_block;
|
||||
for (int n = 1; n < num_blocks; ++n, ++slide_in, ++slide_out) {
|
||||
energy[k + n * channels] = energy[k + (n - 1) * channels]
|
||||
- *slide_out * *slide_out + *slide_in * *slide_in;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static float multi_channel_similarity_measure(
|
||||
const float* dot_prod_a_b,
|
||||
const float* energy_a, const float* energy_b,
|
||||
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);
|
||||
}
|
||||
return similarity_measure;
|
||||
}
|
||||
|
||||
// Dot-product of channels of two AudioBus. For each AudioBus an offset is
|
||||
// given. |dot_product[k]| is the dot-product of channel |k|. The caller should
|
||||
// allocate sufficient space for |dot_product|.
|
||||
static void multi_channel_dot_product(
|
||||
float **a, int frame_offset_a,
|
||||
float **b, int frame_offset_b,
|
||||
int channels,
|
||||
int num_frames, float *dot_product)
|
||||
{
|
||||
assert(frame_offset_a >= 0);
|
||||
assert(frame_offset_b >= 0);
|
||||
|
||||
memset(dot_product, 0, sizeof(*dot_product) * channels);
|
||||
for (int k = 0; k < channels; ++k) {
|
||||
const float* ch_a = a[k] + frame_offset_a;
|
||||
const float* ch_b = b[k] + frame_offset_b;
|
||||
for (int n = 0; n < num_frames; ++n) {
|
||||
dot_product[k] += *ch_a++ * *ch_b++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fit the curve f(x) = a * x^2 + b * x + c such that
|
||||
// f(-1) = y[0]
|
||||
// f(0) = y[1]
|
||||
// f(1) = y[2]
|
||||
// and return the maximum, assuming that y[0] <= y[1] >= y[2].
|
||||
static void quadratic_interpolation(
|
||||
const float* y_values, float* extremum, float* extremum_value)
|
||||
{
|
||||
float a = 0.5f * (y_values[2] + y_values[0]) - y_values[1];
|
||||
float b = 0.5f * (y_values[2] - y_values[0]);
|
||||
float c = y_values[1];
|
||||
|
||||
if (a == 0.f) {
|
||||
// The coordinates are colinear (within floating-point error).
|
||||
*extremum = 0;
|
||||
*extremum_value = y_values[1];
|
||||
} else {
|
||||
*extremum = -b / (2.f * a);
|
||||
*extremum_value = a * (*extremum) * (*extremum) + b * (*extremum) + c;
|
||||
}
|
||||
}
|
||||
|
||||
// Search a subset of all candid blocks. The search is performed every
|
||||
// |decimation| frames. This reduces complexity by a factor of about
|
||||
// 1 / |decimation|. A cubic interpolation is used to have a better estimate of
|
||||
// the best match.
|
||||
static int decimated_search(
|
||||
int decimation, struct interval exclude_interval,
|
||||
float **target_block, int target_block_frames,
|
||||
float **search_segment, int search_segment_frames,
|
||||
int channels,
|
||||
const float *energy_target_block, const float *energy_candidate_blocks)
|
||||
{
|
||||
int num_candidate_blocks = search_segment_frames - (target_block_frames - 1);
|
||||
float dot_prod [MP_NUM_CHANNELS];
|
||||
float similarity[3]; // Three elements for cubic interpolation.
|
||||
|
||||
int n = 0;
|
||||
multi_channel_dot_product(
|
||||
target_block, 0,
|
||||
search_segment, n,
|
||||
channels,
|
||||
target_block_frames, dot_prod);
|
||||
similarity[0] = multi_channel_similarity_measure(
|
||||
dot_prod, energy_target_block,
|
||||
&energy_candidate_blocks[n * channels], channels);
|
||||
|
||||
// Set the starting point as optimal point.
|
||||
float best_similarity = similarity[0];
|
||||
int optimal_index = 0;
|
||||
|
||||
n += decimation;
|
||||
if (n >= num_candidate_blocks) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
multi_channel_dot_product(
|
||||
target_block, 0,
|
||||
search_segment, n,
|
||||
channels,
|
||||
target_block_frames, dot_prod);
|
||||
similarity[1] = multi_channel_similarity_measure(
|
||||
dot_prod, energy_target_block,
|
||||
&energy_candidate_blocks[n * channels], channels);
|
||||
|
||||
n += decimation;
|
||||
if (n >= num_candidate_blocks) {
|
||||
// We cannot do any more sampling. Compare these two values and return the
|
||||
// optimal index.
|
||||
return similarity[1] > similarity[0] ? decimation : 0;
|
||||
}
|
||||
|
||||
for (; n < num_candidate_blocks; n += decimation) {
|
||||
multi_channel_dot_product(
|
||||
target_block, 0,
|
||||
search_segment, n,
|
||||
channels,
|
||||
target_block_frames, dot_prod);
|
||||
|
||||
similarity[2] = multi_channel_similarity_measure(
|
||||
dot_prod, energy_target_block,
|
||||
&energy_candidate_blocks[n * channels], channels);
|
||||
|
||||
if ((similarity[1] > similarity[0] && similarity[1] >= similarity[2]) ||
|
||||
(similarity[1] >= similarity[0] && similarity[1] > similarity[2]))
|
||||
{
|
||||
// A local maximum is found. Do a cubic interpolation for a better
|
||||
// estimate of candidate maximum.
|
||||
float normalized_candidate_index;
|
||||
float candidate_similarity;
|
||||
quadratic_interpolation(similarity, &normalized_candidate_index,
|
||||
&candidate_similarity);
|
||||
|
||||
int candidate_index = n - decimation
|
||||
+ (int)(normalized_candidate_index * decimation + 0.5f);
|
||||
if (candidate_similarity > best_similarity
|
||||
&& !in_interval(candidate_index, exclude_interval)) {
|
||||
optimal_index = candidate_index;
|
||||
best_similarity = candidate_similarity;
|
||||
}
|
||||
} else if (n + decimation >= num_candidate_blocks &&
|
||||
similarity[2] > best_similarity &&
|
||||
!in_interval(n, exclude_interval))
|
||||
{
|
||||
// If this is the end-point and has a better similarity-measure than
|
||||
// optimal, then we accept it as optimal point.
|
||||
optimal_index = n;
|
||||
best_similarity = similarity[2];
|
||||
}
|
||||
memmove(similarity, &similarity[1], 2 * sizeof(*similarity));
|
||||
}
|
||||
return optimal_index;
|
||||
}
|
||||
|
||||
// Search [|low_limit|, |high_limit|] of |search_segment| to find a block that
|
||||
// is most similar to |target_block|. |energy_target_block| is the energy of the
|
||||
// |target_block|. |energy_candidate_blocks| is the energy of all blocks within
|
||||
// |search_block|.
|
||||
static int full_search(
|
||||
int low_limit, int high_limit,
|
||||
struct interval exclude_interval,
|
||||
float **target_block, int target_block_frames,
|
||||
float **search_block, int search_block_frames,
|
||||
int channels,
|
||||
const float* energy_target_block,
|
||||
const float* energy_candidate_blocks)
|
||||
{
|
||||
// int block_size = target_block->frames;
|
||||
float dot_prod [sizeof(float) * MP_NUM_CHANNELS];
|
||||
|
||||
float best_similarity = -FLT_MAX;//FLT_MIN;
|
||||
int optimal_index = 0;
|
||||
|
||||
for (int n = low_limit; n <= high_limit; ++n) {
|
||||
if (in_interval(n, exclude_interval)) {
|
||||
continue;
|
||||
}
|
||||
multi_channel_dot_product(target_block, 0, search_block, n, channels,
|
||||
target_block_frames, dot_prod);
|
||||
|
||||
float similarity = multi_channel_similarity_measure(
|
||||
dot_prod, energy_target_block,
|
||||
&energy_candidate_blocks[n * channels], channels);
|
||||
|
||||
if (similarity > best_similarity) {
|
||||
best_similarity = similarity;
|
||||
optimal_index = n;
|
||||
}
|
||||
}
|
||||
|
||||
return optimal_index;
|
||||
}
|
||||
|
||||
// Find the index of the block, within |search_block|, that is most similar
|
||||
// to |target_block|. Obviously, the returned index is w.r.t. |search_block|.
|
||||
// |exclude_interval| is an interval that is excluded from the search.
|
||||
static int compute_optimal_index(
|
||||
float **search_block, int search_block_frames,
|
||||
float **target_block, int target_block_frames,
|
||||
float *energy_candidate_blocks,
|
||||
int channels,
|
||||
struct interval exclude_interval)
|
||||
{
|
||||
int num_candidate_blocks = search_block_frames - (target_block_frames - 1);
|
||||
|
||||
// This is a compromise between complexity reduction and search accuracy. I
|
||||
// don't have a proof that down sample of order 5 is optimal.
|
||||
// One can compute a decimation factor that minimizes complexity given
|
||||
// the size of |search_block| and |target_block|. However, my experiments
|
||||
// show the rate of missing the optimal index is significant.
|
||||
// This value is chosen heuristically based on experiments.
|
||||
const int search_decimation = 5;
|
||||
|
||||
float energy_target_block [MP_NUM_CHANNELS];
|
||||
// energy_candidate_blocks must have at least size
|
||||
// sizeof(float) * channels * num_candidate_blocks
|
||||
|
||||
// Energy of all candid frames.
|
||||
multi_channel_moving_block_energies(
|
||||
search_block,
|
||||
search_block_frames,
|
||||
channels,
|
||||
target_block_frames,
|
||||
energy_candidate_blocks);
|
||||
|
||||
// Energy of target frame.
|
||||
multi_channel_dot_product(
|
||||
target_block, 0,
|
||||
target_block, 0,
|
||||
channels,
|
||||
target_block_frames, energy_target_block);
|
||||
|
||||
int optimal_index = decimated_search(
|
||||
search_decimation, exclude_interval,
|
||||
target_block, target_block_frames,
|
||||
search_block, search_block_frames,
|
||||
channels,
|
||||
energy_target_block,
|
||||
energy_candidate_blocks);
|
||||
|
||||
int lim_low = MPMAX(0, optimal_index - search_decimation);
|
||||
int lim_high = MPMIN(num_candidate_blocks - 1,
|
||||
optimal_index + search_decimation);
|
||||
return full_search(
|
||||
lim_low, lim_high, exclude_interval,
|
||||
target_block, target_block_frames,
|
||||
search_block, search_block_frames,
|
||||
channels,
|
||||
energy_target_block, energy_candidate_blocks);
|
||||
}
|
||||
|
||||
static void peek_buffer(struct mp_scaletempo2 *p,
|
||||
int frames, int read_offset, int write_offset, float **dest)
|
||||
{
|
||||
assert(p->input_buffer_frames >= frames);
|
||||
for (int i = 0; i < p->channels; ++i) {
|
||||
memcpy(dest[i] + write_offset,
|
||||
p->input_buffer[i] + read_offset,
|
||||
frames * sizeof(float));
|
||||
}
|
||||
}
|
||||
|
||||
static void seek_buffer(struct mp_scaletempo2 *p, int frames)
|
||||
{
|
||||
assert(p->input_buffer_frames >= frames);
|
||||
p->input_buffer_frames -= frames;
|
||||
for (int i = 0; i < p->channels; ++i) {
|
||||
memmove(p->input_buffer[i], p->input_buffer[i] + frames,
|
||||
p->input_buffer_frames * sizeof(float));
|
||||
}
|
||||
}
|
||||
|
||||
static void read_buffer(struct mp_scaletempo2 *p, int frames, float **dest)
|
||||
{
|
||||
peek_buffer(p, frames, 0, 0, dest);
|
||||
seek_buffer(p, frames);
|
||||
}
|
||||
|
||||
static int write_completed_frames_to(struct mp_scaletempo2 *p,
|
||||
int requested_frames, int dest_offset, float **dest)
|
||||
{
|
||||
int rendered_frames = MPMIN(p->num_complete_frames, requested_frames);
|
||||
|
||||
if (rendered_frames == 0)
|
||||
return 0; // There is nothing to read from |wsola_output|, return.
|
||||
|
||||
for (int i = 0; i < p->channels; ++i) {
|
||||
memcpy(dest[i] + dest_offset, p->wsola_output[i],
|
||||
rendered_frames * sizeof(float));
|
||||
}
|
||||
|
||||
// Remove the frames which are read.
|
||||
int frames_to_move = p->wsola_output_size - rendered_frames;
|
||||
for (int k = 0; k < p->channels; ++k) {
|
||||
float *ch = p->wsola_output[k];
|
||||
memmove(ch, &ch[rendered_frames], sizeof(*ch) * frames_to_move);
|
||||
}
|
||||
p->num_complete_frames -= rendered_frames;
|
||||
return rendered_frames;
|
||||
}
|
||||
|
||||
static bool can_perform_wsola(struct mp_scaletempo2 *p)
|
||||
{
|
||||
const int search_block_size = p->num_candidate_blocks
|
||||
+ (p->ola_window_size - 1);
|
||||
return p->target_block_index + p->ola_window_size <= p->input_buffer_frames
|
||||
&& p->search_block_index + search_block_size <= p->input_buffer_frames;
|
||||
}
|
||||
|
||||
// number of frames needed until a wsola iteration can be performed
|
||||
static int frames_needed(struct mp_scaletempo2 *p)
|
||||
{
|
||||
return MPMAX(0, MPMAX(
|
||||
p->target_block_index + p->ola_window_size - p->input_buffer_frames,
|
||||
p->search_block_index + p->search_block_size - p->input_buffer_frames));
|
||||
}
|
||||
|
||||
int mp_scaletempo2_fill_input_buffer(struct mp_scaletempo2 *p,
|
||||
uint8_t **planes, int frame_size, bool final)
|
||||
{
|
||||
int needed = frames_needed(p);
|
||||
int read = MPMIN(needed, frame_size);
|
||||
int total_fill = final ? needed : read;
|
||||
if (total_fill == 0) return 0;
|
||||
|
||||
assert(total_fill + p->input_buffer_frames <= p->input_buffer_size);
|
||||
|
||||
for (int i = 0; i < p->channels; ++i) {
|
||||
memcpy(p->input_buffer[i] + p->input_buffer_frames,
|
||||
planes[i], read * sizeof(float));
|
||||
for (int j = read; j < total_fill; ++j) {
|
||||
p->input_buffer[p->input_buffer_frames + j] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
p->input_buffer_frames += total_fill;
|
||||
return read;
|
||||
}
|
||||
|
||||
static bool target_is_within_search_region(struct mp_scaletempo2 *p)
|
||||
{
|
||||
const int search_block_size = p->num_candidate_blocks + (p->ola_window_size - 1);
|
||||
|
||||
return p->target_block_index >= p->search_block_index
|
||||
&& p->target_block_index + p->ola_window_size
|
||||
<= p->search_block_index + search_block_size;
|
||||
}
|
||||
|
||||
|
||||
static void peek_audio_with_zero_prepend(struct mp_scaletempo2 *p,
|
||||
int read_offset_frames, float **dest, int dest_frames)
|
||||
{
|
||||
assert(read_offset_frames + dest_frames <= p->input_buffer_frames);
|
||||
|
||||
int write_offset = 0;
|
||||
int num_frames_to_read = dest_frames;
|
||||
if (read_offset_frames < 0) {
|
||||
int num_zero_frames_appended = MPMIN(
|
||||
-read_offset_frames, num_frames_to_read);
|
||||
read_offset_frames = 0;
|
||||
num_frames_to_read -= num_zero_frames_appended;
|
||||
write_offset = num_zero_frames_appended;
|
||||
zero_2d_partial(dest, p->channels, num_zero_frames_appended);
|
||||
}
|
||||
peek_buffer(p, num_frames_to_read, read_offset_frames, write_offset, dest);
|
||||
}
|
||||
|
||||
static void get_optimal_block(struct mp_scaletempo2 *p)
|
||||
{
|
||||
int optimal_index = 0;
|
||||
|
||||
// An interval around last optimal block which is excluded from the search.
|
||||
// This is to reduce the buzzy sound. The number 160 is rather arbitrary and
|
||||
// derived heuristically.
|
||||
const int exclude_interval_length_frames = 160;
|
||||
if (target_is_within_search_region(p)) {
|
||||
optimal_index = p->target_block_index;
|
||||
peek_audio_with_zero_prepend(p,
|
||||
optimal_index, p->optimal_block, p->ola_window_size);
|
||||
} else {
|
||||
peek_audio_with_zero_prepend(p,
|
||||
p->target_block_index, p->target_block, p->ola_window_size);
|
||||
peek_audio_with_zero_prepend(p,
|
||||
p->search_block_index, p->search_block, p->search_block_size);
|
||||
int last_optimal = p->target_block_index
|
||||
- p->ola_hop_size - p->search_block_index;
|
||||
struct interval exclude_iterval = {
|
||||
.lo = last_optimal - exclude_interval_length_frames / 2,
|
||||
.hi = last_optimal + exclude_interval_length_frames / 2
|
||||
};
|
||||
|
||||
// |optimal_index| is in frames and it is relative to the beginning of the
|
||||
// |search_block|.
|
||||
optimal_index = compute_optimal_index(
|
||||
p->search_block, p->search_block_size,
|
||||
p->target_block, p->ola_window_size,
|
||||
p->energy_candidate_blocks,
|
||||
p->channels,
|
||||
exclude_iterval);
|
||||
|
||||
// Translate |index| w.r.t. the beginning of |audio_buffer| and extract the
|
||||
// optimal block.
|
||||
optimal_index += p->search_block_index;
|
||||
peek_audio_with_zero_prepend(p,
|
||||
optimal_index, p->optimal_block, p->ola_window_size);
|
||||
|
||||
// Make a transition from target block to the optimal block if different.
|
||||
// Target block has the best continuation to the current output.
|
||||
// Optimal block is the most similar block to the target, however, it might
|
||||
// introduce some discontinuity when over-lap-added. Therefore, we combine
|
||||
// them for a smoother transition. The length of transition window is twice
|
||||
// as that of the optimal-block which makes it like a weighting function
|
||||
// where target-block has higher weight close to zero (weight of 1 at index
|
||||
// 0) and lower weight close the end.
|
||||
for (int k = 0; k < p->channels; ++k) {
|
||||
float* ch_opt = p->optimal_block[k];
|
||||
float* ch_target = p->target_block[k];
|
||||
for (int n = 0; n < p->ola_window_size; ++n) {
|
||||
ch_opt[n] = ch_opt[n] * p->transition_window[n]
|
||||
+ ch_target[n] * p->transition_window[p->ola_window_size + n];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Next target is one hop ahead of the current optimal.
|
||||
p->target_block_index = optimal_index + p->ola_hop_size;
|
||||
}
|
||||
|
||||
static void update_output_time(struct mp_scaletempo2 *p,
|
||||
float playback_rate, double time_change)
|
||||
{
|
||||
p->output_time += time_change;
|
||||
// Center of the search region, in frames.
|
||||
int search_block_center_index = (int)(p->output_time * playback_rate + 0.5);
|
||||
p->search_block_index = search_block_center_index
|
||||
- p->search_block_center_offset;
|
||||
}
|
||||
|
||||
static void remove_old_input_frames(struct mp_scaletempo2 *p, float playback_rate)
|
||||
{
|
||||
const int earliest_used_index = MPMIN(
|
||||
p->target_block_index, p->search_block_index);
|
||||
if (earliest_used_index <= 0)
|
||||
return; // Nothing to remove.
|
||||
|
||||
// Remove frames from input and adjust indices accordingly.
|
||||
seek_buffer(p, earliest_used_index);
|
||||
p->target_block_index -= earliest_used_index;
|
||||
|
||||
// Adjust output index.
|
||||
double output_time_change = ((double) earliest_used_index) / playback_rate;
|
||||
assert(p->output_time >= output_time_change);
|
||||
update_output_time(p, playback_rate, -output_time_change);
|
||||
}
|
||||
|
||||
static bool run_one_wsola_iteration(struct mp_scaletempo2 *p, float playback_rate)
|
||||
{
|
||||
if (!can_perform_wsola(p)){
|
||||
return false;
|
||||
}
|
||||
|
||||
get_optimal_block(p);
|
||||
|
||||
// Overlap-and-add.
|
||||
for (int k = 0; k < p->channels; ++k) {
|
||||
float* ch_opt_frame = p->optimal_block[k];
|
||||
float* ch_output = p->wsola_output[k] + p->num_complete_frames;
|
||||
for (int n = 0; n < p->ola_hop_size; ++n) {
|
||||
ch_output[n] = ch_output[n] * p->ola_window[p->ola_hop_size + n] +
|
||||
ch_opt_frame[n] * p->ola_window[n];
|
||||
}
|
||||
|
||||
// Copy the second half to the output.
|
||||
memcpy(&ch_output[p->ola_hop_size], &ch_opt_frame[p->ola_hop_size],
|
||||
sizeof(*ch_opt_frame) * p->ola_hop_size);
|
||||
}
|
||||
|
||||
p->num_complete_frames += p->ola_hop_size;
|
||||
update_output_time(p, playback_rate, p->ola_hop_size);
|
||||
remove_old_input_frames(p, playback_rate);
|
||||
return true;
|
||||
}
|
||||
|
||||
int mp_scaletempo2_fill_buffer(struct mp_scaletempo2 *p,
|
||||
float **dest, int dest_size, float playback_rate)
|
||||
{
|
||||
if (playback_rate == 0) return 0;
|
||||
|
||||
// Optimize the muted case to issue a single clear instead of performing
|
||||
// the full crossfade and clearing each crossfaded frame.
|
||||
if (playback_rate < p->opts->min_playback_rate
|
||||
|| (playback_rate > p->opts->max_playback_rate
|
||||
&& p->opts->max_playback_rate > 0))
|
||||
{
|
||||
int frames_to_render = MPMIN(dest_size,
|
||||
(int) (p->input_buffer_frames / playback_rate));
|
||||
|
||||
// Compute accurate number of frames to actually skip in the source data.
|
||||
// Includes the leftover partial frame from last request. However, we can
|
||||
// only skip over complete frames, so a partial frame may remain for next
|
||||
// time.
|
||||
p->muted_partial_frame += frames_to_render * playback_rate;
|
||||
int seek_frames = (int) (p->muted_partial_frame);
|
||||
zero_2d_partial(dest, p->channels, frames_to_render);
|
||||
seek_buffer(p, seek_frames);
|
||||
|
||||
// Determine the partial frame that remains to be skipped for next call. If
|
||||
// the user switches back to playing, it may be off time by this partial
|
||||
// frame, which would be undetectable. If they subsequently switch to
|
||||
// another playback rate that mutes, the code will attempt to line up the
|
||||
// frames again.
|
||||
p->muted_partial_frame -= seek_frames;
|
||||
return frames_to_render;
|
||||
}
|
||||
|
||||
int slower_step = (int) ceilf(p->ola_window_size * playback_rate);
|
||||
int faster_step = (int) ceilf(p->ola_window_size / playback_rate);
|
||||
|
||||
// Optimize the most common |playback_rate| ~= 1 case to use a single copy
|
||||
// instead of copying frame by frame.
|
||||
if (p->ola_window_size <= faster_step && slower_step >= p->ola_window_size) {
|
||||
int frames_to_copy = MPMIN(dest_size, p->input_buffer_frames);
|
||||
read_buffer(p, frames_to_copy, dest);
|
||||
return frames_to_copy;
|
||||
}
|
||||
|
||||
int rendered_frames = 0;
|
||||
do {
|
||||
rendered_frames += write_completed_frames_to(p,
|
||||
dest_size - rendered_frames, rendered_frames, dest);
|
||||
} while (rendered_frames < dest_size
|
||||
&& run_one_wsola_iteration(p, playback_rate));
|
||||
return rendered_frames;
|
||||
}
|
||||
|
||||
bool mp_scaletempo2_frames_available(struct mp_scaletempo2 *p)
|
||||
{
|
||||
return can_perform_wsola(p) || p->num_complete_frames > 0;
|
||||
}
|
||||
|
||||
void mp_scaletempo2_destroy(struct mp_scaletempo2 *p)
|
||||
{
|
||||
free(p->ola_window);
|
||||
free(p->transition_window);
|
||||
free(p->wsola_output);
|
||||
free(p->optimal_block);
|
||||
free(p->search_block);
|
||||
free(p->target_block);
|
||||
free(p->input_buffer);
|
||||
free(p->energy_candidate_blocks);
|
||||
}
|
||||
|
||||
void mp_scaletempo2_reset(struct mp_scaletempo2 *p)
|
||||
{
|
||||
p->input_buffer_frames = 0;
|
||||
p->output_time = 0.0;
|
||||
p->search_block_index = 0;
|
||||
p->target_block_index = 0;
|
||||
// Clear the queue of decoded packets.
|
||||
zero_2d(p->wsola_output, p->channels, p->wsola_output_size);
|
||||
p->num_complete_frames = 0;
|
||||
}
|
||||
|
||||
// Return a "periodic" Hann window. This is the first L samples of an L+1
|
||||
// Hann window. It is perfect reconstruction for overlap-and-add.
|
||||
static void get_symmetric_hanning_window(int window_length, float* window)
|
||||
{
|
||||
const float scale = 2.0f * M_PI / window_length;
|
||||
for (int n = 0; n < window_length; ++n)
|
||||
window[n] = 0.5f * (1.0f - cosf(n * scale));
|
||||
}
|
||||
|
||||
|
||||
void mp_scaletempo2_init(struct mp_scaletempo2 *p, int channels, int rate)
|
||||
{
|
||||
p->muted_partial_frame = 0;
|
||||
p->output_time = 0;
|
||||
p->search_block_center_offset = 0;
|
||||
p->search_block_index = 0;
|
||||
p->num_complete_frames = 0;
|
||||
p->channels = channels;
|
||||
|
||||
p->samples_per_second = rate;
|
||||
p->num_candidate_blocks = (int)(p->opts->wsola_search_interval_ms
|
||||
* p->samples_per_second / 1000);
|
||||
p->ola_window_size = (int)(p->opts->ola_window_size_ms
|
||||
* p->samples_per_second / 1000);
|
||||
// Make sure window size in an even number.
|
||||
p->ola_window_size += p->ola_window_size & 1;
|
||||
p->ola_hop_size = p->ola_window_size / 2;
|
||||
// |num_candidate_blocks| / 2 is the offset of the center of the search
|
||||
// block to the center of the first (left most) candidate block. The offset
|
||||
// of the center of a candidate block to its left most point is
|
||||
// |ola_window_size| / 2 - 1. Note that |ola_window_size| is even and in
|
||||
// our convention the center belongs to the left half, so we need to subtract
|
||||
// one frame to get the correct offset.
|
||||
//
|
||||
// Search Block
|
||||
// <------------------------------------------->
|
||||
//
|
||||
// |ola_window_size| / 2 - 1
|
||||
// <----
|
||||
//
|
||||
// |num_candidate_blocks| / 2
|
||||
// <----------------
|
||||
// center
|
||||
// X----X----------------X---------------X-----X
|
||||
// <----------> <---------->
|
||||
// Candidate ... Candidate
|
||||
// 1, ... |num_candidate_blocks|
|
||||
p->search_block_center_offset = p->num_candidate_blocks / 2
|
||||
+ (p->ola_window_size / 2 - 1);
|
||||
p->ola_window = realloc(p->ola_window, sizeof(float) * p->ola_window_size);
|
||||
get_symmetric_hanning_window(p->ola_window_size, p->ola_window);
|
||||
p->transition_window = realloc(p->transition_window,
|
||||
sizeof(float) * p->ola_window_size * 2);
|
||||
get_symmetric_hanning_window(2 * p->ola_window_size, p->transition_window);
|
||||
|
||||
p->wsola_output_size = p->ola_window_size + p->ola_hop_size;
|
||||
p->wsola_output = realloc_2d(p->wsola_output, p->channels, p->wsola_output_size);
|
||||
// Initialize for overlap-and-add of the first block.
|
||||
zero_2d(p->wsola_output, p->channels, p->wsola_output_size);
|
||||
|
||||
// Auxiliary containers.
|
||||
p->optimal_block = realloc_2d(p->optimal_block, p->channels, p->ola_window_size);
|
||||
p->search_block_size = p->num_candidate_blocks + (p->ola_window_size - 1);
|
||||
p->search_block = realloc_2d(p->search_block, p->channels, p->search_block_size);
|
||||
p->target_block = realloc_2d(p->target_block, p->channels, p->ola_window_size);
|
||||
|
||||
p->input_buffer_size = 4 * MPMAX(p->ola_window_size, p->search_block_size);
|
||||
p->input_buffer = realloc_2d(p->input_buffer, p->channels, p->input_buffer_size);
|
||||
p->input_buffer_frames = 0;
|
||||
|
||||
p->energy_candidate_blocks = realloc(p->energy_candidate_blocks,
|
||||
sizeof(float) * p->channels * p->num_candidate_blocks);
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
// This filter was ported from Chromium
|
||||
// (https://chromium.googlesource.com/chromium/chromium/+/51ed77e3f37a9a9b80d6d0a8259e84a8ca635259/media/filters/audio_renderer_algorithm.cc)
|
||||
//
|
||||
// Copyright 2015 The Chromium Authors. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include "common/common.h"
|
||||
|
||||
struct mp_scaletempo2_opts {
|
||||
// Max/min supported playback rates for fast/slow audio. Audio outside of these
|
||||
// ranges are muted.
|
||||
// Audio at these speeds would sound better under a frequency domain algorithm.
|
||||
float min_playback_rate;
|
||||
float max_playback_rate;
|
||||
// Overlap-and-add window size in milliseconds.
|
||||
float ola_window_size_ms;
|
||||
// Size of search interval in milliseconds. The search interval is
|
||||
// [-delta delta] around |output_index| * |playback_rate|. So the search
|
||||
// interval is 2 * delta.
|
||||
float wsola_search_interval_ms;
|
||||
};
|
||||
|
||||
struct mp_scaletempo2 {
|
||||
struct mp_scaletempo2_opts *opts;
|
||||
// Number of channels in audio stream.
|
||||
int channels;
|
||||
// Sample rate of audio stream.
|
||||
int samples_per_second;
|
||||
// If muted, keep track of partial frames that should have been skipped over.
|
||||
double muted_partial_frame;
|
||||
// Book keeping of the current time of generated audio, in frames. This
|
||||
// should be appropriately updated when out samples are generated, regardless
|
||||
// of whether we push samples out when fill_buffer() is called or we store
|
||||
// audio in |wsola_output| for the subsequent calls to fill_buffer().
|
||||
// Furthermore, if samples from |audio_buffer| are evicted then this
|
||||
// member variable should be updated based on |playback_rate|.
|
||||
// Note that this member should be updated ONLY by calling update_output_time(),
|
||||
// so that |search_block_index| is update accordingly.
|
||||
double output_time;
|
||||
// The offset of the center frame of |search_block| w.r.t. its first frame.
|
||||
int search_block_center_offset;
|
||||
// Index of the beginning of the |search_block|, in frames.
|
||||
int search_block_index;
|
||||
// Number of Blocks to search to find the most similar one to the target
|
||||
// frame.
|
||||
int num_candidate_blocks;
|
||||
// Index of the beginning of the target block, counted in frames.
|
||||
int target_block_index;
|
||||
// Overlap-and-add window size in frames.
|
||||
int ola_window_size;
|
||||
// The hop size of overlap-and-add in frames. This implementation assumes 50%
|
||||
// overlap-and-add.
|
||||
int ola_hop_size;
|
||||
// Number of frames in |wsola_output| that overlap-and-add is completed for
|
||||
// them and can be copied to output if fill_buffer() is called. It also
|
||||
// specifies the index where the next WSOLA window has to overlap-and-add.
|
||||
int num_complete_frames;
|
||||
// Overlap-and-add window.
|
||||
float *ola_window;
|
||||
// Transition window, used to update |optimal_block| by a weighted sum of
|
||||
// |optimal_block| and |target_block|.
|
||||
float *transition_window;
|
||||
// This stores a part of the output that is created but couldn't be rendered.
|
||||
// Output is generated frame-by-frame which at some point might exceed the
|
||||
// number of requested samples. Furthermore, due to overlap-and-add,
|
||||
// the last half-window of the output is incomplete, which is stored in this
|
||||
// buffer.
|
||||
float **wsola_output;
|
||||
int wsola_output_size;
|
||||
// Auxiliary variables to avoid allocation in every iteration.
|
||||
// Stores the optimal block in every iteration. This is the most
|
||||
// similar block to |target_block| within |search_block| and it is
|
||||
// overlap-and-added to |wsola_output|.
|
||||
float **optimal_block;
|
||||
// A block of data that search is performed over to find the |optimal_block|.
|
||||
float **search_block;
|
||||
int search_block_size;
|
||||
// Stores the target block, denoted as |target| above. |search_block| is
|
||||
// searched for a block (|optimal_block|) that is most similar to
|
||||
// |target_block|.
|
||||
float **target_block;
|
||||
// Buffered audio data.
|
||||
float **input_buffer;
|
||||
int input_buffer_size;
|
||||
int input_buffer_frames;
|
||||
float *energy_candidate_blocks;
|
||||
};
|
||||
|
||||
void mp_scaletempo2_destroy(struct mp_scaletempo2 *p);
|
||||
void mp_scaletempo2_reset(struct mp_scaletempo2 *p);
|
||||
void mp_scaletempo2_init(struct mp_scaletempo2 *p, int channels, int rate);
|
||||
int mp_scaletempo2_fill_input_buffer(struct mp_scaletempo2 *p,
|
||||
uint8_t **planes, int frame_size, bool final);
|
||||
int mp_scaletempo2_fill_buffer(struct mp_scaletempo2 *p,
|
||||
float **dest, int dest_size, float playback_rate);
|
||||
bool mp_scaletempo2_frames_available(struct mp_scaletempo2 *p);
|
||||
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* Copyright (C)2002 Anders Johansson ajh@atri.curtin.edu.au
|
||||
*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <math.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "af.h"
|
||||
#include "demux/demux.h"
|
||||
|
||||
struct priv {
|
||||
float level; // User-specified gain level for each channel
|
||||
float rgain; // Replaygain level
|
||||
int rgain_track; // Enable/disable track based replaygain
|
||||
int rgain_album; // Enable/disable album based replaygain
|
||||
float rgain_preamp; // Set replaygain pre-amplification
|
||||
int rgain_clip; // Enable/disable clipping prevention
|
||||
float replaygain_fallback;
|
||||
int soft; // Enable/disable soft clipping
|
||||
int fast; // Use fix-point volume control
|
||||
int detach; // Detach if gain volume is neutral
|
||||
float cfg_volume;
|
||||
int warn;
|
||||
};
|
||||
|
||||
// Convert to gain value from dB. input <= -200dB will become 0 gain.
|
||||
static float from_dB(float in, float k, float mi, float ma)
|
||||
{
|
||||
if (in <= -200)
|
||||
return 0.0;
|
||||
return pow(10.0, MPCLAMP(in, mi, ma) / k);
|
||||
}
|
||||
|
||||
static int control(struct af_instance *af, int cmd, void *arg)
|
||||
{
|
||||
struct priv *s = af->priv;
|
||||
|
||||
switch (cmd) {
|
||||
case AF_CONTROL_REINIT: {
|
||||
struct mp_audio *in = arg;
|
||||
|
||||
mp_audio_copy_config(af->data, in);
|
||||
mp_audio_force_interleaved_format(af->data);
|
||||
|
||||
if (s->fast && af_fmt_from_planar(in->format) != AF_FORMAT_FLOAT) {
|
||||
mp_audio_set_format(af->data, AF_FORMAT_S16);
|
||||
} else {
|
||||
mp_audio_set_format(af->data, AF_FORMAT_FLOAT);
|
||||
}
|
||||
if (af_fmt_is_planar(in->format))
|
||||
mp_audio_set_format(af->data, af_fmt_to_planar(af->data->format));
|
||||
s->rgain = 1.0;
|
||||
struct replaygain_data *rg = af->replaygain_data;
|
||||
if ((s->rgain_track || s->rgain_album) && rg) {
|
||||
MP_VERBOSE(af, "Replaygain: Track=%f/%f Album=%f/%f\n",
|
||||
rg->track_gain, rg->track_peak,
|
||||
rg->album_gain, rg->album_peak);
|
||||
|
||||
float gain, peak;
|
||||
if (s->rgain_track) {
|
||||
gain = rg->track_gain;
|
||||
peak = rg->track_peak;
|
||||
} else {
|
||||
gain = rg->album_gain;
|
||||
peak = rg->album_peak;
|
||||
}
|
||||
|
||||
gain += s->rgain_preamp;
|
||||
s->rgain = from_dB(gain, 20.0, -200.0, 60.0);
|
||||
|
||||
MP_VERBOSE(af, "Applying replay-gain: %f\n", s->rgain);
|
||||
|
||||
if (!s->rgain_clip) { // clipping prevention
|
||||
s->rgain = MPMIN(s->rgain, 1.0 / peak);
|
||||
MP_VERBOSE(af, "...with clipping prevention: %f\n", s->rgain);
|
||||
}
|
||||
} else if (s->replaygain_fallback) {
|
||||
s->rgain = from_dB(s->replaygain_fallback, 20.0, -200.0, 60.0);
|
||||
MP_VERBOSE(af, "Applying fallback gain: %f\n", s->rgain);
|
||||
}
|
||||
if (s->detach && fabs(s->level * s->rgain - 1.0) < 0.00001)
|
||||
return AF_DETACH;
|
||||
return af_test_output(af, in);
|
||||
}
|
||||
case AF_CONTROL_SET_VOLUME:
|
||||
s->level = *(float *)arg;
|
||||
MP_VERBOSE(af, "volume gain: %f\n", s->level);
|
||||
return AF_OK;
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static void filter_plane(struct af_instance *af, struct mp_audio *data, int p)
|
||||
{
|
||||
struct priv *s = af->priv;
|
||||
|
||||
float level = s->level * s->rgain * from_dB(s->cfg_volume, 20.0, -200.0, 60.0);
|
||||
int num_samples = data->samples * data->spf;
|
||||
|
||||
if (af_fmt_from_planar(af->data->format) == AF_FORMAT_S16) {
|
||||
int vol = 256.0 * level;
|
||||
if (vol != 256) {
|
||||
if (af_make_writeable(af, data) < 0)
|
||||
return; // oom
|
||||
int16_t *a = data->planes[p];
|
||||
for (int i = 0; i < num_samples; i++) {
|
||||
int x = (a[i] * vol) >> 8;
|
||||
a[i] = MPCLAMP(x, SHRT_MIN, SHRT_MAX);
|
||||
}
|
||||
}
|
||||
} else if (af_fmt_from_planar(af->data->format) == AF_FORMAT_FLOAT) {
|
||||
float vol = level;
|
||||
if (vol != 1.0) {
|
||||
if (af_make_writeable(af, data) < 0)
|
||||
return; // oom
|
||||
float *a = data->planes[p];
|
||||
for (int i = 0; i < num_samples; i++) {
|
||||
float x = a[i] * vol;
|
||||
a[i] = s->soft ? af_softclip(x) : MPCLAMP(x, -1.0, 1.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int filter(struct af_instance *af, struct mp_audio *data)
|
||||
{
|
||||
if (data) {
|
||||
for (int n = 0; n < data->num_planes; n++)
|
||||
filter_plane(af, data, n);
|
||||
af_add_output_frame(af, data);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int af_open(struct af_instance *af)
|
||||
{
|
||||
struct priv *s = af->priv;
|
||||
if (s->warn)
|
||||
MP_WARN(af, "This filter is deprecated. Use --volume directly.\n");
|
||||
af->control = control;
|
||||
af->filter_frame = filter;
|
||||
s->level = 1.0;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
// Description of this filter
|
||||
const struct af_info af_info_volume = {
|
||||
.info = "Volume control audio filter",
|
||||
.name = "volume",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_FLOATRANGE("volumedb", cfg_volume, 0, -200, 60),
|
||||
OPT_FLAG("replaygain-track", rgain_track, 0),
|
||||
OPT_FLAG("replaygain-album", rgain_album, 0),
|
||||
OPT_FLOATRANGE("replaygain-preamp", rgain_preamp, 0, -15, 15),
|
||||
OPT_FLAG("replaygain-clip", rgain_clip, 0),
|
||||
OPT_FLOATRANGE("replaygain-fallback", replaygain_fallback, 0, -200, 60),
|
||||
OPT_FLAG("softclip", soft, 0),
|
||||
OPT_FLAG("s16", fast, 0),
|
||||
OPT_FLAG("detach", detach, 0),
|
||||
OPT_FLAG("warn", warn, 0, OPTDEF_INT(1)),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2002 Anders Johansson ajh@atri.curtin.edu.au
|
||||
*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MPLAYER_EQUALIZER_H
|
||||
#define MPLAYER_EQUALIZER_H
|
||||
|
||||
/* Equalizer plugin header file defines struct used for setting or
|
||||
getting the gain of a specific channel and frequency */
|
||||
|
||||
typedef struct equalizer_s
|
||||
{
|
||||
float gain; // Gain in dB -15 - 15
|
||||
int channel; // Channel number 0 - 5
|
||||
int band; // Frequency band 0 - 9
|
||||
}equalizer_t;
|
||||
|
||||
/* The different frequency bands are:
|
||||
nr. center frequency
|
||||
0 31.25 Hz
|
||||
1 62.50 Hz
|
||||
2 125.0 Hz
|
||||
3 250.0 Hz
|
||||
4 500.0 Hz
|
||||
5 1.000 kHz
|
||||
6 2.000 kHz
|
||||
7 4.000 kHz
|
||||
8 8.000 kHz
|
||||
9 16.00 kHz
|
||||
*/
|
||||
|
||||
#endif /* MPLAYER_EQUALIZER_H */
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "af.h"
|
||||
|
||||
/* Convert from ms to sample time */
|
||||
int af_from_ms(int n, float* in, int* out, int rate, float mi, float ma)
|
||||
{
|
||||
int i = 0;
|
||||
// Sanity check
|
||||
if(!in || !out)
|
||||
return AF_ERROR;
|
||||
|
||||
for(i=0;i<n;i++)
|
||||
out[i]=(int)((float)rate * MPCLAMP(in[i],mi,ma)/1000.0);
|
||||
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* test if output format matches
|
||||
* af: audio filter
|
||||
* out: needed format, will be overwritten by available
|
||||
* format if they do not match
|
||||
* returns: AF_FALSE if formats do not match, AF_OK if they match
|
||||
*
|
||||
* compares the format, rate and nch values of af->data with out
|
||||
* Note: logically, *out=*af->data always happens, because out contains the
|
||||
* format only, no actual audio data or memory allocations. *out always
|
||||
* contains the parameters from af->data after the function returns.
|
||||
*/
|
||||
int af_test_output(struct af_instance* af, struct mp_audio* out)
|
||||
{
|
||||
if((af->data->format != out->format) ||
|
||||
(af->data->bps != out->bps) ||
|
||||
(af->data->rate != out->rate) ||
|
||||
!mp_chmap_equals(&af->data->channels, &out->channels)){
|
||||
*out = *af->data;
|
||||
return AF_FALSE;
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
/* Soft clipping, the sound of a dream, thanks to Jon Wattes
|
||||
post to Musicdsp.org */
|
||||
float af_softclip(float a)
|
||||
{
|
||||
if (a >= M_PI/2)
|
||||
return 1.0;
|
||||
else if (a <= -M_PI/2)
|
||||
return -1.0;
|
||||
else
|
||||
return sin(a);
|
||||
}
|
||||
Loaded 100 of 694 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user