Compare commits

..
4 Commits
Author SHA1 Message Date
Kevin Mitchell 0d893bebdc Release 0.27.1 2018-02-10 04:45:10 -08:00
Ricardo Constantino 011b9dc46a ytdl_hook: fix safe url checking with EDL urls 2018-02-10 04:45:10 -08:00
Ricardo Constantino 83299b8382 ytdl_hook: move url_is_safe earlier in code
lua isn't javascript.
2018-02-10 04:45:09 -08:00
Ricardo Constantino 899ce94693 ytdl_hook: whitelist protocols from urls retrieved from youtube-dl
Not very clean since there's a lot of potential unsafe urls that youtube-dl
can give us, depending on whether it's a single url, split tracks,
playlists, segmented dash, etc.
2018-02-10 04:45:03 -08:00
694 changed files with 67606 additions and 98633 deletions

No files matched your search

+25
View File
@@ -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.)
-16
View File
@@ -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.
-43
View File
@@ -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.
-25
View File
@@ -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.
-9
View File
@@ -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
+6 -4
View File
@@ -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
View File
@@ -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
+321 -44
View File
@@ -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
View File
@@ -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.)
-177
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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.
+4 -6
View File
@@ -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
-113
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large. Load diff
+10 -129
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large. Load diff
+865 -2387
View File
File diff suppressed because it is too large. Load diff
+4 -94
View File
@@ -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
~~~~~~~~~~~~~~~
-221
View File
@@ -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
View File
@@ -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
View File
@@ -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.
+5 -4
View File
@@ -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``
+3 -7
View File
@@ -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
View File
@@ -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.
View File
File renamed without changes.
-502
View File
@@ -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!
+95 -60
View File
@@ -1,59 +1,51 @@
![mpv logo](https://raw.githubusercontent.com/mpv-player/mpv.io/master/source/images/mpv-logo-128.png)
![http://mpv.io/](https://raw.githubusercontent.com/mpv-player/mpv.io/master/source/images/mpv-logo-128.png)
# 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
View File
File diff suppressed because it is too large. Load diff
+4 -6
View File
@@ -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
View File
@@ -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
View File
@@ -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()
+9 -4
View File
@@ -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:
+5
View File
@@ -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
View File
@@ -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
View File
@@ -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
-124
View File
@@ -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)
-37
View File
@@ -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)
-35
View File
@@ -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)
-265
View File
@@ -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
-32
View File
@@ -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
View File
@@ -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 = []
+1 -1
View File
@@ -22,7 +22,7 @@ OS2Version: 3
OS2_WeightWidthSlopeOnly: 0
OS2_UseTypoMetrics: 0
CreationTime: 1408646554
ModificationTime: 1576096543
ModificationTime: 1492879730
PfmFamily: 81
TTFWeight: 400
TTFWidth: 5
-15
View File
@@ -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
-20
View File
@@ -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
-31
View File
@@ -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
-27
View File
@@ -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
View File
@@ -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.")
+1 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}
+1 -1
View File
@@ -1 +1 @@
0.33.0
0.27.1
+4 -19
View File
@@ -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
View File
@@ -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;
-21
View File
@@ -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
View File
@@ -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;
}
+99
View File
@@ -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
+161
View File
@@ -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;
}
+45
View File
@@ -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
+49
View File
@@ -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
View File
@@ -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
View File
@@ -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,
};
+287
View File
@@ -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;
}
+66
View File
@@ -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 */
+829
View File
@@ -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);
}
+161
View File
@@ -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 */
+255
View File
@@ -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}
},
};
-114
View File
@@ -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,
};
+215
View File
@@ -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
View File
@@ -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
View File
@@ -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,
};
+409
View File
@@ -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}
},
};
+597
View File
@@ -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}
},
};
+206
View File
@@ -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
View File
@@ -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
View File
@@ -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,
};
-245
View File
@@ -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,
};
-724
View File
@@ -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);
}
-121
View File
@@ -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);
+188
View File
@@ -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}
},
};
+47
View File
@@ -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 */
+72
View File
@@ -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