Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ae812ec6d | ||
|
|
417fe22f67 | ||
|
|
b889623768 | ||
|
|
db0af67e7e |
No files matched your search
@@ -0,0 +1,26 @@
|
||||
### 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.)
|
||||
Do not use garbage like "cloud storage", especially not Google Drive.
|
||||
@@ -1,16 +0,0 @@
|
||||
---
|
||||
name: 'README: DO NOT IGNORE OR DELETE THE ISSUE TEMPLATE'
|
||||
about: 'Chose and fill out one of the following templates!'
|
||||
title: ''
|
||||
labels: priority:ignored-issue-template
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
We ask you to not ignore the issue template. Fill it out as good and correct as
|
||||
possible. Issues that don't adhere to our request will be closed for ignoring
|
||||
the issue template. This is because analyzing a bug without a log file is harder
|
||||
than necessary. Low quality bug reports are noise.
|
||||
|
||||
Please go back and chose the proper issue template. Opening issues with this
|
||||
template will be closed immediately.
|
||||
@@ -1,49 +0,0 @@
|
||||
---
|
||||
name: 'Report a Linux Issue'
|
||||
about: 'Create a report for a runtime related Linux Issue'
|
||||
title: ''
|
||||
labels: 'os:linux'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### Important Information
|
||||
|
||||
Provide following Information:
|
||||
- mpv version
|
||||
- Linux Distribution and Version
|
||||
- Source of the mpv binary
|
||||
- If known which version of mpv introduced the problem
|
||||
- Window Manager and version
|
||||
- GPU driver and version
|
||||
- Possible screenshot or video of visual glitches
|
||||
|
||||
If you're not using git master or the latest release, update.
|
||||
Releases are listed here: https://github.com/mpv-player/mpv/releases
|
||||
|
||||
### Reproduction steps
|
||||
|
||||
Try to reproduce your issue with --no-config first. If it isn't reproducible
|
||||
with --no-config try to first find out which option or script causes your issue.
|
||||
|
||||
Describe the reproduction steps as precise as possible. It's very likely that
|
||||
the bug you experience wasn't reproduced by the developer because the workflow
|
||||
differes from your own.
|
||||
|
||||
### Expected behavior
|
||||
|
||||
### Actual behavior
|
||||
|
||||
### Log file
|
||||
|
||||
Make a log file made with -v -v or --log-file=output.txt, paste it to
|
||||
https://0x0.st/ or attach it to the github issue, and replace this text with a
|
||||
link to it.
|
||||
|
||||
The issue will be closed for ignoring the issue template.
|
||||
|
||||
### Sample files
|
||||
|
||||
Sample files needed to reproduce this issue can be uploaded to https://0x0.st/
|
||||
or similar sites. (Only needed if the issue cannot be reproduced without it.)
|
||||
Do not use garbage like "cloud storage", especially not Google Drive.
|
||||
@@ -1,51 +0,0 @@
|
||||
---
|
||||
name: 'Report a macOS Issue'
|
||||
about: 'Create a report for a runtime related macOS Issue'
|
||||
title: ''
|
||||
labels: 'os:mac'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### Important Information
|
||||
|
||||
Provide following Information:
|
||||
- mpv version
|
||||
- macOS Version
|
||||
- Source of the mpv binary or bundle
|
||||
- If known which version of mpv introduced the problem
|
||||
- Possible screenshot or video of visual glitches
|
||||
|
||||
If you're not using git master or the latest release, update.
|
||||
Releases are listed here: https://github.com/mpv-player/mpv/releases
|
||||
|
||||
### Reproduction steps
|
||||
|
||||
Try to reproduce your issue with --no-config first. If it isn't reproducible
|
||||
with --no-config try to first find out which option or script causes your issue.
|
||||
|
||||
Describe the reproduction steps as precise as possible. It's very likely that
|
||||
the bug you experience wasn't reproduced by the developer because the workflow
|
||||
differes from your own.
|
||||
|
||||
### Expected behavior
|
||||
|
||||
### Actual behavior
|
||||
|
||||
### Log file
|
||||
|
||||
Make a log file made with -v -v or --log-file=output.txt. If you use the Bundle
|
||||
from a version later than 0.32 a default log is created for your last run at
|
||||
~/Library/Logs/mpv.log. You can jump to that file via the Help > Show log File…
|
||||
menu. Paste the log to https://0x0.st/ or attach it to the github issue, and
|
||||
replace this text with a link to it.
|
||||
|
||||
In the case of a crash please provide the macOS Crash Report (Backtrace).
|
||||
|
||||
The issue will be closed for ignoring the issue template.
|
||||
|
||||
### Sample files
|
||||
|
||||
Sample files needed to reproduce this issue can be uploaded to https://0x0.st/
|
||||
or similar sites. (Only needed if the issue cannot be reproduced without it.)
|
||||
Do not use garbage like "cloud storage", especially not Google Drive.
|
||||
@@ -1,47 +0,0 @@
|
||||
---
|
||||
name: 'Report a Windows Issue'
|
||||
about: 'Create a report for a runtime related Windows Issue'
|
||||
title: ''
|
||||
labels: 'os:win'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### Important Information
|
||||
|
||||
Provide following Information:
|
||||
- mpv version
|
||||
- Windows Version
|
||||
- Source of the mpv binary
|
||||
- If known which version of mpv introduced the problem
|
||||
- Possible screenshot or video of visual glitches
|
||||
|
||||
If you're not using git master or the latest release, update.
|
||||
Releases are listed here: https://github.com/mpv-player/mpv/releases
|
||||
|
||||
### Reproduction steps
|
||||
|
||||
Try to reproduce your issue with --no-config first. If it isn't reproducible
|
||||
with --no-config try to first find out which option or script causes your issue.
|
||||
|
||||
Describe the reproduction steps as precise as possible. It's very likely that
|
||||
the bug you experience wasn't reproduced by the developer because the workflow
|
||||
differes from your own.
|
||||
|
||||
### Expected behavior
|
||||
|
||||
### Actual behavior
|
||||
|
||||
### Log file
|
||||
|
||||
Make a log file made with -v -v or --log-file=output.txt, paste it to
|
||||
https://0x0.st/ or attach it to the github issue, and replace this text with a
|
||||
link to it.
|
||||
|
||||
The issue will be closed for ignoring the issue template.
|
||||
|
||||
### Sample files
|
||||
|
||||
Sample files needed to reproduce this issue can be uploaded to https://0x0.st/
|
||||
or similar sites. (Only needed if the issue cannot be reproduced without it.)
|
||||
Do not use garbage like "cloud storage", especially not Google Drive.
|
||||
@@ -1,43 +0,0 @@
|
||||
---
|
||||
name: 'Report a different Issue'
|
||||
about: 'Create a report for a runtime related Issue'
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### Important Information
|
||||
|
||||
Provide following Information:
|
||||
- mpv version
|
||||
- Platform and Version
|
||||
- Source of the mpv binary
|
||||
|
||||
If you're not using git master or the latest release, update.
|
||||
Releases are listed here: https://github.com/mpv-player/mpv/releases
|
||||
|
||||
### Reproduction steps
|
||||
|
||||
Try to reproduce your issue with --no-config first. If it isn't reproducible
|
||||
with --no-config try to first find out which option or script causes your issue.
|
||||
|
||||
Describe the reproduction steps as precise as possible. It's very likely that
|
||||
the bug you experience wasn't reproduced by the developer because the workflow
|
||||
differes from your own.
|
||||
|
||||
### Expected behavior
|
||||
|
||||
### Actual behavior
|
||||
|
||||
### Log file
|
||||
|
||||
Make a log file made with -v -v or --log-file=output.txt, paste it to
|
||||
https://0x0.st/ or attach it to the github issue, and replace this text with a
|
||||
link to it.
|
||||
|
||||
### Sample files
|
||||
|
||||
Sample files needed to reproduce this issue can be uploaded to https://0x0.st/
|
||||
or similar sites. (Only needed if the issue cannot be reproduced without it.)
|
||||
Do not use garbage like "cloud storage", especially not Google Drive.
|
||||
@@ -1,30 +0,0 @@
|
||||
---
|
||||
name: 'Report a build Problem'
|
||||
about: 'Create a report for a build related Issue'
|
||||
title: ''
|
||||
labels: 'core:waf'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### mpv version and platform versions
|
||||
|
||||
If you're not using git master or the latest release, update.
|
||||
Releases are listed here: https://github.com/mpv-player/mpv/releases
|
||||
|
||||
### Reproduction steps
|
||||
|
||||
Describe the reproduction steps as precise as possible. It's very likely that
|
||||
the bug you experience wasn't reproduced by the developer because the workflow
|
||||
differes from your own.
|
||||
|
||||
### Expected behavior
|
||||
|
||||
### Actual behavior
|
||||
|
||||
### Log file
|
||||
|
||||
Upload build/config.log or ./waf configure output to https://0x0.st/ or attach
|
||||
it to the github issue, and replace this text with a link to it.
|
||||
|
||||
The issue will be closed for ignoring the issue template.
|
||||
@@ -1,22 +0,0 @@
|
||||
---
|
||||
name: 'Request a new Feature'
|
||||
about: 'Create a request for a new feature'
|
||||
title: ''
|
||||
labels: 'meta:feature-request'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Before requesting a new feature make sure it hasn't been requested yet.
|
||||
https://github.com/mpv-player/mpv/labels/meta%3Afeature-request
|
||||
|
||||
### Expected behavior of the wanted feature
|
||||
|
||||
### Alternative behavior of the wanted feature
|
||||
|
||||
### Log file
|
||||
|
||||
Even if you think it's not necessary at first, it might help us later to find
|
||||
possible issues. Make a log file made with -v -v or --log-file=output.txt, paste
|
||||
it to https://0x0.st/ or attach it to the github issue, and replace this text
|
||||
with a link to it.
|
||||
@@ -1,25 +0,0 @@
|
||||
---
|
||||
name: 'Ask a Question'
|
||||
about: 'Ask a question about mpv'
|
||||
title: ''
|
||||
labels: 'meta:question'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Don't ask questions about issues, errors or problems you have and instead open
|
||||
a proper issue with the right template from the previous selection.
|
||||
|
||||
This template is meant for questions about the workings of mpv, to clarify about
|
||||
unspecified behaviour of options or command, or anything else that is not well
|
||||
described and needs clarification.
|
||||
|
||||
Before asking a question make sure it hasn't been asked or answered yet.
|
||||
https://github.com/mpv-player/mpv/labels/meta%3Aquestion
|
||||
|
||||
### Log file
|
||||
|
||||
Even if you think it's not necessary at first, it might help us later to find
|
||||
possible issues. Make a log file made with -v -v or --log-file=output.txt, paste
|
||||
it to https://0x0.st/ or attach it to the github issue, and replace this text
|
||||
with a link to it.
|
||||
@@ -1,9 +0,0 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: mpv irc channel
|
||||
url: https://mpv.io/community/
|
||||
about: Feel free to ask questions here irc://irc.freenode.net/mpv
|
||||
- name: mpv irc developer channel
|
||||
url: https://mpv.io/community/
|
||||
about: Ask questions related to the development of mpv here
|
||||
irc://irc.freenode.net/mpv-devel
|
||||
@@ -1,5 +1,7 @@
|
||||
Read this before you submit this pull request:
|
||||
https://github.com/mpv-player/mpv/blob/master/DOCS/contribute.md
|
||||
Ideally, you shouldn't need enter any text here, and your commit messages should
|
||||
explain your changes sufficiently (especially why they are needed). Read
|
||||
https://github.com/mpv-player/mpv/blob/master/DOCS/contribute.md for coding
|
||||
style and development conventions. Remove this text block, but if you haven't
|
||||
agreed to it before, leave the following sentence in place:
|
||||
|
||||
Reading this link and following the rules will get your pull request reviewed
|
||||
and merged faster. Nobody wants lazy pull requests.
|
||||
I agree that my changes can be relicensed to LGPL 2.1 or later.
|
||||
+22
-139
@@ -1,166 +1,49 @@
|
||||
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=ffmpeg-git
|
||||
- LIBAV=libav-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
|
||||
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
|
||||
|
||||
@@ -16,13 +16,11 @@ licensed as LGPLv2.1+. Also see the list of files with specific licenses below
|
||||
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
|
||||
If changes are done on GPL code, must come with the implicit 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.
|
||||
|
||||
For information about authors and contributors, consult the git log, which
|
||||
contains the complete SVN and CVS history as well.
|
||||
|
||||
@@ -36,6 +34,8 @@ 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:
|
||||
- no audio filtering, which breaks: --af, pitch correction, fine control over
|
||||
downmix/upmix/resampling behavior
|
||||
- Linux X11 video output
|
||||
- BSD audio output via OSS
|
||||
- NVIDIA/Linux hardware decoding (vdpau, although nvdec usually works)
|
||||
@@ -47,12 +47,28 @@ at all.
|
||||
|
||||
The following files are still GPL only (--enable-lgpl disables them):
|
||||
|
||||
audio/filter/* will be replaced with new filter chain
|
||||
audio/filter/af_format.c mostly LGPL (except af glue code)
|
||||
audio/filter/af_lavc3enc.c as above
|
||||
audio/filter/af_lavfi.c as above
|
||||
audio/filter/af_scaletempo.c as above
|
||||
audio/filter/af_rubberband.c as above
|
||||
audio/out/ao_jack.c will stay GPL
|
||||
audio/out/ao_oss.c will stay GPL
|
||||
audio/audio.* needed by af code only
|
||||
demux/demux_tv.c will stay GPL
|
||||
stream/ai_* will stay GPL (TV code)
|
||||
stream/audio_in.* will stay GPL (TV code)
|
||||
stream/dvb* must stay GPL
|
||||
stream/frequencies.* must stay GPL
|
||||
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_smb.c will stay GPLv3
|
||||
stream/stream_tv.c will stay GPL
|
||||
stream/tv* will stay GPL
|
||||
video/out/vo_caca.c unknown
|
||||
video/out/vo_direct3d.c unknown
|
||||
video/out/vo_vaapi.c probably impossible (some company's code)
|
||||
@@ -69,6 +85,11 @@ The following files are still GPL only (--enable-lgpl disables them):
|
||||
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
|
||||
The following files contain some optional GPL code (--enable-lgpl disables it):
|
||||
|
||||
options/parse_commandline.c dvd:// expansion
|
||||
player/audio.c libaf glue code
|
||||
|
||||
None of the exceptions 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).
|
||||
@@ -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,103 +32,7 @@ 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
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -136,7 +136,7 @@ pacman -S git python $MINGW_PACKAGE_PREFIX-{pkg-config,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}
|
||||
pacman -S $MINGW_PACKAGE_PREFIX-{ffmpeg,libjpeg-turbo,lua51,angleproject-git}
|
||||
```
|
||||
|
||||
Building mpv
|
||||
@@ -168,12 +168,8 @@ Or, compile and install both libmpv and mpv:
|
||||
Linking libmpv with MSVC programs
|
||||
---------------------------------
|
||||
|
||||
mpv/libmpv cannot be built with Visual Studio (Microsoft is too incompetent to
|
||||
support C99/C11 properly and/or hates open source and Linux too much to
|
||||
seriously do it). But you can build C++ programs in Visual Studio and link them
|
||||
with a libmpv built with MinGW.
|
||||
|
||||
To do this, you need a Visual Studio which supports ``stdint.h`` (recent ones do),
|
||||
You can build C++ programs in Visual Studio and link them with libmpv. To do
|
||||
this, you need a Visual Studio which supports ``stdint.h`` (recent ones do),
|
||||
and you need to create a import library for the mpv DLL:
|
||||
|
||||
```bash
|
||||
|
||||
+30
-113
@@ -5,60 +5,35 @@ General
|
||||
-------
|
||||
|
||||
The main contact for mpv development is IRC, specifically #mpv
|
||||
and #mpv-devel on Freenode. Github is used for code review and
|
||||
long term discussions.
|
||||
and #mpv-devel on Freenode.
|
||||
|
||||
Sending patches
|
||||
---------------
|
||||
|
||||
- Make a github pull request, or send a link to a plaintext patch created with
|
||||
``git format-patch``.
|
||||
- Plain diffs posted as pastebins are not acceptable! (Especially if the http
|
||||
link returns HTML.) They only cause extra work for everyone, because they lack
|
||||
commit message and authorship information.
|
||||
- Never send patches to any of the developers email addresses.
|
||||
- If your changes are not supposed to be merged immediately, mark them as
|
||||
"[RFC]" in the commit message or the pull request title.
|
||||
- Be sure to test your changes. If you didn't, please say so in the commit
|
||||
message and the pull request text.
|
||||
|
||||
Copyright of contributions
|
||||
--------------------------
|
||||
|
||||
- The copyright belongs to contributors. The project is a collaborative work. By
|
||||
sending your changes, you agree to license your contributions according to the
|
||||
requirements of this project.
|
||||
``git format-patch``. diffs posted as pastebins (especially if the http link
|
||||
returns HTML) just cause extra work for everyone, because they lack commit
|
||||
message and authorship information.
|
||||
- All new code must be LGPLv2.1+ licensed, or come with the implicit agreement
|
||||
that it will be relicensed to LGPLv2.1+ later (see ``Copyright`` in the
|
||||
repository root directory).
|
||||
- 100% compatible licenses are allowed too.
|
||||
- Changes in files with more liberal licenses (such as BSD, MIT, or ISC) are
|
||||
assumed to be dual-licensed under LGPLv2.1+ and the license indicated in the
|
||||
file header.
|
||||
- You must be either the exclusive author of the patch, or acknowledge all
|
||||
authors involved in the commit message. If you take 3rd party code, authorship
|
||||
and copyright must be properly acknowledged. If you're making changes on
|
||||
behalf of your employer, and the employer owns the copyright, you must mention
|
||||
this. If the license of the code is not LGPLv2.1+, you must mention this.
|
||||
- These license statements are legally binding.
|
||||
- Don't use fake names (something that looks like an actual name, and may be
|
||||
and copyright must be properly acknowledged. If the license of the code is not
|
||||
LGPLv2.1+, this must be mentioned.
|
||||
- Don't use fake names (something that looks like an actual names, and may be
|
||||
someone else's name, but is not your legal name). Using a pseudonyms is
|
||||
allowed if it can be used to identify or contact you, even if whatever
|
||||
account you used to submit the patch dies.
|
||||
- Do not add your name to the license header. This convention is not used by
|
||||
this project, and neither copyright law nor any of the used licenses require
|
||||
it.
|
||||
|
||||
Write good commit messages
|
||||
--------------------------
|
||||
|
||||
- When creating pull requests, be sure to test your changes. If you didn't,
|
||||
please say so in the pull request message.
|
||||
- Write informative commit messages. Use present tense to describe the
|
||||
situation with the patch applied, and past tense for the situation before
|
||||
the change.
|
||||
- The subject line (the first line in a commit message) must contain a
|
||||
- The subject line (the first line in a commit message) should contain a
|
||||
prefix identifying the sub system, followed by a short description what
|
||||
impact this commit has. This subject line and the commit message body
|
||||
must not be longer than 72 characters per line, because it messes up the
|
||||
shouldn't be longer than 72 characters per line, because it messes up the
|
||||
output of many git tools otherwise.
|
||||
|
||||
For example, you fixed a crash in af_volume.c:
|
||||
@@ -68,11 +43,7 @@ Write good commit messages
|
||||
|
||||
Having a prefix gives context, and is especially useful when trying to find
|
||||
a specific change by looking at the history, or when running ``git blame``.
|
||||
- The first word after the ``:`` is lower case.
|
||||
- Don't end the subject line with a ``.``.
|
||||
- Put an empty line between the subject line and the commit message.
|
||||
If this is missing, it will break display in common git tools.
|
||||
- The body of the commit message (everything else after the subject line) must
|
||||
- The body of the commit message (everything else after the subject line) should
|
||||
be as informative as possible and contain everything that isn't obvious. Don't
|
||||
hesitate to dump as much information as you can - it doesn't cost you
|
||||
anything. Put some effort into it. If someone finds a bug months or years
|
||||
@@ -81,56 +52,25 @@ Write good commit messages
|
||||
information to test the original bug. The old bug might be reintroduced while
|
||||
fixing the new bug.
|
||||
|
||||
The commit message must be wrapped on 72 characters per line, because git
|
||||
The commit message should be wrapped on 72 characters per line, because git
|
||||
tools usually do not break text automatically. On the other hand, you do not
|
||||
need to break text that would be unnatural to break (like data for test cases,
|
||||
or long URLs).
|
||||
- Another summary of good conventions: https://chris.beams.io/posts/git-commit/
|
||||
|
||||
Split changes into multiple commits
|
||||
-----------------------------------
|
||||
|
||||
- Follow git good practices, and split independent changes into several commits.
|
||||
It's usually OK to put them into a single pull request.
|
||||
Important: put an empty line between the subject line and the commit message.
|
||||
If this is missing, it will break display in common git tools.
|
||||
- Try to separate cosmetic and functional changes. It's ok to make a few
|
||||
additional cosmetic changes in the same file you're working on. But don't do
|
||||
something like reformatting a whole file, and hiding an actual functional
|
||||
change in the same commit.
|
||||
- Splitting changes does _not_ mean that you should make them as fine-grained
|
||||
as possible. Commits should form logical steps in development. The way you
|
||||
split changes is important for code review and analyzing bugs.
|
||||
|
||||
Always squash fixup commits when making changes to pull requests
|
||||
----------------------------------------------------------------
|
||||
|
||||
- If you make fixup commits to your pull request, you should generally squash
|
||||
them with "git rebase -i". We prefer to have pull requests in a merge
|
||||
ready state.
|
||||
- We don't squash-merge (nor do we use github's feature that does this) because
|
||||
pull requests with multiple commits are perfectly legitimate, and the only
|
||||
thing that makes sense in non-trivial cases.
|
||||
- With complex pull requests, it *may* make sense to keep them separate, but
|
||||
they should be clearly marked as such. Reviewing commits is generally easier
|
||||
with fixups squashed.
|
||||
- Reviewers are encouraged to look at individual commits instead of github's
|
||||
"changes from all commits" view (which just encourages bad git and review
|
||||
practices).
|
||||
|
||||
Touching user-visible parts may require updating the mpv docs
|
||||
-------------------------------------------------------------
|
||||
|
||||
- Most user-visible things are normally documented in DOCS/man/. If your commit
|
||||
touches documented behavior, list of sub-options, etc., you need to adjust the
|
||||
documentation.
|
||||
- These changes usually go into the same commit that changes the code.
|
||||
- Changes to command line options (addition/modification/removal) must be
|
||||
documented in options.rst.
|
||||
- Changes to input properties or input commands must be documented in input.rst.
|
||||
- All incompatible changes to the user interface (options, properties, commands)
|
||||
must be documented with a small note in interface-changes.rst. (Additions may
|
||||
be documented there as well, but this isn't required.)
|
||||
- Changes to the libmpv API must be reflected in the libmpv's headers doxygen,
|
||||
and in client-api-changes.rst.
|
||||
documented in options.rst. Changes to input properties or input commands must
|
||||
be documented in input.rst. All changes to the user interface (options,
|
||||
properties, commands) must be documented with a small note in
|
||||
interface-changes.rst (although documenting additions is optional, and
|
||||
obscure corner cases can potentially be skipped too). Changes to the libmpv
|
||||
API must be reflected in the libmpv's headers doxygen, and should be
|
||||
documented in client-api-changes.rst.
|
||||
|
||||
Code formatting
|
||||
---------------
|
||||
@@ -140,7 +80,7 @@ mpv uses C99 with K&R formatting, with some exceptions.
|
||||
- Use the K&R indent style.
|
||||
- Use 4 spaces of indentation, never use tabs (except in Makefiles).
|
||||
- Add a single space between keywords and binary operators. There are some other
|
||||
cases where spaces must be added. Example:
|
||||
cases where spaces should be added. Example:
|
||||
|
||||
```C
|
||||
if ((a * b) > c) {
|
||||
@@ -173,7 +113,7 @@ mpv uses C99 with K&R formatting, with some exceptions.
|
||||
do_something();
|
||||
}
|
||||
```
|
||||
- If the if has an else branch, both branches must use braces, even if they're
|
||||
- If the if has an else branch, both branches should use braces, even if they're
|
||||
technically redundant.
|
||||
|
||||
Example:
|
||||
@@ -203,31 +143,18 @@ mpv uses C99 with K&R formatting, with some exceptions.
|
||||
|
||||
(If the if body is simple enough, this rule can be skipped.)
|
||||
- Remove any trailing whitespace.
|
||||
- Do not make stray whitespaces changes.
|
||||
|
||||
Header #include statement order
|
||||
-------------------------------
|
||||
|
||||
The order of ``#include`` statements in the source code is not very consistent.
|
||||
New code must follow the following conventions:
|
||||
|
||||
- Put standard includes (``#include <stdlib.h>`` etc.) on the top,
|
||||
- then after a blank line, add library includes (``#include <zlib.h>`` etc.)
|
||||
- then after a blank line, add internal includes (``#include "player/core.h"``)
|
||||
- sort them alphabetically within these sections
|
||||
|
||||
General coding
|
||||
--------------
|
||||
|
||||
- Use C99. Also freely make use of C99 features if it's appropriate, such as
|
||||
stdbool.h. (Except VLA and complex number types.)
|
||||
- Don't use non-standard language (such as GNU C-only features). In some cases
|
||||
they may be warranted, if they are optional (such as attributes enabling
|
||||
printf-like format string checks). "#pragma once" is allowed as an exception.
|
||||
But in general, standard C99 must be used.
|
||||
stdbool.h.
|
||||
- Don't use GNU-only features. In some cases they may be warranted, if they
|
||||
are optional (such as attributes enabling printf-like format string checks).
|
||||
But in general, standard C99 should be used.
|
||||
- The same applies to libc functions. We have to be Windows-compatible too. Use
|
||||
functions guaranteed by C99 or POSIX only, unless your use is guarded by a
|
||||
configure check. There is some restricted use of C11 (ask on IRC for details).
|
||||
configure check.
|
||||
- Prefer fusing declaration and initialization, rather than putting declarations
|
||||
on the top of a block. Obvious data flow is more important than avoiding
|
||||
mixing declarations and statements, which is just a C90 artifact.
|
||||
@@ -235,14 +162,6 @@ General coding
|
||||
channel first. There might be a better way to add a feature and it can avoid
|
||||
wasted work.
|
||||
|
||||
Code of Conduct
|
||||
---------------
|
||||
|
||||
Please note that this project is released with a Contributor Code of Conduct.
|
||||
By participating in this project you agree to abide by its terms.
|
||||
The Contributor Code of Conduct can be found here:
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct/
|
||||
|
||||
Rules for git push access
|
||||
-------------------------
|
||||
|
||||
@@ -261,9 +180,7 @@ you got access, the following rules must be followed:
|
||||
major bug fixes, or you're the original author of the code). If there is more
|
||||
than one maintainer, you may need to come to an agreement with the others how
|
||||
to handle this to avoid conflict.
|
||||
- If you make a pull requests (especially if it's to code you maintain), and you
|
||||
want reviews, explicitly ping the people from which you expect reviews.
|
||||
- As a maintainer, you can approve pull requests by others to "your" code.
|
||||
- As a maintainer, you can approve pushes by others to "your" code.
|
||||
- If you approve or merge 3rd party changes, make sure they follow the general
|
||||
development rules.
|
||||
- Changes to user interface and public API must always be approved by the
|
||||
|
||||
+1
-220
@@ -100,18 +100,6 @@ entries affect all other file entries in the EDL file. Their format is highly
|
||||
implementation specific. They should generally follow the file header, and come
|
||||
before any file entries.
|
||||
|
||||
Disabling chapter generation and copying
|
||||
========================================
|
||||
|
||||
By default, chapters from the source ranges are copied to the virtual file's
|
||||
chapters. Also, a chapter is inserted after each range. This can be disabled
|
||||
with the ``no_chapters`` header.
|
||||
|
||||
Example::
|
||||
|
||||
!no_chapters
|
||||
|
||||
|
||||
MP4 DASH
|
||||
========
|
||||
|
||||
@@ -143,164 +131,6 @@ The current implementation will
|
||||
- not add segment boundaries as chapter points
|
||||
- require full compatibility between all segments (same codec etc.)
|
||||
|
||||
Another header part of this mechanism is ``no_clip``. This header is similar
|
||||
to ``mp4_dash``, but does not include on-demand opening/closing of segments,
|
||||
and does not support init segments. It also exists solely to support internal
|
||||
ytdl requirements. Using ``no_clip`` with segments is not recommended and
|
||||
probably breaks. ``mp4_dash`` already implicitly does a variant of ``no_clip``.
|
||||
|
||||
The ``mp4_dash`` and ``no_clip`` headers are not part of the core EDL format.
|
||||
They may be changed or removed at any time, depending on mpv's internal
|
||||
requirements.
|
||||
|
||||
Separate files for tracks
|
||||
=========================
|
||||
|
||||
The special ``new_stream`` header lets you specify separate parts and time
|
||||
offsets for separate tracks. This can for example be used to source audio and
|
||||
video track from separate files.
|
||||
|
||||
Example::
|
||||
|
||||
# mpv EDL v0
|
||||
video.mkv
|
||||
!new_stream
|
||||
audio.mkv
|
||||
|
||||
This adds all tracks from both files to the virtual track list. Upon playback,
|
||||
the tracks will be played at the same time, instead of appending them. The files
|
||||
can contain more than 1 stream; the apparent effect is the same as if the second
|
||||
part after the ``!new_stream`` part were in a separate ``.edl`` file and added
|
||||
with ``--external-file``.
|
||||
|
||||
Note that all metadata between the stream sets created by ``new_stream`` is
|
||||
disjoint. Global metadata is taken from the first part only.
|
||||
|
||||
In context of mpv, this is redundant to the ``--audio-file`` and
|
||||
``--external-file`` options, but (as of this writing) has the advantage that
|
||||
this will use a unified cache for all streams.
|
||||
|
||||
The ``new_stream`` header is not part of the core EDL format. It may be changed
|
||||
or removed at any time, depending on mpv's internal requirements.
|
||||
|
||||
If the first ``!new_stream`` is redundant, it is ignored. This is the same
|
||||
example as above::
|
||||
|
||||
# mpv EDL v0
|
||||
!new_stream
|
||||
video.mkv
|
||||
!new_stream
|
||||
audio.mkv
|
||||
|
||||
Note that ``!new_stream`` must be the first header. Whether the parser accepts
|
||||
(i.e. ignores) or rejects other headers before that is implementation specific.
|
||||
|
||||
Track metadata
|
||||
==============
|
||||
|
||||
The special ``track_meta`` header can set some specific metadata fields of the
|
||||
current ``!new_stream`` partition. The tags are applied to all tracks within
|
||||
the partition. It is not possible to set the metadata for individual tracks (the
|
||||
feature was needed only for single-track media).
|
||||
|
||||
It provides following parameters change track metadata:
|
||||
|
||||
``lang``
|
||||
Set the language tag.
|
||||
|
||||
``title``
|
||||
Set the title tag.
|
||||
|
||||
``byterate``
|
||||
Number of bytes per second this stream uses. (Purely informational.)
|
||||
|
||||
``index``
|
||||
The numeric index of the track this should map to (default: -1). This is
|
||||
the 0-based index of the virtual stream as seen by the player, enumerating
|
||||
all audio/video/subtitle streams. If nothing matches, this is silently
|
||||
discarded. The special index -1 (the default) has two meanings: if there
|
||||
was a previous meta data entry (either ``!track_meta`` or ``!delay_open``
|
||||
element since the last ``!new_stream``), then this element manipulates
|
||||
the previous meta data entry. If there was no previous entry, a new meta
|
||||
data entry that matches all streams is created.
|
||||
|
||||
Example::
|
||||
|
||||
# mpv EDL v0
|
||||
!track_meta,lang=bla,title=blabla
|
||||
file.mkv
|
||||
!new_stream
|
||||
!track_meta,title=ducks
|
||||
sub.srt
|
||||
|
||||
If ``file.mkv`` has an audio and a video stream, both will use ``blabla`` as
|
||||
title. The subtitle stream will use ``ducks`` as title.
|
||||
|
||||
The ``track_meta`` header is not part of the core EDL format. It may be changed
|
||||
or removed at any time, depending on mpv's internal requirements.
|
||||
|
||||
Delayed media opening
|
||||
=====================
|
||||
|
||||
The special ``delay_open`` header can be used to open the media URL of the
|
||||
stream only when the track is selected for the first time. This is supposed to
|
||||
be an optimization to speed up opening of a remote stream if there are many
|
||||
tracks for whatever reasons.
|
||||
|
||||
This has various tricky restrictions, and also will defer failure to open a
|
||||
stream to "later". By design, it's supposed to be used for single-track streams.
|
||||
|
||||
Using multiple segments requires you to specify all offsets and durations (also
|
||||
it was never tested whether it works at all). Interaction with ``mp4_dash`` may
|
||||
be strange.
|
||||
|
||||
You can describe multiple sub-tracks by using multiple ``delay_open`` headers
|
||||
before the same source URL. (If there are multiple sub-tracks of the same media
|
||||
type, then the mapping to the real stream is probably rather arbitrary.) If the
|
||||
source contains tracks not described, a warning is logged when the delayed
|
||||
opening happens, and the track is hidden.
|
||||
|
||||
This has the following parameters:
|
||||
|
||||
``media_type``
|
||||
Required. Must be set to ``video``, ``audio``, or ``sub``. (Other tracks in
|
||||
the opened URL are ignored.)
|
||||
|
||||
``codec``
|
||||
The mpv codec name that is expected. Although mpv tries to initialize a
|
||||
decoder with it currently (and will fail track selection if it does not
|
||||
initialize successfully), it is not used for decoding - decoding still uses
|
||||
the information retrieved from opening the actual media information, and may
|
||||
be a different codec (you should try to avoid this, of course). Defaults to
|
||||
``null``.
|
||||
|
||||
Above also applies for similar fields such as ``w``. These fields are
|
||||
mostly to help with user track pre-selection.
|
||||
|
||||
``flags``
|
||||
A ``+`` separated list of boolean flags. Currently defined flags:
|
||||
|
||||
``default``
|
||||
Set the default track flag.
|
||||
|
||||
``forced``
|
||||
Set the forced track flag.
|
||||
|
||||
Other values are ignored after triggering a warning.
|
||||
|
||||
``w``, ``h``
|
||||
For video codecs: expected video size. See ``codec`` for details.
|
||||
|
||||
``fps``
|
||||
For video codecs: expected video framerate, as integer. (The rate is usually
|
||||
only crudely reported, and it makes no sense to expect exact values.)
|
||||
|
||||
``samplerate``
|
||||
For audio codecs: expected sample rate, as integer.
|
||||
|
||||
The ``delay_open`` header is not part of the core EDL format. It may be changed
|
||||
or removed at any time, depending on mpv's internal requirements.
|
||||
|
||||
Timestamp format
|
||||
================
|
||||
|
||||
@@ -308,8 +138,7 @@ Currently, time values are floating point values in seconds.
|
||||
|
||||
As an extension, you can set the ``timestamps=chapters`` option. If this option
|
||||
is set, timestamps have to be integers, and refer to chapter numbers, starting
|
||||
with 0. The default value for this parameter is ``seconds``, which means the
|
||||
time is as described in the previous paragraph.
|
||||
with 0.
|
||||
|
||||
Example::
|
||||
|
||||
@@ -318,54 +147,6 @@ Example::
|
||||
|
||||
Plays chapter 3 and ends with the start of chapter 7 (4 chapters later).
|
||||
|
||||
Implicit chapters
|
||||
=================
|
||||
|
||||
mpv will add one chapter per segment entry to the virtual timeline.
|
||||
|
||||
By default, the chapter's titles will match the entries' filenames.
|
||||
You can override set the ``title`` option to override the chapter title for
|
||||
that segment.
|
||||
|
||||
Example::
|
||||
|
||||
# mpv EDL v0
|
||||
cap.ts,5,240
|
||||
OP.mkv,0,90,title=Show Opening
|
||||
|
||||
The virtual timeline will have two chapters, one called "cap.ts" from 0-240s
|
||||
and a second one called "Show Opening" from 240-330s.
|
||||
|
||||
Entry which defines the track layout
|
||||
====================================
|
||||
|
||||
Normally, you're supposed to put only files with compatible layouts into an EDL
|
||||
file. However, at least the mpv implementation accepts entries that use
|
||||
different codecs, or even have a different number of audio/video/subtitle
|
||||
tracks. In this case, it's not obvious, which virtual tracks the EDL show should
|
||||
expose when being played.
|
||||
|
||||
Currently, mpv will apply an arbitrary heuristic which tracks the EDL file
|
||||
should expose. (Before mpv 0.30.0, it always used the first source file in the
|
||||
segment list.)
|
||||
|
||||
You can set the ``layout`` option to ``this`` to make a specific entry define
|
||||
the track layout.
|
||||
|
||||
Example::
|
||||
|
||||
# mpv EDL v0
|
||||
file_with_2_streams.ts,5,240
|
||||
file_with_5_streams.mkv,0,90,layout=this
|
||||
|
||||
The way the different virtual EDL tracks are associated with the per-segment
|
||||
ones is highly implementation-defined, and uses a heuristic. If a segment is
|
||||
missing a track, there will be a "hole", and bad behavior may result. Improving
|
||||
this is subject to further development (due to being fringe cases, they don't
|
||||
have a high priority).
|
||||
|
||||
If future versions of mpv change this again, this option may be ignored.
|
||||
|
||||
Syntax of EDL URIs
|
||||
==================
|
||||
|
||||
|
||||
+9
-12
@@ -3,7 +3,8 @@ General usage
|
||||
|
||||
::
|
||||
|
||||
mpv infile -o outfile [-of outfileformat] [-ofopts formatoptions] [-orawts] \
|
||||
mpv infile -o outfile [-of outfileformat] [-ofopts formatoptions] \
|
||||
[-ofps outfps | -oautofps] [-oharddup] [-ocopyts | -orawts] [-oneverdrop] \
|
||||
[(any other mpv options)] \
|
||||
-ovc outvideocodec [-ovcopts outvideocodecoptions] \
|
||||
-oac outaudiocodec [-oacopts outaudiocodecoptions]
|
||||
@@ -30,13 +31,9 @@ section::
|
||||
[myencprofile]
|
||||
vf-add = scale=480:-2
|
||||
ovc = libx264
|
||||
ovcopts-add = preset=medium
|
||||
ovcopts-add = tune=fastdecode
|
||||
ovcopts-add = preset=medium,tune=fastdecode
|
||||
ovcopts-add = crf=23
|
||||
ovcopts-add = maxrate=1500k
|
||||
ovcopts-add = bufsize=1000k
|
||||
ovcopts-add = rc_init_occupancy=900k
|
||||
ovcopts-add = refs=2
|
||||
ovcopts-add = maxrate=1500k,bufsize=1000k,rc_init_occupancy=900k,refs=2
|
||||
ovcopts-add = profile=baseline
|
||||
oac = aac
|
||||
oacopts-add = b=96k
|
||||
@@ -63,13 +60,13 @@ for.
|
||||
Typical MPEG-4 Part 2 ("ASP", "DivX") encoding, AVI container::
|
||||
|
||||
mpv infile -o outfile.avi \
|
||||
--vf=fps=25 \
|
||||
-ofps 25 \
|
||||
-ovc mpeg4 -ovcopts qscale=4 \
|
||||
-oac libmp3lame -oacopts ab=128k
|
||||
|
||||
Note: AVI does not support variable frame rate, so the fps filter must be used.
|
||||
The frame rate should ideally match the input (25 for PAL, 24000/1001 or
|
||||
30000/1001 for NTSC)
|
||||
Note: AVI does not support variable frame rate, so -ofps must be used. The
|
||||
frame rate should ideally match the input (25 for PAL, 24000/1001 or 30000/1001
|
||||
for NTSC)
|
||||
|
||||
Typical MPEG-4 Part 10 ("AVC", "H.264") encoding, Matroska (MKV) container::
|
||||
|
||||
@@ -132,7 +129,7 @@ What works
|
||||
==========
|
||||
|
||||
* Encoding at variable frame rate (default)
|
||||
* Encoding at constant frame rate using --vf=fps=RATE
|
||||
* Encoding at constant frame rate using -ofps framerate -oharddup
|
||||
* 2-pass encoding (specify flags=+pass1 in the first pass's -ovcopts, specify
|
||||
flags=+pass2 in the second pass)
|
||||
* Hardcoding subtitles using vobsub, ass or srt subtitle rendering (just
|
||||
|
||||
+3
-302
@@ -7,316 +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
|
||||
@@ -357,12 +59,11 @@ Interface changes
|
||||
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
|
||||
- rename --hwdec=rpi to --hwdec=mmal, sane for the -copy variant (no
|
||||
backwards compatibility)
|
||||
- deprecate the --ff-aid, --ff-vid, --ff-sid options and properties (there is
|
||||
- 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
|
||||
|
||||
+35
-51
@@ -5,8 +5,7 @@ Audio filters allow you to modify the audio stream and its properties. The
|
||||
syntax is:
|
||||
|
||||
``--af=...``
|
||||
Setup a chain of audio filters. See ``--vf`` (`VIDEO FILTERS`_) for the
|
||||
full syntax.
|
||||
Setup a chain of audio filters. See ``--vf`` for the syntax.
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -24,6 +23,37 @@ See ``--vf`` group of options for info on how ``--af-defaults``, ``--af-add``,
|
||||
|
||||
Available filters are:
|
||||
|
||||
``lavrresample[=option1:option2:...]``
|
||||
This filter uses libavresample (or libswresample, depending on the build)
|
||||
to change sample rate, sample format, or channel layout of the audio stream.
|
||||
This filter is automatically enabled if the audio output does not support
|
||||
the audio configuration of the file being played.
|
||||
|
||||
It supports only the following sample formats: u8, s16, s32, float.
|
||||
|
||||
``filter-size=<length>``
|
||||
Length of the filter with respect to the lower sampling rate. (default:
|
||||
16)
|
||||
``phase-shift=<count>``
|
||||
Log2 of the number of polyphase entries. (..., 10->1024, 11->2048,
|
||||
12->4096, ...) (default: 10->1024)
|
||||
``cutoff=<cutoff>``
|
||||
Cutoff frequency (0.0-1.0), default set depending upon filter length.
|
||||
``linear``
|
||||
If set then filters will be linearly interpolated between polyphase
|
||||
entries. (default: no)
|
||||
``no-detach``
|
||||
Do not detach if input and output audio format/rate/channels match.
|
||||
(If you just want to set defaults for this filter that will be used
|
||||
even by automatically inserted lavrresample instances, you should
|
||||
prefer setting them with ``--af-defaults=lavrresample:...``.)
|
||||
``normalize=<yes|no|auto>``
|
||||
Whether to normalize when remixing channel layouts (default: auto).
|
||||
``auto`` uses the value set by ``--audio-normalize-downmix``.
|
||||
``o=<string>``
|
||||
Set AVOptions on the SwrContext or AVAudioResampleContext. These should
|
||||
be documented by FFmpeg or Libav.
|
||||
|
||||
``lavcac3enc[=options]``
|
||||
Encode multi-channel audio to AC-3 at runtime using libavcodec. Supports
|
||||
16-bit native-endian input format, maximum 6 channels. The output is
|
||||
@@ -61,7 +91,7 @@ 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``
|
||||
``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 +120,8 @@ Available filters are:
|
||||
Force mixing to a specific channel layout. See ``--audio-channels`` option
|
||||
for possible values.
|
||||
|
||||
``<out-format>``
|
||||
|
||||
``<out-srate>``
|
||||
|
||||
``<out-channels>``
|
||||
@@ -161,28 +193,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 +219,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 +233,3 @@ Available filters are:
|
||||
|
||||
``o=<string>``
|
||||
AVOptions.
|
||||
|
||||
``fix-pts=<yes|no>``
|
||||
Determine PTS based on sample count (default: no). If this is enabled,
|
||||
the player won't rely on libavfilter passing through PTS accurately.
|
||||
Instead, it pass a sample count as PTS to libavfilter, and compute the
|
||||
PTS used by mpv based on that and the input PTS. This helps with filters
|
||||
which output a recomputed PTS instead of the original PTS (including
|
||||
filters which require the PTS to start at 0). mpv normally expects
|
||||
filters to not touch the PTS (or only to the extent of changing frame
|
||||
boundaries), so this is not the default, but it will be needed to use
|
||||
broken filters. In practice, these broken filters will either cause slow
|
||||
A/V desync over time (with some files), or break playback completely if
|
||||
you seek or start playback from the middle of a file.
|
||||
|
||||
``drop``
|
||||
This filter drops or repeats audio frames to adapt to playback speed. It
|
||||
always operates on full audio frames, because it was made to handle SPDIF
|
||||
(compressed audio passthrough). This is used automatically if the
|
||||
``--video-sync=display-adrop`` option is used. Do not use this filter (or
|
||||
the given option); they are extremely low quality.
|
||||
+39
-24
@@ -14,7 +14,8 @@ in the list.
|
||||
|
||||
See ``--ao=help`` for a list of compiled-in audio output drivers. The
|
||||
driver ``--ao=alsa`` is preferred. ``--ao=pulse`` is preferred on systems
|
||||
where PulseAudio is used.
|
||||
where PulseAudio is used. On BSD systems, ``--ao=oss`` or ``--ao=sndio``
|
||||
may work (the latter being experimental).
|
||||
|
||||
Available audio output drivers are:
|
||||
|
||||
@@ -26,7 +27,7 @@ Available audio output drivers are:
|
||||
.. warning::
|
||||
|
||||
To get multichannel/surround audio, use ``--audio-channels=auto``. The
|
||||
default for this option is ``auto-safe``, which makes this audio output
|
||||
default for this option is ``auto-safe``, which makes this audio otuput
|
||||
explicitly reject multichannel output, as there is no way to detect
|
||||
whether a certain channel layout is actually supported.
|
||||
|
||||
@@ -35,6 +36,18 @@ Available audio output drivers are:
|
||||
with automatic upmixing with shared access, so playing stereo
|
||||
and multichannel audio at the same time will work as expected.
|
||||
|
||||
``oss``
|
||||
OSS audio output driver
|
||||
|
||||
The following global options are supported by this audio output:
|
||||
|
||||
``--oss-mixer-device``
|
||||
Sets the audio mixer device (default: ``/dev/mixer``).
|
||||
``--oss-mixer-channel``
|
||||
Sets the audio mixer channel (default: ``pcm``). Other valid values
|
||||
include **vol, pcm, line**. For a complete list of options look for
|
||||
``SOUND_DEVICE_NAMES`` in ``/usr/include/linux/soundcard.h``.
|
||||
|
||||
``jack``
|
||||
JACK (Jack Audio Connection Kit) audio output driver.
|
||||
|
||||
@@ -97,21 +110,10 @@ Available audio output drivers are:
|
||||
exclusive mode (bypasses the sound server).
|
||||
|
||||
``openal``
|
||||
OpenAL audio output driver. This is broken and does not work.
|
||||
Experimental OpenAL audio output driver
|
||||
|
||||
``--openal-num-buffers=<2-128>``
|
||||
Specify the number of audio buffers to use. Lower values are better for
|
||||
lower CPU usage. Default: 4.
|
||||
|
||||
``--openal-num-samples=<256-32768>``
|
||||
Specify the number of complete samples to use for each buffer. Higher
|
||||
values are better for lower CPU usage. Default: 8192.
|
||||
|
||||
``--openal-direct-channels=<yes|no>``
|
||||
Enable OpenAL Soft's direct channel extension when available to avoid
|
||||
tinting the sound with ambisonics or HRTF.
|
||||
Channels are dropped when when they are not available as downmixing
|
||||
will be disabled. Default: no.
|
||||
.. note:: This driver is not very useful. Playing multi-channel audio with
|
||||
it is slow.
|
||||
|
||||
``pulse``
|
||||
PulseAudio audio output driver
|
||||
@@ -126,7 +128,7 @@ Available audio output drivers are:
|
||||
Set the audio buffer size in milliseconds. A higher value buffers
|
||||
more data, and has a lower probability of buffer underruns. A smaller
|
||||
value makes the audio stream react faster, e.g. to playback speed
|
||||
changes.
|
||||
changes. Default: 250.
|
||||
|
||||
``--pulse-latency-hacks=<yes|no>``
|
||||
Enable hacks to workaround PulseAudio timing bugs (default: no). If
|
||||
@@ -139,10 +141,6 @@ Available audio output drivers are:
|
||||
If you have stuttering video when using pulse, try to enable this
|
||||
option. (Or try to update PulseAudio.)
|
||||
|
||||
``--pulse-allow-suspended=<yes|no>``
|
||||
Allow mpv to use PulseAudio even if the sink is suspended (default: no).
|
||||
Can be useful if PulseAudio is running as a bridge to jack and mpv has its sink-input set to the one jack is using.
|
||||
|
||||
``sdl``
|
||||
SDL 1.2+ audio output driver. Should work on any platform supported by SDL
|
||||
1.2, but may require the ``SDL_AUDIODRIVER`` environment variable to be set
|
||||
@@ -160,6 +158,10 @@ Available audio output drivers are:
|
||||
obtained exact buffer size. A value of 0 selects the sound system
|
||||
default.
|
||||
|
||||
``--sdl-bufcnt=<count>``
|
||||
Sets the number of extra audio buffers in mpv. Usually needs not be
|
||||
changed.
|
||||
|
||||
``null``
|
||||
Produces no audio output but maintains video playback speed. You can use
|
||||
``--ao=null --ao-null-untimed`` for benchmarking.
|
||||
@@ -196,9 +198,6 @@ Available audio output drivers are:
|
||||
If not empty, this is a ``,`` separated list of channel layouts the
|
||||
AO allows. This can be used to test channel layout selection.
|
||||
|
||||
``--ao-null-format``
|
||||
Force the audio output format the AO will accept. If unset accepts any.
|
||||
|
||||
``pcm``
|
||||
Raw PCM/WAVE file writer audio output
|
||||
|
||||
@@ -216,5 +215,21 @@ Available audio output drivers are:
|
||||
``no-waveheader`` option - with ``waveheader`` it's broken, because
|
||||
it will write a WAVE header every time the file is opened.
|
||||
|
||||
``rsound``
|
||||
Audio output to an RSound daemon. Use ``--audio-device=rsound/<hostname>``
|
||||
to set the host name (with ``<hostname>`` replaced, without the ``< >``).
|
||||
|
||||
.. note:: Completely useless, unless you intend to run RSound. Not to be
|
||||
confused with RoarAudio, which is something completely
|
||||
different.
|
||||
|
||||
``sndio``
|
||||
Audio output to the OpenBSD sndio sound system
|
||||
|
||||
.. note:: Experimental. There are known bugs and issues.
|
||||
|
||||
(Note: only supports mono, stereo, 4.0, 5.1 and 7.1 channel
|
||||
layouts.)
|
||||
|
||||
``wasapi``
|
||||
Audio output to the Windows Audio Session API.
|
||||
@@ -8,12 +8,10 @@ There is no real changelog, but you can look at the following things:
|
||||
|
||||
https://github.com/mpv-player/mpv/releases
|
||||
* The git log, which is the "real" changelog
|
||||
* The file https://github.com/mpv-player/mpv/blob/master/DOCS/interface-changes.rst
|
||||
documents changes to the command and user interface, such as options and
|
||||
properties. (It usually documents breaking changes only, additions and
|
||||
enhancements are often not listed.)
|
||||
* C API changes are listed in
|
||||
https://github.com/mpv-player/mpv/blob/master/DOCS/client-api-changes.rst
|
||||
* The files ``client-api-changes.rst`` and ``interface-changes.rst`` in the
|
||||
``DOCS`` sub directoryon the git repository, which document API and user
|
||||
interface changes (the latter usually documents breaking changes only, rather
|
||||
than additions).
|
||||
* The file ``mplayer-changes.rst`` in the ``DOCS`` sub directory on the git
|
||||
repository, which used to be in place of this section. It documents some
|
||||
changes that happened since mplayer2 forked off MPlayer. (Not updated
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
CONSOLE
|
||||
=======
|
||||
|
||||
The console is a REPL for mpv input commands. It is displayed on the video
|
||||
window. It also shows log messages. It can be disabled entirely using the
|
||||
``--load-osd-console=no`` option.
|
||||
|
||||
Keybindings
|
||||
-----------
|
||||
|
||||
\`
|
||||
Show the console.
|
||||
|
||||
ESC
|
||||
Hide the console.
|
||||
|
||||
ENTER
|
||||
Run the typed command.
|
||||
|
||||
Shift+ENTER
|
||||
Type a literal newline character.
|
||||
|
||||
Ctrl+LEFT and Ctrl+RIGHT
|
||||
Move cursor to previous/next word.
|
||||
|
||||
UP and DOWN
|
||||
Navigate command history.
|
||||
|
||||
PGUP
|
||||
Go to the first command in the history.
|
||||
|
||||
PGDN
|
||||
Stop navigating command history.
|
||||
|
||||
INSERT
|
||||
Toggle insert mode.
|
||||
|
||||
Shift+INSERT
|
||||
Paste text (uses the primary selection on X11).
|
||||
|
||||
TAB
|
||||
Complete the command or property name at the cursor.
|
||||
|
||||
Ctrl+C
|
||||
Clear current line.
|
||||
|
||||
Ctrl+K
|
||||
Delete text from the cursor to the end of the line.
|
||||
|
||||
Ctrl+L
|
||||
Clear all log messages from the console.
|
||||
|
||||
Ctrl+U
|
||||
Delete text from the cursor to the beginning of the line.
|
||||
|
||||
Ctrl+V
|
||||
Paste text (uses the clipboard on X11).
|
||||
|
||||
Ctrl+W
|
||||
Delete text from the cursor to the beginning of the current word.
|
||||
|
||||
Commands
|
||||
--------
|
||||
|
||||
``script-message-to console type <text> [<cursor_pos>]``
|
||||
Show the console and pre-fill it with the provided text, optionally
|
||||
specifying the initial cursor position as a positive integer starting from
|
||||
1.
|
||||
|
||||
.. admonition:: Example for input.conf
|
||||
|
||||
``% script-message-to console type "seek absolute-percent" 6``
|
||||
|
||||
Known issues
|
||||
------------
|
||||
|
||||
- Pasting text is slow on Windows
|
||||
- Non-ASCII keyboard input has restrictions
|
||||
- The cursor keys move between Unicode code-points, not grapheme clusters
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
This script can be customized through a config file ``script-opts/console.conf``
|
||||
placed in mpv's user directory and through the ``--script-opts`` command-line
|
||||
option. The configuration syntax is described in `ON SCREEN CONTROLLER`_.
|
||||
|
||||
Key bindings can be changed in a standard way, see for example stats.lua
|
||||
documentation.
|
||||
|
||||
Configurable Options
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
``scale``
|
||||
Default: 1
|
||||
|
||||
All drawing is scaled by this value, including the text borders and the
|
||||
cursor.
|
||||
|
||||
If the VO backend in use has HiDPI scale reporting implemented, the option
|
||||
value is scaled with the reported HiDPI scale.
|
||||
|
||||
``font``
|
||||
Default: unset (picks a hardcoded font depending on detected platform)
|
||||
|
||||
Set the font used for the REPL and the console. This probably doesn't
|
||||
have to be a monospaced font.
|
||||
|
||||
``font_size``
|
||||
Default: 16
|
||||
|
||||
Set the font size used for the REPL and the console. This will be
|
||||
multiplied by "scale."
|
||||
+84
-52
@@ -8,29 +8,64 @@ You can encode files from one format/codec to another using this facility.
|
||||
|
||||
``--of=<format>``
|
||||
Specifies the output format (overrides autodetection by the file name
|
||||
extension of the file specified by ``-o``). See ``--of=help`` for a full
|
||||
list of supported formats.
|
||||
extension of the file specified by ``-o``). This can be a comma separated
|
||||
list of possible formats to try. See ``--of=help`` for a full list of
|
||||
supported formats.
|
||||
|
||||
``--ofopts=<options>``
|
||||
Specifies the output format options for libavformat.
|
||||
See ``--ofopts=help`` for a full list of supported options.
|
||||
|
||||
This is a key/value list option. See `List Options`_ for details.
|
||||
Options are managed in lists. There are a few commands to manage the
|
||||
options list.
|
||||
|
||||
``--ofopts-add=<option>``
|
||||
Appends the option given as an argument to the options list. (Passing
|
||||
multiple options is currently still possible, but deprecated.)
|
||||
``--ofopts-add=<options1[,options2,...]>``
|
||||
Appends the options given as arguments to the options list.
|
||||
|
||||
``--ofopts=""``
|
||||
``--ofopts-pre=<options1[,options2,...]>``
|
||||
Prepends the options given as arguments to the options list.
|
||||
|
||||
``--ofopts-del=<index1[,index2,...]>``
|
||||
Deletes the options at the given indexes. Index numbers start at 0,
|
||||
negative numbers address the end of the list (-1 is the last).
|
||||
|
||||
``--ofopts-clr``
|
||||
Completely empties the options list.
|
||||
|
||||
``--ofps=<float value>``
|
||||
Specifies the output format time base (default: 24000). Low values like 25
|
||||
limit video fps by dropping frames.
|
||||
|
||||
``--oautofps``
|
||||
Sets the output format time base to the guessed frame rate of the input
|
||||
video (simulates MEncoder behavior, useful for AVI; may cause frame drops).
|
||||
Note that not all codecs and not all formats support VFR encoding, and some
|
||||
which do have bugs when a target bitrate is specified - use ``--ofps`` or
|
||||
``--oautofps`` to force CFR encoding in these cases.
|
||||
|
||||
``--omaxfps=<float value>``
|
||||
Specifies the minimum distance of adjacent frames (default: 0, which means
|
||||
unset). Content of lower frame rate is not readjusted to this frame rate;
|
||||
content of higher frame rate is decimated to this frame rate.
|
||||
|
||||
``--oharddup``
|
||||
If set, the frame rate given by ``--ofps`` is attained not by skipping time
|
||||
codes, but by duplicating frames (constant frame rate mode).
|
||||
|
||||
``--oneverdrop``
|
||||
If set, frames are never dropped. Instead, time codes of video are
|
||||
readjusted to always increase. This may cause AV desync, though; to work
|
||||
around this, use a high-fps time base using ``--ofps`` and absolutely
|
||||
avoid ``--oautofps``.
|
||||
|
||||
``--oac=<codec>``
|
||||
Specifies the output audio codec. See ``--oac=help`` for a full list of
|
||||
supported codecs.
|
||||
Specifies the output audio codec. This can be a comma separated list of
|
||||
possible codecs to try. See ``--oac=help`` for a full list of supported
|
||||
codecs.
|
||||
|
||||
``--oaoffset=<value>``
|
||||
Shifts audio data by the given time (in seconds) by adding/removing
|
||||
samples at the start. Deprecated.
|
||||
samples at the start.
|
||||
|
||||
``--oacopts=<options>``
|
||||
Specifies the output audio codec options for libavcodec.
|
||||
@@ -41,28 +76,36 @@ You can encode files from one format/codec to another using this facility.
|
||||
"``--oac=libmp3lame --oacopts=b=128000``"
|
||||
selects 128 kbps MP3 encoding.
|
||||
|
||||
This is a key/value list option. See `List Options`_ for details.
|
||||
Options are managed in lists. There are a few commands to manage the
|
||||
options list.
|
||||
|
||||
``--oacopts-add=<option>``
|
||||
Appends the option given as an argument to the options list. (Passing
|
||||
multiple options is currently still possible, but deprecated.)
|
||||
``--oacopts-add=<options1[,options2,...]>``
|
||||
Appends the options given as arguments to the options list.
|
||||
|
||||
``--oacopts=""``
|
||||
``--oacopts-pre=<options1[,options2,...]>``
|
||||
Prepends the options given as arguments to the options list.
|
||||
|
||||
``--oacopts-del=<index1[,index2,...]>``
|
||||
Deletes the options at the given indexes. Index numbers start at 0,
|
||||
negative numbers address the end of the list (-1 is the last).
|
||||
|
||||
``--oacopts-clr``
|
||||
Completely empties the options list.
|
||||
|
||||
``--oafirst``
|
||||
Force the audio stream to become the first stream in the output.
|
||||
By default, the order is unspecified. Deprecated.
|
||||
By default, the order is unspecified.
|
||||
|
||||
``--ovc=<codec>``
|
||||
Specifies the output video codec. See ``--ovc=help`` for a full list of
|
||||
supported codecs.
|
||||
Specifies the output video codec. This can be a comma separated list of
|
||||
possible codecs to try. See ``--ovc=help`` for a full list of supported
|
||||
codecs.
|
||||
|
||||
``--ovoffset=<value>``
|
||||
Shifts video data by the given time (in seconds) by shifting the pts
|
||||
values. Deprecated.
|
||||
values.
|
||||
|
||||
``--ovcopts=<options>``
|
||||
``--ovcopts <options>``
|
||||
Specifies the output video codec options for libavcodec.
|
||||
See --ovcopts=help for a full list of supported options.
|
||||
|
||||
@@ -74,18 +117,32 @@ You can encode files from one format/codec to another using this facility.
|
||||
``"--ovc=libx264 --ovcopts=crf=23"``
|
||||
selects VBR quality factor 23 for H.264 encoding.
|
||||
|
||||
This is a key/value list option. See `List Options`_ for details.
|
||||
Options are managed in lists. There are a few commands to manage the
|
||||
options list.
|
||||
|
||||
``--ovcopts-add=<option>``
|
||||
Appends the option given as an argument to the options list. (Passing
|
||||
multiple options is currently still possible, but deprecated.)
|
||||
``--ovcopts-add=<options1[,options2,...]>``
|
||||
Appends the options given as arguments to the options list.
|
||||
|
||||
``--ovcopts=""``
|
||||
``--ovcopts-pre=<options1[,options2,...]>``
|
||||
Prepends the options given as arguments to the options list.
|
||||
|
||||
``--ovcopts-del=<index1[,index2,...]>``
|
||||
Deletes the options at the given indexes. Index numbers start at 0,
|
||||
negative numbers address the end of the list (-1 is the last).
|
||||
|
||||
``--ovcopts-clr``
|
||||
Completely empties the options list.
|
||||
|
||||
``--ovfirst``
|
||||
Force the video stream to become the first stream in the output.
|
||||
By default, the order is unspecified. Deprecated.
|
||||
By default, the order is unspecified.
|
||||
|
||||
``--ocopyts``
|
||||
Copies input pts to the output video (not supported by some output
|
||||
container formats, e.g. AVI). Discontinuities are still fixed.
|
||||
By default, audio pts are set to playback time and video pts are
|
||||
synchronized to match audio pts, as some output formats do not support
|
||||
anything else.
|
||||
|
||||
``--orawts``
|
||||
Copies input pts to the output video (not supported by some output
|
||||
@@ -93,31 +150,6 @@ You can encode files from one format/codec to another using this facility.
|
||||
and all pts are passed through as-is. Never seek backwards or use multiple
|
||||
input files in this mode!
|
||||
|
||||
``--no-ocopy-metadata``
|
||||
``--no-ometadata``
|
||||
Turns off copying of metadata from input files to output files when
|
||||
encoding (which is enabled by default).
|
||||
|
||||
``--oset-metadata=<metadata-tag[,metadata-tag,...]>``
|
||||
Specifies metadata to include in the output file.
|
||||
Supported keys vary between output formats. For example, Matroska (MKV) and
|
||||
FLAC allow almost arbitrary keys, while support in MP4 and MP3 is more
|
||||
limited.
|
||||
|
||||
This is a key/value list option. See `List Options`_ for details.
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
"``--oset-metadata=title="Output title",comment="Another tag"``"
|
||||
adds a title and a comment to the output file.
|
||||
|
||||
``--oremove-metadata=<metadata-tag[,metadata-tag,...]>``
|
||||
Specifies metadata to exclude from the output file when copying from the
|
||||
input file.
|
||||
|
||||
This is a string list option. See `List Options`_ for details.
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
"``--oremove-metadata=comment,genre``"
|
||||
excludes copying of the the comment and genre tags to the output
|
||||
file.
|
||||
+400
-1519
File diff suppressed because it is too large.
Load diff
+10
-129
@@ -71,18 +71,9 @@ To be able to simultaneously read and write from the IPC pipe, like on Linux,
|
||||
it's necessary to write an external program that uses overlapped file I/O (or
|
||||
some wrapper like .NET's NamedPipeClientStream.)
|
||||
|
||||
You can open the pipe in PuTTY as "serial" device. This is not very
|
||||
comfortable, but gives a way to test interactively without having to write code.
|
||||
|
||||
Protocol
|
||||
--------
|
||||
|
||||
The protocol uses UTF-8-only JSON as defined by RFC-8259. Unlike standard JSON,
|
||||
"\u" escape sequences are not allowed to construct surrogate pairs. To avoid
|
||||
getting conflicts, encode all text characters including and above codepoint
|
||||
U+0020 as UTF-8. mpv might output broken UTF-8 in corner cases (see "UTF-8"
|
||||
section below).
|
||||
|
||||
Clients can execute commands on the player by sending JSON messages of the
|
||||
following form:
|
||||
|
||||
@@ -117,10 +108,7 @@ Because events can occur at any time, it may be difficult at times to determine
|
||||
which response goes with which command. Commands may optionally include a
|
||||
``request_id`` which, if provided in the command request, will be copied
|
||||
verbatim into the response. mpv does not intrepret the ``request_id`` in any
|
||||
way; it is solely for the use of the requester. The only requirement is that
|
||||
the ``request_id`` field must be an integer (a number without fractional parts
|
||||
in the range ``-2^63..2^63-1``). Using other types is deprecated and will
|
||||
currently show a warning. In the future, this will raise an error.
|
||||
way; it is solely for the use of the requester.
|
||||
|
||||
For example, this request:
|
||||
|
||||
@@ -134,8 +122,6 @@ Would generate this response:
|
||||
|
||||
{ "error": "success", "data": 1.468135, "request_id": 100 }
|
||||
|
||||
If you don't specify a ``request_id``, command replies will set it to 0.
|
||||
|
||||
All commands, replies, and events are separated from each other with a line
|
||||
break character (``\n``).
|
||||
|
||||
@@ -147,72 +133,6 @@ with ``#`` and empty lines are ignored.
|
||||
Currently, embedded 0 bytes terminate the current line, but you should not
|
||||
rely on this.
|
||||
|
||||
Data flow
|
||||
---------
|
||||
|
||||
Currently, the mpv-side IPC implementation does not service the socket while a
|
||||
command is executed and the reply is written. It is for example not possible
|
||||
that other events, that happened during the execution of the command, are
|
||||
written to the socket before the reply is written.
|
||||
|
||||
This might change in the future. The only guarantee is that replies to IPC
|
||||
messages are sent in sequence.
|
||||
|
||||
Also, since socket I/O is inherently asynchronous, it is possible that you read
|
||||
unrelated event messages from the socket, before you read the reply to the
|
||||
previous command you sent. In this case, these events were queued by the mpv
|
||||
side before it read and started processing your command message.
|
||||
|
||||
If the mpv-side IPC implementation switches away from blocking writes and
|
||||
blocking command execution, it may attempt to send events at any time.
|
||||
|
||||
You can also use asynchronous commands, which can return in any order, and
|
||||
which do not block IPC protocol interaction at all while the command is
|
||||
executed in the background.
|
||||
|
||||
Asynchronous commands
|
||||
---------------------
|
||||
|
||||
Command can be run asynchronously. This behaves exactly as with normal command
|
||||
execution, except that execution is not blocking. Other commands can be sent
|
||||
while it's executing, and command completion can be arbitrarily reordered.
|
||||
|
||||
The ``async`` field controls this. If present, it must be a boolean. If missing,
|
||||
``false`` is assumed.
|
||||
|
||||
For example, this initiates an asynchronous command:
|
||||
|
||||
::
|
||||
|
||||
{ "command": ["screenshot"], "request_id": 123, "async": true }
|
||||
|
||||
And this is the completion:
|
||||
|
||||
::
|
||||
|
||||
{"request_id":123,"error":"success","data":null}
|
||||
|
||||
By design, you will not get a confirmation that the command was started. If a
|
||||
command is long running, sending the message will lead to any reply until much
|
||||
later when the command finishes.
|
||||
|
||||
Some commands execute synchronously, but these will behave like asynchronous
|
||||
commands that finished execution immediately.
|
||||
|
||||
Cancellation of asynchronous commands is available in the libmpv API, but has
|
||||
not yet been implemented in the IPC protocol.
|
||||
|
||||
Commands with named arguments
|
||||
-----------------------------
|
||||
|
||||
If the ``command`` field is a JSON object, named arguments are expected. This
|
||||
is described in the C API ``mpv_command_node()`` documentation (the
|
||||
``MPV_FORMAT_NODE_MAP`` case). In some cases, this may make commands more
|
||||
readable, while some obscure commands basically require using named arguments.
|
||||
|
||||
Currently, only "proper" commands (as listed by `List of Input Commands`_)
|
||||
support named arguments.
|
||||
|
||||
Commands
|
||||
--------
|
||||
|
||||
@@ -260,7 +180,14 @@ extra commands can also be used as part of the protocol:
|
||||
{ "error": "success" }
|
||||
|
||||
``set_property_string``
|
||||
Alias for ``set_property``. Both commands accept native values and strings.
|
||||
Like ``set_property``, but the argument value must be passed as string.
|
||||
|
||||
Example:
|
||||
|
||||
::
|
||||
|
||||
{ "command": ["set_property_string", "pause", "yes"] }
|
||||
{ "error": "success" }
|
||||
|
||||
``observe_property``
|
||||
Watch a property for changes. If the given property is changed, then an
|
||||
@@ -338,50 +265,4 @@ sometimes sends invalid JSON. If that is a problem for the client application's
|
||||
parser, it should filter the raw data for invalid UTF-8 sequences and perform
|
||||
the desired replacement, before feeding the data to its JSON parser.
|
||||
|
||||
mpv will not attempt to construct invalid UTF-8 with broken "\u" escape
|
||||
sequences. This includes surrogate pairs.
|
||||
|
||||
JSON extensions
|
||||
---------------
|
||||
|
||||
The following non-standard extensions are supported:
|
||||
|
||||
- a list or object item can have a trailing ","
|
||||
- object syntax accepts "=" in addition of ":"
|
||||
- object keys can be unquoted, if they start with a character in "A-Za-z\_"
|
||||
and contain only characters in "A-Za-z0-9\_"
|
||||
- byte escapes with "\xAB" are allowed (with AB being a 2 digit hex number)
|
||||
|
||||
Example:
|
||||
|
||||
::
|
||||
|
||||
{ objkey = "value\x0A" }
|
||||
|
||||
Is equivalent to:
|
||||
|
||||
::
|
||||
|
||||
{ "objkey": "value\n" }
|
||||
|
||||
Alternative ways of starting clients
|
||||
------------------------------------
|
||||
|
||||
You can create an anonymous IPC connection without having to set
|
||||
``--input-ipc-server``. This is achieved through a mpv pseudo scripting backend
|
||||
that starts processes.
|
||||
|
||||
You can put ``.run`` file extension in the mpv scripts directory in its config
|
||||
directory (see the `FILES`_ section for details), or load them through other
|
||||
means (see `Script location`_). These scripts are simply executed with the OS
|
||||
native mechanism (as if you ran them in the shell). They must have a proper
|
||||
shebang and have the executable bit set.
|
||||
|
||||
When executed, a socket (the IPC connection) is passed to them through file
|
||||
descriptor inheritance. The file descriptor is indicated as the special command
|
||||
line argument ``--mpv-ipc-fd=N``, where ``N`` is the numeric file descriptor.
|
||||
|
||||
The rest is the same as with a normal ``--input-ipc-server`` IPC connection. mpv
|
||||
does not attempt to observe or other interact with the started script process.
|
||||
|
||||
This does not work in Windows yet.
|
||||
mpv will not attempt to construct invalid UTF-8 with broken escape sequences.
|
||||
+24
-72
@@ -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(...)``
|
||||
@@ -189,14 +180,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
|
||||
--------------------
|
||||
@@ -249,9 +233,6 @@ Note: ``read_file`` and ``write_file`` throw on errors, allow text content only.
|
||||
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 +265,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 +283,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 +297,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 +318,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 +337,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``.
|
||||
+169
-222
@@ -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
|
||||
--------------------------------------------------
|
||||
|
||||
@@ -101,10 +63,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 +106,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 +206,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 +217,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 +309,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 +370,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 +403,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 +427,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 +479,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
|
||||
----------------
|
||||
|
||||
@@ -653,27 +509,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 +531,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.
|
||||
|
||||
@@ -784,29 +627,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 +682,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 +727,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 +850,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
|
||||
|
||||
+140
-634
File diff suppressed because it is too large.
Load diff
+694
-2088
File diff suppressed because it is too large.
Load diff
+4
-94
@@ -128,7 +128,7 @@ Configuration
|
||||
-------------
|
||||
|
||||
The OSC offers limited configuration through a config file
|
||||
``script-opts/osc.conf`` placed in mpv's user dir and through the
|
||||
``lua-settings/osc.conf`` placed in mpv's user dir and through the
|
||||
``--script-opts`` command-line option. Options provided through the command-line
|
||||
will override those from the config file.
|
||||
|
||||
@@ -167,46 +167,9 @@ Configurable Options
|
||||
``seekbarstyle``
|
||||
Default: bar
|
||||
|
||||
Sets the style of the playback position marker and overall shape
|
||||
of the seekbar: ``bar``, ``diamond`` or ``knob``.
|
||||
|
||||
``seekbarhandlesize``
|
||||
Default: 0.6
|
||||
|
||||
Size ratio of the seek handle if ``seekbarstyle`` is set to ``dimaond``
|
||||
or ``knob``. This is relative to the full height of the seekbar.
|
||||
|
||||
``seekbarkeyframes``
|
||||
Default: yes
|
||||
|
||||
Controls the mode used to seek when dragging the seekbar (default: true). If
|
||||
set to true, default seeking mode is used (usually keyframes, but player
|
||||
defaults and heuristics can change it to exact). If set to false, exact
|
||||
seeking on mouse drags will be used instead. Keyframes are preferred, but
|
||||
exact seeks may be useful in cases where keyframes cannot be found. Note
|
||||
that using exact seeks can potentially make mouse dragging much slower.
|
||||
|
||||
``seekrangestyle``
|
||||
Default: inverted
|
||||
|
||||
Display seekable ranges on the seekbar. ``bar`` shows them on the full
|
||||
height of the bar, ``line`` as a thick line and ``inverted`` as a thin
|
||||
line that is inverted over playback position markers. ``none`` will hide
|
||||
them. Additionally, ``slider`` will show a permanent handle inside the seekbar
|
||||
with cached ranges marked inside. Note that these will look differently
|
||||
based on the seekbarstyle option. Also, ``slider`` does not work with
|
||||
``seekbarstyle`` set to ``bar``.
|
||||
|
||||
``seekrangeseparate``
|
||||
Default: yes
|
||||
|
||||
Controls whether to show line-style seekable ranges on top of the
|
||||
seekbar or separately if ``seekbarstyle`` is set to ``bar``.
|
||||
|
||||
``seekrangealpha``
|
||||
Default: 200
|
||||
|
||||
Alpha of the seekable ranges, 0 (opaque) to 255 (fully transparent).
|
||||
Sets the style of the seekbar, slider (diamond marker), knob (circle
|
||||
marker with guide), or bar (fill).
|
||||
Default pre-0.21.0 was 'slider'.
|
||||
|
||||
``deadzonesize``
|
||||
Default: 0.5
|
||||
@@ -322,59 +285,6 @@ Configurable Options
|
||||
default is conservative to allow wide fonts to be used without overflow.
|
||||
However, with many common fonts a bigger number can be used. YMMV.
|
||||
|
||||
``boxvideo``
|
||||
Default: no
|
||||
|
||||
Whether to overlay the osc over the video (``no``), or to box the video
|
||||
within the areas not covered by the osc (``yes``). If this option is set,
|
||||
the osc may overwrite the ``--video-margin-ratio-*`` options, even if the
|
||||
user has set them. (It will not overwrite them if all of them are set to
|
||||
default values.) Additionally, ``visibility`` must be set to ``always``.
|
||||
Otherwise, this option does nothing.
|
||||
|
||||
Currently, this is supported for the ``bottombar`` and ``topbar`` layout
|
||||
only. The other layouts do not change if this option is set. Separately,
|
||||
if window controls are present (see below), they will be affected
|
||||
regardless of which osc layout is in use.
|
||||
|
||||
The border is static and appears even if the OSC is configured to appear
|
||||
only on mouse interaction. If the OSC is invisible, the border is simply
|
||||
filled with the background color (black by default).
|
||||
|
||||
This currently still makes the OSC overlap with subtitles (if the
|
||||
``--sub-use-margins`` option is set to ``yes``, the default). This may be
|
||||
fixed later.
|
||||
|
||||
This does not work correctly with video outputs like ``--vo=xv``, which
|
||||
render OSD into the unscaled video.
|
||||
|
||||
``windowcontrols``
|
||||
Default: auto (Show window controls if there is no window border)
|
||||
|
||||
Whether to show window management controls over the video, and if so,
|
||||
which side of the window to place them. This may be desirable when the
|
||||
window has no decorations, either because they have been explicitly
|
||||
disabled (``border=no``) or because the current platform doesn't support
|
||||
them (eg: gnome-shell with wayland).
|
||||
|
||||
The set of window controls is fixed, offering ``minimize``, ``maximize``,
|
||||
and ``quit``. Not all platforms implement ``minimize`` and ``maximize``,
|
||||
but ``quit`` will always work.
|
||||
|
||||
``windowcontrols_alignment``
|
||||
Default: right
|
||||
|
||||
If window controls are shown, indicates which side should they be aligned
|
||||
to.
|
||||
|
||||
Supports ``left`` and ``right`` which will place the controls on those
|
||||
respective sides.
|
||||
|
||||
``greenandgrumpy``
|
||||
Default: no
|
||||
|
||||
Set to ``yes`` to reduce festivity (i.e. disable santa hat in December.)
|
||||
|
||||
Script Commands
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
+2
-61
@@ -22,16 +22,7 @@ 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
|
||||
2 Show frame timings
|
||||
==== ==================
|
||||
|
||||
Font
|
||||
@@ -44,7 +35,7 @@ monospaced digits is recommended. By default, the open source font
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
This script can be customized through a config file ``script-opts/stats.conf``
|
||||
This script can be customized through a config file ``lua-settings/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`_.
|
||||
|
||||
@@ -62,22 +53,9 @@ Configurable Options
|
||||
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
|
||||
|
||||
@@ -182,40 +160,3 @@ 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).
|
||||
+96
-317
@@ -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,25 @@ 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:
|
||||
|
||||
``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 +120,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 +163,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 +207,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 +240,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 +261,16 @@ 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.
|
||||
|
||||
``lavfi=graph[:sws-flags[:o=opts]]``
|
||||
Filter video using FFmpeg's libavfilter.
|
||||
@@ -426,31 +351,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 +384,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 +407,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 +425,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 +433,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 +469,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 +492,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 +535,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,117 +549,10 @@ 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
|
||||
which algorithm is actually selected. ``none`` always falls back. On
|
||||
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.
|
||||
|
||||
+88
-217
@@ -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
|
||||
@@ -250,7 +295,7 @@ Available video output drivers are:
|
||||
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
|
||||
quality or performance by changing the ``--gpu-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
|
||||
@@ -263,7 +308,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 +324,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 +380,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 +400,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 +415,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 +425,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,95 +488,26 @@ 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)
|
||||
``--drm-overlay=<number>``
|
||||
Select the DRM overlay index to use.
|
||||
Overlay index is zero based, and related to crtc.
|
||||
(default: 0)
|
||||
|
||||
``mediacodec_embed`` (Android)
|
||||
Renders ``IMGFMT_MEDIACODEC`` frames directly to an ``android.view.Surface``.
|
||||
@@ -638,14 +518,5 @@ Available video output drivers are:
|
||||
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
|
||||
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.
|
||||
@@ -144,6 +144,7 @@ Mac OS X
|
||||
* Native OpenGL backend.
|
||||
* Cocoa event loop is independent from MPlayer's event loop, so user
|
||||
actions like accessing menus and live resizing do not block the playback.
|
||||
* Apple Remote support.
|
||||
* Media Keys support.
|
||||
* VDA support using libavcodec hwaccel API instead of FFmpeg's decoder with up
|
||||
to 2-2.5x reduction in CPU usage.
|
||||
@@ -247,7 +248,7 @@ Command Line Switches
|
||||
``-msglevel`` ``--msg-level`` (changed semantics)
|
||||
``-msgmodule`` ``--msg-module``
|
||||
``-name`` ``--x11-name``
|
||||
``-noar`` ``(removed; replaced by MediaPlayer framework)``
|
||||
``-noar`` ``--no-input-appleremote``
|
||||
``-noautosub`` ``--no-sub-auto``
|
||||
``-noconsolecontrols`` ``--no-input-terminal``
|
||||
``-nosound`` ``--no-audio``
|
||||
|
||||
@@ -20,28 +20,24 @@ Release procedure
|
||||
|
||||
While on master:
|
||||
|
||||
- Update the `RELEASE_NOTES` file, replacing the previous release notes.
|
||||
- Update the `RELEASE_NOTES` file.
|
||||
|
||||
- Update the `VERSION` file.
|
||||
|
||||
- Update `DOCS/client-api-changes.rst` and `DOCS/interface-changes.rst`
|
||||
(in particular, update the last version numbers if necessary)
|
||||
|
||||
- Create signed commit with changes.
|
||||
- Commit changes.
|
||||
|
||||
- Create signed tag v0.X.Y.
|
||||
|
||||
- Add -UNKNOWN suffix to version in `VERSION` file.
|
||||
|
||||
- Commit changes, push release branch (`release/0.X`) and tag to GitHub.
|
||||
- Commit changes, push branch and tag to GitHub.
|
||||
|
||||
- Create a new GitHub release using the content of `RELEASE_NOTES` related to
|
||||
the new version.
|
||||
|
||||
If necessary (to e.g. exclude commits already on master), the release can
|
||||
be done on a branch with different commit history. The release branch **must**
|
||||
then be merged to master so `git describe` will pick up the tag.
|
||||
|
||||
Release notes template
|
||||
----------------------
|
||||
|
||||
|
||||
+12
-427
@@ -15,7 +15,7 @@ player/*.c:
|
||||
* parse command line, add files from the command line to playlist
|
||||
(m_config_parse_mp_command_line())
|
||||
* check help options etc. (call handle_help_options()), possibly exit
|
||||
* call mp_play_files() function that works down the playlist:
|
||||
* call play_files() function that works down the playlist:
|
||||
* run idle loop (idle_loop()), until there are files in the
|
||||
playlist or an exit command was given (only if --idle it set)
|
||||
* actually load and play a file in play_current_file():
|
||||
@@ -30,7 +30,7 @@ player/*.c:
|
||||
* determine next entry on the playlist to play
|
||||
* loop, or exit if no next file or quit is requested
|
||||
(see enum stop_play_reason)
|
||||
* call mp_destroy()
|
||||
* call exit_player_with_rc()
|
||||
* run_playloop():
|
||||
* calls fill_audio_out_buffers()
|
||||
This checks whether new audio needs to be decoded, and pushes it
|
||||
@@ -44,9 +44,9 @@ player/*.c:
|
||||
|
||||
Things worth saying about the playback core:
|
||||
- most state is in MPContext (core.h), which is not available to the
|
||||
subsystems (and should not be made available)
|
||||
subsystems
|
||||
- the currently played tracks are in mpctx->current_tracks, and decoder
|
||||
state in track.dec/d_sub
|
||||
state in track.d_video/d_audio/d_sub
|
||||
- the other subsystems rarely call back into the frontend, and the frontend
|
||||
polls them instead (probably a good thing)
|
||||
- one exceptions are wakeup callbacks, which notify a "higher" component
|
||||
@@ -68,7 +68,6 @@ ta.h & ta.c:
|
||||
as in it doesn't require valgrind.) You can enable it by setting the
|
||||
MPV_LEAK_REPORT environment variable to "1":
|
||||
export MPV_LEAK_REPORT=1
|
||||
Or permanently by building with --enable-ta-leak-report.
|
||||
This will list all unfree'd allocations on exit.
|
||||
|
||||
Documentation can be found here:
|
||||
@@ -104,45 +103,13 @@ options/options.h, options/options.c
|
||||
options.c. Most default values for options and MPOpts are in
|
||||
mp_default_opts at the end of options.c.
|
||||
|
||||
MPOpts is unfortunately quite monolithic, but is being incrementally broken
|
||||
up into sub-structs. Many components have their own sub-option structs
|
||||
separate from MPOpts. New options should be bound to the component that uses
|
||||
them. Add a new option table/struct if needed.
|
||||
|
||||
The global MPOpts still contains the sub-structs as fields, which serves to
|
||||
link them to the option parser. For example, an entry like this may be
|
||||
typical:
|
||||
|
||||
{"", OPT_SUBSTRUCT(demux_opts, demux_conf)},
|
||||
|
||||
This directs the option access code to include all options in demux_conf
|
||||
into the global option list, with no prefix (""), and as part of the
|
||||
MPOpts.demux_opts field. The MPOpts.demux_opts field is actually not
|
||||
accessed anywhere, and instead demux.c does this:
|
||||
|
||||
struct m_config_cache *opts_cache =
|
||||
m_config_cache_alloc(demuxer, global, &demux_conf);
|
||||
struct demux_opts *opts = opts_cache->opts;
|
||||
|
||||
... to get a copy of its options.
|
||||
|
||||
See m_config.h (below) how to access options.
|
||||
MPOpts is unfortunately quite monolithic, and virtually accessed by
|
||||
everything.But some components (like video outputs and video filters) have
|
||||
their own sub-option tables separate from MPOpts.
|
||||
|
||||
The actual option parser is spread over m_option.c, m_config.c, and
|
||||
parse_commandline.c, and uses the option table in options.c.
|
||||
|
||||
options/m_config.h & m_config.c:
|
||||
Code for querying and managing options. This (unfortunately) contains both
|
||||
declarations for the "legacy-ish" global m_config struct, and ways to access
|
||||
options in a threads-safe way anywhere, like m_config_cache_alloc().
|
||||
|
||||
m_config_cache_alloc() lets anyone read, observe, and write options in any
|
||||
thread. The only state it needs is struct mpv_global, which is an opaque
|
||||
type that can be passed "down" the component hierarchy. For safety reasons,
|
||||
you should not pass down any pointers to option structs (like MPOpts), but
|
||||
instead pass down mpv_global, and use m_config_cache_alloc() (or similar)
|
||||
to get a synchronized copy of the options.
|
||||
|
||||
input/input.c:
|
||||
This translates keyboard input coming from VOs and other sources (such
|
||||
as remote control devices like Apple IR or client API commands) to the
|
||||
@@ -172,6 +139,9 @@ stream/*:
|
||||
Some stream inputs are just there to invoke special demuxers, like
|
||||
stream_mf.c. (Basically to make the prefix "mf://" do something special.)
|
||||
|
||||
cache.c is a caching wrapper around streams implementations, needed for
|
||||
smooth network playback.
|
||||
|
||||
demux/:
|
||||
Demuxers split data streams into audio/video/sub streams, which in turn
|
||||
are split in packets. Packets (see demux_packet.h) are mostly byte chunks
|
||||
@@ -185,10 +155,6 @@ demux/:
|
||||
There is a stream header for each audio/video/sub stream, and each of them
|
||||
holds codec information about the stream and other information.
|
||||
|
||||
demux.c is a bit big, the main reason being that it contains the demuxer
|
||||
cache, which is implemented as a list of packets. The cache is complex
|
||||
because it support seeking, multiple ranges, prefetching, and so on.
|
||||
|
||||
video/:
|
||||
This contains several things related to audio/video decoding, as well as
|
||||
video filters.
|
||||
@@ -211,13 +177,13 @@ video/out/:
|
||||
Video output. They also create GUI windows and handle user input. In most
|
||||
cases, the windowing code is shared among VOs, like x11_common.c for X11 and
|
||||
w32_common.c for Windows. The VOs stand between frontend and windowing code.
|
||||
vo_gpu can pick a windowing system at runtime, e.g. the same binary can
|
||||
vo_opengl can pick a windowing system at runtime, e.g. the same binary can
|
||||
provide both X11 and Cocoa support on OSX.
|
||||
|
||||
VOs can be reconfigured at runtime. A vo_reconfig() call can change the video
|
||||
resolution and format, without destroying the window.
|
||||
|
||||
vo_gpu should be taken as reference.
|
||||
vo_opengl should be taken as reference.
|
||||
|
||||
audio/:
|
||||
format.h/format.c define the uncompressed audio formats. (As well as some
|
||||
@@ -274,384 +240,3 @@ sub/:
|
||||
etc/:
|
||||
The file input.conf is actually integrated into the mpv binary by the
|
||||
build system. It contains the default keybindings.
|
||||
|
||||
Best practices and Concepts within mpv
|
||||
======================================
|
||||
|
||||
General contribution etc.
|
||||
-------------------------
|
||||
|
||||
See: DOCS/contribute.md
|
||||
|
||||
Error checking
|
||||
--------------
|
||||
|
||||
If an error is relevant, it should be handled. If it's interesting, log the
|
||||
error. However, mpv often keeps errors silent and reports failures somewhat
|
||||
coarsely by propagating them upwards the caller chain. This is OK, as long as
|
||||
the errors are not very interesting, or would require a developer to debug it
|
||||
anyway (in which case using a debugger would be more convenient, and the
|
||||
developer would need to add temporary debug printfs to get extremely detailed
|
||||
information which would not be appropriate during normal operation).
|
||||
|
||||
Basically, keep a balance on error reporting. But always check them, unless you
|
||||
have a good argument not to.
|
||||
|
||||
Memory allocation errors (OOM) are a special class of errors. Normally such
|
||||
allocation failures are not handled "properly". Instead, abort() is called.
|
||||
(New code should use MP_HANDLE_OOM() for this.) This is done out of laziness and
|
||||
for convenience, and due to the fact that MPlayer/mplayer2 never handled it
|
||||
correctly. (MPlayer varied between handling it correctly, trying to do so but
|
||||
failing, and just not caring, while mplayer2 started using abort() for it.)
|
||||
|
||||
This is justifiable in a number of ways. Error handling paths are notoriously
|
||||
untested and buggy, so merely having them won't make your program more reliable.
|
||||
Having these error handling paths also complicates non-error code, due to the
|
||||
need to roll back state at any point after a memory allocation.
|
||||
|
||||
Take any larger body of code, that is supposed to handle OOM, and test whether
|
||||
the error paths actually work, for example by overriding malloc with a version
|
||||
that randomly fails. You will find bugs quickly, and often they will be very
|
||||
annoying to fix (if you can even reproduce them).
|
||||
|
||||
In addition, a clear indication that something went wrong may be missing. On
|
||||
error your program may exhibit "degraded" behavior by design. Consider a video
|
||||
encoder dropping frames somewhere in the middle of a video due to temporary
|
||||
allocation failures, instead of just exiting with an errors. In other cases, it
|
||||
may open conceptual security holes. Failing fast may be better.
|
||||
|
||||
mpv uses GPU APIs, which may be break on allocation errors (because driver
|
||||
authors will have the same issues as described here), or don't even have a real
|
||||
concept for dealing with OOM (OpenGL).
|
||||
|
||||
libmpv is often used by GUIs, which I predict always break if OOM happens.
|
||||
|
||||
Last but not least, OSes like Linux use "overcommit", which basically means that
|
||||
your program may crash any time OOM happens, even if it doesn't use malloc() at
|
||||
all!
|
||||
|
||||
But still, don't just assume malloc() always succeeds. Use MP_HANDLE_OOM(). The
|
||||
ta* APIs do this for you. The reason for this is that dereferencing a NULL
|
||||
pointer can have security relevant consequences if large offsets are involved.
|
||||
Also, a clear error message is better than a random segfault.
|
||||
|
||||
Some big memory allocations are checked anyway. For example, all code must
|
||||
assume that allocating video frames or packets can fail. (The above example
|
||||
of dropping video frames during encoding is entirely possible in mpv.)
|
||||
|
||||
Undefined behavior
|
||||
------------------
|
||||
|
||||
Undefined behavior (UB) is a concept in the C language. C is famous for being a
|
||||
language that makes it almost impossible to write working code, because
|
||||
undefined behavior is so easily triggered, compilers will happily abuse it to
|
||||
generate "faster" code, debugging tools will shout at you, and sometimes it
|
||||
even means your code doesn't work.
|
||||
|
||||
There is a lot of literature on this topic. Read it.
|
||||
|
||||
(In C's defense, UB exists in other languages too, but since they're not used
|
||||
for low level infrastructure, and/or these languages are at times not rigorously
|
||||
defined, simply nobody cares. However, the C standard committee is still guilty
|
||||
for not addressing this. I'll admit that I can't even tell from the standard's
|
||||
gibberish whether some specific behavior is UB or not. It's written like tax
|
||||
law.)
|
||||
|
||||
In mpv, we generally try to avoid undefined behavior. For one, we want portable
|
||||
and reliable operation. But more importantly, we want clean output from
|
||||
debugging tools, in order to find real bugs more quickly and effectively.
|
||||
|
||||
Avoid the "works in practice" argument. Once debugging tools come into play, or
|
||||
simply when "in practice" stops being true, this will all get back to you in a
|
||||
bad way.
|
||||
|
||||
Global state, library safety
|
||||
----------------------------
|
||||
|
||||
Mutable global state is when code uses global variables that are not read-only.
|
||||
This must be avoided in mpv. Always use context structs that the caller of
|
||||
your code needs to allocate, and whose pointers are passed to your functions.
|
||||
|
||||
Library safety means that your code (or library) can be used by a library
|
||||
without causing conflicts with other library users in the same process. To any
|
||||
piece of code, a "safe" library's API can simply be used, without having to
|
||||
worry about other API users that may be around somewhere.
|
||||
|
||||
Libraries are often not library safe, because they they use global mutable state
|
||||
or other "global" resources. Typical examples include use of signals, simple
|
||||
global variables (like hsearch() in libc), or internal caches not protected by
|
||||
locks.
|
||||
|
||||
A surprisingly high number of libraries are not library safe because they need
|
||||
global initialization. Typically they provide an API function, which
|
||||
"initializes" the library, and which must be called before calling any other
|
||||
API functions. Often, you are to provide global configuration parameters, which
|
||||
can change the behavior of the library. If two libraries A and B use library C,
|
||||
but A and B initialize C with different parameters, something "bad" may happen.
|
||||
In addition, these global initialization functions are often not thread-safe. So
|
||||
if A and B try to initialize C at the same time (from different threads and
|
||||
without knowing about each other), it may cause undefined behavior. (libcurl is
|
||||
a good example of both of these issues. FFmpeg and some TLS libraries used to be
|
||||
affected, but improved.)
|
||||
|
||||
This is so bad because library A and B from the previous example most likely
|
||||
have no way to cooperate, because they're from different authors and have no
|
||||
business knowing each others. They'd need a library D, which wraps library C
|
||||
in a safe way. Unfortunately, typically something worse happens: libraries get
|
||||
"infected" by the unsafeness of its sub-libraries, and export a global init API
|
||||
just to initialize the sub-libraries. In the previous example, libraries A and B
|
||||
would export global init APIs just to init library C, even though the rest of
|
||||
A/B are clean and library safe. (Again, libcurl is an example of this, if you
|
||||
subtract other historic anti-features.)
|
||||
|
||||
The main problem with library safety is that its lack propagates to all
|
||||
libraries using the library.
|
||||
|
||||
We require libmpv to be library safe. This is not really possible, because some
|
||||
libraries are not library safe (FFmpeg, Xlib, partially ALSA). However, for
|
||||
ideological reasons, there is no global init API, and best effort is made to try
|
||||
to avoid problems.
|
||||
|
||||
libmpv has some features that are not library safe, but which are disabled by
|
||||
default (such as terminal usage aka stdout, or JSON IPC blocking SIGPIPE for
|
||||
internal convenience).
|
||||
|
||||
A notable, very disgustingly library unsafe behavior of libmpv is calling
|
||||
abort() on some memory allocation failure. See error checking section.
|
||||
|
||||
Logging
|
||||
-------
|
||||
|
||||
All logging and terminal output in mpv goes through the functions and macros
|
||||
provided in common/msg.h. This is in part for library safety, and in part to
|
||||
make sure users can silence all output, or to redirect the output elsewhere,
|
||||
like a log file or the internal console.lua script.
|
||||
|
||||
Locking
|
||||
-------
|
||||
|
||||
See generally available literature. In mpv, we use pthread for this.
|
||||
|
||||
Always keep locking clean. Don't skip locking just because it will work "in
|
||||
practice". (See undefined behavior section.) If your use case is simple, you may
|
||||
use C11 atomics (osdep/atomic.h for partial C99 support), but most likely you
|
||||
will only hurt yourself and others.
|
||||
|
||||
Always make clear which fields in a struct are protected by which lock. If a
|
||||
field is immutable, or simply not thread-safe (e.g. state for a single worker
|
||||
thread), document it as well.
|
||||
|
||||
Internal mpv APIs are assumed to be not thread-safe by default. If they have
|
||||
special guarantees (such as being usable by more than one thread at a time),
|
||||
these should be explicitly documented.
|
||||
|
||||
All internal mpv APIs must be free of global state. Even if a component is not
|
||||
thread-safe, multiple threads can use _different_ instances of it without any
|
||||
locking.
|
||||
|
||||
On a side note, recursive locks may seem convenient at first, but introduce
|
||||
additional problems with condition variables and locking hierarchies. They
|
||||
should be avoided.
|
||||
|
||||
Locking hierarchy
|
||||
-----------------
|
||||
|
||||
A simple way to avoid deadlocks with classic locking is to define a locking
|
||||
hierarchy or lock order. If all threads acquire locks in the same order, no
|
||||
deadlocks will happen.
|
||||
|
||||
For example, a "leaf" lock is a lock that is below all other locks in the
|
||||
hierarchy. You can acquire it any time, as long as you don't acquire other
|
||||
locks while holding it.
|
||||
|
||||
Unfortunately, C has no way to declare or check the lock order, so you should at
|
||||
least document it.
|
||||
|
||||
In addition, try to avoid exposing locks to the outside. Making the declaration
|
||||
of a lock private to a specific .c file (and _not_ exporting accessors or
|
||||
lock/unlock functions that manipulate the lock) is a good idea. Your component's
|
||||
API may acquire internal locks, but should release them when returning. Keeping
|
||||
the entire locking in a single file makes it easy to check it.
|
||||
|
||||
Avoiding callback hell
|
||||
----------------------
|
||||
|
||||
mpv code is separated in components, like the "frontend" (i.e. MPContext mpctx),
|
||||
VOs, AOs, demuxers, and more. The frontend usually calls "down" the usage
|
||||
hierarchy: mpctx almost on top, then things like vo/ao, and utility code on the
|
||||
very bottom.
|
||||
|
||||
"Callback hell" is when when components call both up and down the hierarchy,
|
||||
which for example leads to accidentally recursion, reentrancy problems, or
|
||||
locking nightmares. This is avoided by (mostly) calling only down the hierarchy.
|
||||
Basically the call graph forms a DAG. The other direction is handled by event
|
||||
queues, wakeup callbacks, and similar mechanisms.
|
||||
|
||||
Typically, a component provides an API, and does not know anything about its
|
||||
user. The API user (component higher in the hierarchy) polls the state of the
|
||||
lower component when needed.
|
||||
|
||||
This also enforces some level of modularization, and with some luck the locking
|
||||
hierarchy. (Basically, locks of lower components automatically become leaf
|
||||
locks.) Another positive effect is simpler memory management.
|
||||
|
||||
(Also see e.g.: http://250bpm.com/blog:24)
|
||||
|
||||
Wakeup callbacks
|
||||
----------------
|
||||
|
||||
This is a common concept in mpv. Even the public API uses it. It's used when an
|
||||
API has internal threads (or otherwise triggers asynchronous events), but the
|
||||
component call hierarchy needs to be kept. The wakeup callback is the only
|
||||
exception to the call hierarchy, and always calls up.
|
||||
|
||||
For example, vo spawns a thread that the API user (the mpv frontend) does not
|
||||
need to know about. vo simply provides a single-threaded API (or that looks like
|
||||
one). This API needs a way to notify the API user of new events. But the vo
|
||||
event producer is on the vo thread - it can't simply invoke a callback back into
|
||||
the API user, because then the API user has to deal with locking, despite not
|
||||
using threads. In addition, this will probably cause problems like mentioned in
|
||||
the "callback hell" section, especially lock order issues.
|
||||
|
||||
The solution is the wakeup callback. It merely unblocks the API user from
|
||||
waiting, and the API user then uses the normal vo API to examine whether or
|
||||
which state changed. As a concept, it documents what a wakeup callback is
|
||||
allowed to do and what not, to avoid the aforementioned problems.
|
||||
|
||||
Generally, you are not allowed to call any API from the wakeup callback. You
|
||||
just do whatever is needed to unblock your thread. For example, if it's waiting
|
||||
on a mutex/condition variable, acquire the mutex, set a change flag, signal
|
||||
the condition variable, unlock, return. (This mutex must not be held when
|
||||
calling the API. It must be a leaf lock.)
|
||||
|
||||
Restricting the wakeup callback like this sidesteps any reentrancy issues and
|
||||
other complexities. The API implementation can simply hold internal (and
|
||||
non-recursive) locks while invoking the wakeup callback.
|
||||
|
||||
The API user still needs to deal with locking (probably), but there's only the
|
||||
need to implement a single "receiver", that can handle the entire API of the
|
||||
used component. (Or multiple APIs - MPContext for example has only 1 wakeup
|
||||
callback that handles all AOs, VOs, input, demuxers, and more. It simple re-runs
|
||||
the playloop.)
|
||||
|
||||
You could get something more advanced by turning this into a message queue. The
|
||||
API would append a message to the queue, and the API user can read it. But then
|
||||
you still need a way to "wakeup" the API user (unless you force the API user
|
||||
to block on your API, which will make things inconvenient for the API user). You
|
||||
also need to worry about what happens if the message queue overruns (you either
|
||||
lose messages or have unbounded memory usage). In the mpv public API, the
|
||||
distinction between message queue and wakeup callback is sort of blurry, because
|
||||
it does provide a message queue, but an additional wakeup callback, so API
|
||||
users are not required to call mpv_wait_event() with a high timeout.
|
||||
|
||||
mpv itself prefers using wakeup callbacks over a generic event queue, because
|
||||
most times an event queue is not needed (or complicates things), and it is
|
||||
better to do it manually.
|
||||
|
||||
(You could still abstract the API user side of wakeup callback handling, and
|
||||
avoid reimplementing it all the time. Although mp_dispatch_queue already
|
||||
provides mechanisms for this.)
|
||||
|
||||
Condition variables
|
||||
-------------------
|
||||
|
||||
They're used whenever a thread needs to wait for something, without nonsense
|
||||
like sleep calls or busy waiting. mpv uses the standard pthread API for this.
|
||||
There's a lot of literature on it. Read it.
|
||||
|
||||
For initial understanding, it may be helpful to know that condition variables
|
||||
are not variables that signal a condition. pthread_cond_t does not have any
|
||||
state per-se. Maybe pthread_cond_t would better be named pthread_interrupt_t,
|
||||
because its sole purpose is to interrupt a thread waiting via pthread_cond_wait()
|
||||
(or similar). The "something" in "waiting for something" can be called
|
||||
predicate (to avoid confusing it with "condition"). Consult literature for the
|
||||
proper terms.
|
||||
|
||||
The very short version is...
|
||||
|
||||
Shared declarations:
|
||||
|
||||
pthread_mutex_t lock;
|
||||
pthread_cond_t cond_var;
|
||||
struct something state_var; // protected by lock, changes signaled by cond_var
|
||||
|
||||
Waiter thread:
|
||||
|
||||
pthread_mutex_lock(&lock);
|
||||
|
||||
// Wait for a change in state_var. We want to wait until predicate_fulfilled()
|
||||
// returns true.
|
||||
// Must be a loop for 2 reasons:
|
||||
// 1. cond_var may be associated with other conditions too
|
||||
// 2. pthread_cond_wait() can have sporadic wakeups
|
||||
while (!predicate_fulfilled(&state_var)) {
|
||||
// This unlocks, waits for cond_var to be signaled, and then locks again.
|
||||
// The _whole_ point of cond_var is that unlocking and waiting for the
|
||||
// signal happens atomically.
|
||||
pthread_cond_wait(&cond_var, &lock);
|
||||
}
|
||||
|
||||
// Here you may react to the state change. The state cannot change
|
||||
// asynchronously as long as you still hold the lock (and didn't release
|
||||
// and reacquire it).
|
||||
// ...
|
||||
|
||||
pthread_mutex_unlock(&lock);
|
||||
|
||||
Signaler thread:
|
||||
|
||||
pthread_mutex_lock(&lock);
|
||||
|
||||
// Something changed. Update the shared variable with the new state.
|
||||
update_state(&state_var);
|
||||
|
||||
// Notify that something changed. This will wake up the waiter thread if
|
||||
// it's blocked in pthread_cond_wait(). If not, nothing happens.
|
||||
pthread_cond_broadcast(&cond_var);
|
||||
|
||||
// Fun fact: good implementations wake up the waiter only when the lock is
|
||||
// released, to reduce kernel scheduling overhead.
|
||||
pthread_mutex_unlock(&lock);
|
||||
|
||||
Some basic rules:
|
||||
1. Always access your state under proper locking
|
||||
2. Always check your predicate before every call to pthread_cond_wait()
|
||||
(And don't call pthread_cond_wait() if the predicate is fulfilled.)
|
||||
3. Always call pthread_cond_wait() in a loop
|
||||
(And only if your predicate failed without releasing the lock..)
|
||||
4. Always call pthread_cond_broadcast()/_signal() inside of its associated
|
||||
lock
|
||||
|
||||
mpv sometimes violates rule 3, and leaves "retrying" (i.e. looping) to the
|
||||
caller.
|
||||
|
||||
Common pitfalls:
|
||||
- Thinking that pthread_cond_t is some kind of semaphore, or holds any
|
||||
application state or the user predicate (it _only_ wakes up threads
|
||||
that are at the same time blocking on pthread_cond_wait() and friends,
|
||||
nothing else)
|
||||
- Changing the predicate, but not updating all pthread_cond_broadcast()/
|
||||
_signal() calls correctly
|
||||
- Forgetting that pthread_cond_wait() unlocks the lock (other threads can
|
||||
and must acquire the lock)
|
||||
- Holding multiple nested locks while trying to wait (=> deadlock, violates
|
||||
the lock order anyway)
|
||||
- Waiting for a predicate correctly, but unlocking/relocking before acting
|
||||
on it (unlocking allows arbitrary state changes)
|
||||
- Confusing which lock/condition var. is used to manage a bit of state
|
||||
|
||||
Generally available literature probably has better examples and explanations.
|
||||
|
||||
Using condition variables the proper way is generally preferred over using more
|
||||
messy variants of them. (Just saying because on win32, "SetEvent" exists, and
|
||||
it's inferior to condition variables. Try to avoid the win32 primitives, even if
|
||||
you're dealing with Windows-only code.)
|
||||
|
||||
Threads
|
||||
-------
|
||||
|
||||
Threading should be conservatively used. Normally, mpv code pretends to be
|
||||
single-threaded, and provides thread-unsafe APIs. Threads are used coarsely,
|
||||
and if you can avoid messing with threads, you should. For example, VOs and AOs
|
||||
do not need to deal with threads normally, even though they run on separate
|
||||
threads. The glue code "isolates" them from any threading issues.
|
||||
@@ -1,6 +1,8 @@
|
||||

|
||||

|
||||
|
||||
# mpv
|
||||
## mpv
|
||||
|
||||
--------------
|
||||
|
||||
|
||||
* [External links](#external-links)
|
||||
@@ -9,9 +11,12 @@
|
||||
* [Downloads](#downloads)
|
||||
* [Changelog](#changelog)
|
||||
* [Compilation](#compilation)
|
||||
* [FFmpeg vs. Libav](#ffmpeg-vs-libav)
|
||||
* [FFmpeg ABI compatibility](#ffmpeg-abi-compatibility)
|
||||
* [Release cycle](#release-cycle)
|
||||
* [Bug reports](#bug-reports)
|
||||
* [Contributing](#contributing)
|
||||
* [Relation to MPlayer and mplayer2](#relation-to-mplayer-and-mplayer2)
|
||||
* [License](#license)
|
||||
* [Contact](#contact)
|
||||
|
||||
@@ -20,17 +25,15 @@
|
||||
|
||||
|
||||
* [Wiki](https://github.com/mpv-player/mpv/wiki)
|
||||
* [FAQ][FAQ]
|
||||
* [FAQ](https://github.com/mpv-player/mpv/wiki/FAQ)
|
||||
* [Manual](http://mpv.io/manual/master/)
|
||||
|
||||
|
||||
## 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].
|
||||
|
||||
@@ -40,14 +43,12 @@ Releases can be found on the [release list][releases].
|
||||
- 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 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
|
||||
|
||||
@@ -115,9 +116,9 @@ Essential dependencies (incomplete list):
|
||||
- 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)
|
||||
|
||||
Libass dependencies (when building libass):
|
||||
Libass dependencies:
|
||||
|
||||
- gcc or clang, yasm on x86 and x86_64
|
||||
- fribidi, freetype, fontconfig development headers (for libass)
|
||||
@@ -125,17 +126,13 @@ 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,
|
||||
@@ -148,6 +145,28 @@ 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).
|
||||
|
||||
|
||||
## 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
|
||||
@@ -184,32 +203,42 @@ 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.
|
||||
|
||||
## Relation to MPlayer and mplayer2
|
||||
|
||||
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.
|
||||
|
||||
For details see [FAQ entry](https://github.com/mpv-player/mpv/wiki/FAQ#How_is_mpv_related_to_MPlayer).
|
||||
|
||||
If you are wondering what's different from mplayer2 and MPlayer, an incomplete
|
||||
and largely unmaintained list of changes is located [here][mplayer-changes].
|
||||
|
||||
## License
|
||||
|
||||
GPLv2 "or later" by default, LGPLv2.1 "or later" with `--enable-lgpl`.
|
||||
See [details.](https://github.com/mpv-player/mpv/blob/master/Copyright)
|
||||
|
||||
## History
|
||||
|
||||
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].
|
||||
|
||||
## 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
|
||||
|
||||
+123
-112
@@ -1,12 +1,27 @@
|
||||
Release 0.33.0
|
||||
Release 0.28.1
|
||||
==============
|
||||
|
||||
This release requires FFmpeg 4.0 or newer.
|
||||
This release fixes CVE-2018-6360.
|
||||
|
||||
For packagers: Note that mpv's build system is only supported on Python 3.
|
||||
If you use the `bootstrap.py` script it will take care of this, otherwise you
|
||||
need to explicitly invoke the build system using `python3 waf`.
|
||||
Fixes and Minor Enhancements
|
||||
----------------------------
|
||||
- ytdl_hook: whitelist protocols from urls retrieved from youtube-dl (#5456)
|
||||
|
||||
Release 0.28.0
|
||||
==============
|
||||
|
||||
This release needs recent FFmpeg (newer than 3.4) due to major refactoring.
|
||||
Required library versions:
|
||||
|
||||
* libavutil >= 56.6.100
|
||||
* libavcodec >= 58.7.100
|
||||
* libavformat >= 58.0.102
|
||||
* libswscale >= 5.0.101
|
||||
* libavfilter >= 7.0.101
|
||||
* libswresample >= 3.0.100
|
||||
|
||||
The LGPL 2.1+ relicensing process is now mostly complete. The remaining GPL-only
|
||||
code (see Copyright file) can be disabled at build time by using --enable-lgpl.
|
||||
|
||||
Features
|
||||
--------
|
||||
@@ -14,53 +29,33 @@ Features
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- scripting: load scripts from directories
|
||||
- mac: activate logging when started from the bundle
|
||||
- ytdl_hook.lua: delay load subtitles
|
||||
- sub: add an option to filter subtitles by regex
|
||||
- scripting: add a way to run subprocesses as "scripts"
|
||||
- command: implement asynchronous commands and support for named arguments
|
||||
- player: add optional separate video decoding thread
|
||||
- vo_gpu: d3d11: add support for exclusive fullscreen
|
||||
- w32_common: Support HiDPI on Windows
|
||||
- vo_x11: add 10 bit support
|
||||
- vo_gpu: add BT.2390 tone-mapping
|
||||
- client API: add software rendering API
|
||||
- audio: add scaletempo2 filter based on chromium
|
||||
- auto_profiles: add this script
|
||||
- stream: Implement slice:// for reading slices of streams
|
||||
- player: add automatic loading of external cover art files
|
||||
- vo_sixel: implement terminal video output using sixel
|
||||
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- sws_utils: use zimg by default if available
|
||||
- build: pick up Lua 5.2 by default (preferred over 5.1)
|
||||
- build: disable RPI vendor blob auto-detection in favor of open-source stack
|
||||
- build: make C11 atomics mandatory
|
||||
- build: make libass non-optional
|
||||
- player: stricter filename matching for external subtitle auto-loading
|
||||
- lua: support Unicode paths in script loading and IO library
|
||||
- vo_direct3d: rip out texture video rendering path and dumb down OSD rendering
|
||||
- audio: rewrite internal audio handling and AO API
|
||||
- build: disable GLX by default
|
||||
- Add DRM_PRIME Format Handling and Display for RockChip MPP decoders
|
||||
- csputils: Add support for Display P3 primaries
|
||||
- demux: support multiple seekable cached ranges, display cache ranges on OSC
|
||||
- demux_playlist: support .url files (#5107)
|
||||
- dvb: Add multiple frontends support (up to 8)
|
||||
- dvb: implement parsing of modulation for VDR-style channels config
|
||||
- hwdec: add mediacodec hardware decoder for IMGFMT_MEDIACODEC frames,
|
||||
rename mediacodec to mediacodec-copy
|
||||
- lua: integrate stats.lua script (bound to i/I by default)
|
||||
- vd_lavc: add support for nvdec hwaccel
|
||||
- vo_gpu: add android opengl backend
|
||||
- vo_gpu: initial d3d11 support
|
||||
- vo_gpu: vulkan support
|
||||
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- stream_libarchive: disable tar support due to bugs
|
||||
- Remove remains of Libav compatibility
|
||||
- stream_smb: remove due to lack of thread safety and the abundance
|
||||
of alternatives, FFmpeg still includes SMB support
|
||||
- command: remove legacy hook API (has been deprecated for a long time)
|
||||
- client API: remove deprecated qthelper.hpp header
|
||||
- removed audio outputs: sndio, rsound, oss
|
||||
- x11: remove xdg-screensaver invocations that supported dbus based idle inhibit
|
||||
- client API: deactivate the opengl_cb API
|
||||
- build system: drop Python 2 compatbility
|
||||
- af: remove deprecated audio filters (channels, equalizer, pan, volume;
|
||||
replacements in lavfi)
|
||||
- vf: remove most GPL video filters (crop, dsize, expand, flip, gradfun, mirror,
|
||||
noformat, pullup, rotate, scale, stereo3d, yadif; replacements in lavfi)
|
||||
- vf_buffer: remove this filter
|
||||
- video: remove automatic stereo3d filter insertion
|
||||
- vo_gpu: remove hwdec_vaglx interop
|
||||
- vo_opengl: refactor into vo_gpu
|
||||
- vo_wayland: remove
|
||||
|
||||
|
||||
Options and Commands
|
||||
@@ -69,90 +64,106 @@ Options and Commands
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- demux: add option to disable cache "sharing" between back and forward buffers
|
||||
- player: add ab-loop-count option/property
|
||||
- ytdl_hook: add a way to use ytdl's default formats
|
||||
- ytdl_hook: add all_formats option that loads all formats that were found
|
||||
- demux_mkv: document probe-start-time option and enable it by default
|
||||
- command: extend osd-overlay command with bounds reporting
|
||||
- player: a number of new playlist contol commands/properties
|
||||
- ipc: add --input-ipc-client option
|
||||
- options: add option to control display-sync factor
|
||||
- vo_gpu: add better gamut clipping option
|
||||
- vo_gpu: vulkan: add ability to disable events
|
||||
- x11: add option to make window appear on a specific workspace
|
||||
- wayland: expose wayland-app-id as a user option
|
||||
- player: add --subs-with-matching-audio option
|
||||
- command: add read-only focused property
|
||||
- screenshot: option to use software rendering for screenshots
|
||||
- command: add delete-watch-later-config
|
||||
- command: new property mouse-pos with current position and hover state
|
||||
- demux: add option to create CC tracks eagerly (--sub-create-cc-track)
|
||||
- options: add --start=none to reset previously set start time
|
||||
- options: add --vlang switch
|
||||
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- options: remove deprecation warning for "-foo bar" syntax
|
||||
- player: make audio hr-seek default
|
||||
- ad_lavc: disable decoder downmix by default
|
||||
- command: support save-position-on-quit for "stop" command too
|
||||
- command: extend subprocess command
|
||||
- options: do not accept ":" as separator anymore in key/value lists
|
||||
|
||||
|
||||
Deprecated
|
||||
~~~~~~~~~~
|
||||
|
||||
- demux: deprecate --cache-secs
|
||||
- cache: lower default size to 2*10MB
|
||||
- demux: bump the demuxer cache readahead duration to 10 hours
|
||||
- demux: use seekable cache for network by default, bump prefetch limit
|
||||
- msg: make --msg-level affect --log-file too
|
||||
- player/misc.c: allow both --length and --end to control play endpoint
|
||||
- player: match subtitles with language tags with --sub-auto=exact
|
||||
- rename --opengl-hwdec-interop to --gpu-hwdec-interop (now mostly useless)
|
||||
- vd_lavc: prefer nvdec over vdpau with --hwdec=auto (better codec and surface
|
||||
format support)
|
||||
- vd_lavc: rename --hwdec=rpi to --hwdec=mmal
|
||||
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- wayland: remove wayland-frame-wait-offset option
|
||||
- input: remove deprecated --input-file option
|
||||
- vo_vdpau: remove deprecated/inactive --vo-vdpau-deint option
|
||||
- options: remove --heartbeat-cmd and --heartbeat-interval
|
||||
(incidentally fixes #4888)
|
||||
|
||||
|
||||
Fixes and Minor Enhancements
|
||||
----------------------------
|
||||
|
||||
- options: stop hiding deprecated options from --help output
|
||||
- lua, js: add mp.get_script_directory() function
|
||||
- lua: fix security relevant loading order issue with scripts
|
||||
- player: make screenshot each-frame mode more accurate
|
||||
- bash completion: complete ao/af/vo/vf options
|
||||
- zimg: add alpha support
|
||||
- wayland: make resizing better
|
||||
- edl: add mechanism for delay loading streams
|
||||
- stream_file: fix caching-related performance regression on CIFS
|
||||
- cocoa-cb: fix crashes and issues with UI updates
|
||||
- sub, demux: improve behavior with negative subtitle delay/muxed subs
|
||||
- demux: make seek ranges work for static images + audio
|
||||
- cocoa-cb: support maximize/minimize on startup
|
||||
- umpv: change from legacy FIFO to socket
|
||||
- stats: move input speed to cache page, make it a graph
|
||||
- stats: add fourth page with performance graphs
|
||||
- command: print edition title to OSD when cycling
|
||||
- zimg: add support for big endian input and output
|
||||
- demux_mkv: add png intra support
|
||||
- build: detect VT_GETMODE on FreeBSD and DragonFly
|
||||
- win32: use windows 10 native virtual-terminal if available
|
||||
- vo_gpu: enable frame caching for still frames
|
||||
- command: add property to return text subtitles in ASS
|
||||
- vo_gpu: ra_pl: add timers support
|
||||
- build: allow vo_wlshm on more Wayland platforms (e.g. FreeBSD)
|
||||
- zimg: add slice threading and use it by default
|
||||
- command: add a way to access properties of a current track
|
||||
- vo_gpu: EGL: fix transparency on X11/EGL/Mesa
|
||||
- vd_lavc: add AV1 to the default allowed hwdec codec list
|
||||
- TOOLS/autoload.lua: add ogm, ogg and opus extensions
|
||||
- Use /dev/tty instead of stdin for terminal input (#4190)
|
||||
- audio: add audio softvol processing to AO (replaces previously GPL’d code)
|
||||
- audio: fix channel conversion with NA channels (e.g. with ALSA)
|
||||
- audio: fix missing volume update on init and reinit
|
||||
- csputils: Fix DCI P3 primaries white point
|
||||
- demux: don't allow subtitles to mess up buffered time display
|
||||
- demux: fix .cue files with audio files that contain attached pictures
|
||||
- demux: fix accounting for seekable ranges on track switches (fixes missing
|
||||
audio when cycling through audio tracks with e.g. EDL, --merge-files, ordered
|
||||
chapters and youtube-dl pseudo DASH)
|
||||
- demux: fix crash with cue/ordered chapter files (#5027)
|
||||
- demux: speed up cache seeking with a coarse index
|
||||
- demux_lavf: always give libavformat the filename when probing
|
||||
(helps with mp3 files)
|
||||
- demux_mkv: add V_SNOW tag to mkv_video_tags
|
||||
- dvb: Fix long channel switching: next/prev channel
|
||||
- dvb: fixes for ATSC tuning
|
||||
- lavc_conv: clamp timestamps to positive (#5047)
|
||||
- macOS: fix bundle on macOS High Sierra (10.13) (#4926, #4866)
|
||||
- mp_image: always copy color attributes on hw download (#4804)
|
||||
- mp_image: select an explicit fallback for chroma location (#4804)
|
||||
- msg: bump log level of --log-file to -v -v
|
||||
- msg: reinterpret a bunch of message levels
|
||||
- osc: fix rare stack overflow when changing visibility mode
|
||||
- osdep/io: add android-related bullshit to fix files >2 GiB
|
||||
- player/playloop.c: respect playback start time when using --loop-file
|
||||
- player: allow seeking in cached parts of unseekable streams
|
||||
- player: make track language matching case insensitive (#5272)
|
||||
- player: use start timestamp for ab-looping if --ab-loop-a is absent
|
||||
- player: when loading external file, always add all track types (#5132)
|
||||
- restore-old-bindings.conf: add old macOS/Wayland AXIS bindings
|
||||
- screenshot: create directories from template
|
||||
- scripting: report dlerror() output
|
||||
- sd_ass: accept RFC8081 font media types
|
||||
- sd_ass: accept otc as fallback OpenType collection file extension
|
||||
- stream_libarchive: work around various types of locale braindeath
|
||||
(https://git.io/vbiFJ)
|
||||
- subprocess-win: don't change the mouse cursor when creating processes
|
||||
- video: add a hack to avoid missing subtitles with vf_sub (#5194)
|
||||
- video: fix alpha handling (#4983)
|
||||
- video: fix memory leaks (roughly 1 KB per decoded frame) with hwdec copy modes
|
||||
- video: fix rotation and deinterlace auto filters
|
||||
- video: properly pass through ICC data
|
||||
- vo: add support for externally driven renderloop and make wayland use it
|
||||
(partially fixes display-sync under wayland; disables rendering when window
|
||||
is invisible)
|
||||
- vo_gpu: change --tone-mapping-desaturate algorithm
|
||||
- vo_gpu: enable 3DLUTs in dumb mode
|
||||
- vo_gpu: fix gamma scale
|
||||
- vo_gpu: fix mobius tone mapping compatibility to GLSL 120 (#5069)
|
||||
- vo_gpu: fix video sometimes not being rerendered on equalizer change
|
||||
- vo_gpu: kill off FBOTEX_FUZZY (#1814)
|
||||
- vo_gpu: opengl: fix possible screenshot window crash (#4905)
|
||||
- vo_gpu: opengl: use GLX_MESA_swap_control where available
|
||||
- vo_gpu: reduce the --alpha=blend-tiles checkerboard intensity
|
||||
- vo_gpu: win: remove exclusive-fullscreen detection hack
|
||||
- vo_lavc: remove messy delayed subtitle rendering logic (#4689)
|
||||
- wayland_common: implement output tracking, many cleanups and bugfixes
|
||||
- Windows: skip window snapping if Windows handled it
|
||||
- Windows: add more-POSIXy versions of open() and fstat() (#4711)
|
||||
- ytdl_hook: don't prepend ytdl:// to non-youtube links in playlists (#5003)
|
||||
|
||||
|
||||
This listing is not complete. Check DOCS/client-api-changes.rst for a history
|
||||
of changes to the client API, and DOCS/interface-changes.rst for a history
|
||||
of changes to other user-visible interfaces.
|
||||
|
||||
A complete changelog can be seen by running `git log v0.32.0..v0.33.0`
|
||||
A complete changelog can be seen by running `git log v0.27.0..v0.28.0`
|
||||
in the git repository or by visiting either
|
||||
https://github.com/mpv-player/mpv/compare/v0.32.0...v0.33.0 or
|
||||
https://git.srsfckn.biz/mpv/log/?qt=range&q=v0.32.0..v0.33.0
|
||||
https://github.com/mpv-player/mpv/compare/v0.27.0...v0.28.0 or
|
||||
https://git.srsfckn.biz/mpv/log/?qt=range&q=v0.27.0..v0.28.0
|
||||
|
||||
@@ -10,12 +10,15 @@ export PYTHON=/usr/bin/python3
|
||||
"$PYTHON" bootstrap.py
|
||||
"$PYTHON" waf configure \
|
||||
--check-c-compiler=gcc \
|
||||
--enable-spirv-cross \
|
||||
--disable-egl-angle-lib \
|
||||
--enable-crossc \
|
||||
--enable-d3d-hwaccel \
|
||||
--enable-d3d11 \
|
||||
--enable-egl-angle \
|
||||
--enable-jpeg \
|
||||
--enable-lcms2 \
|
||||
--enable-libarchive \
|
||||
--enable-libass \
|
||||
--enable-lua \
|
||||
--enable-rubberband \
|
||||
--enable-shaderc \
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
#!/usr/bin/bash
|
||||
set -e
|
||||
|
||||
export PYTHON=/usr/bin/python3
|
||||
|
||||
# Write an empty fonts.conf to speed up fc-cache
|
||||
export FONTCONFIG_FILE=/dummy-fonts.conf
|
||||
cat >"$FONTCONFIG_FILE" <<EOF
|
||||
@@ -14,12 +12,12 @@ EOF
|
||||
# Install build dependencies for mpv
|
||||
pacman -S --noconfirm --needed \
|
||||
$MINGW_PACKAGE_PREFIX-toolchain \
|
||||
$MINGW_PACKAGE_PREFIX-angleproject-git \
|
||||
$MINGW_PACKAGE_PREFIX-cmake \
|
||||
$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 \
|
||||
@@ -54,19 +52,21 @@ pacman -Sc --noconfirm
|
||||
# Compile shaderc
|
||||
(
|
||||
git clone --depth=1 https://github.com/google/shaderc && cd shaderc
|
||||
"$PYTHON" utils/git-sync-deps
|
||||
git clone --depth=1 https://github.com/google/glslang.git third_party/glslang
|
||||
git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Tools.git third_party/spirv-tools
|
||||
git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Headers.git third_party/spirv-headers
|
||||
|
||||
mkdir build && cd build
|
||||
cmake -GNinja -DCMAKE_BUILD_TYPE=Release -DSHADERC_SKIP_TESTS=ON \
|
||||
-DCMAKE_INSTALL_PREFIX=$MINGW_PREFIX ..
|
||||
ninja install
|
||||
cp -f libshaderc/libshaderc_shared.dll $MINGW_PREFIX/bin/
|
||||
)
|
||||
|
||||
# Compile SPIRV-Cross
|
||||
# Compile crossc
|
||||
(
|
||||
git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Cross && cd SPIRV-Cross
|
||||
git clone --depth=1 https://github.com/rossy/crossc && cd crossc
|
||||
git submodule update --init
|
||||
|
||||
mkdir build && cd build
|
||||
cmake -GNinja -DSPIRV_CROSS_SHARED=ON -DCMAKE_INSTALL_PREFIX=$MINGW_PREFIX ..
|
||||
ninja install
|
||||
make -j4 install prefix=$MINGW_PREFIX
|
||||
)
|
||||
+24
-60
@@ -20,24 +20,12 @@ def is_user_lib(objfile, libname):
|
||||
not "libc." in libname and \
|
||||
not "libgcc." in libname and \
|
||||
not os.path.basename(libname) == 'Python' and \
|
||||
not os.path.basename(objfile) in libname and \
|
||||
not "libswift" in libname
|
||||
not os.path.basename(objfile) in libname
|
||||
|
||||
def otool(objfile):
|
||||
command = "otool -L '%s' | grep -e '\t' | awk '{ print $1 }'" % objfile
|
||||
output = subprocess.check_output(command, shell = True, universal_newlines=True)
|
||||
return set(filter(partial(is_user_lib, objfile), output.split()))
|
||||
|
||||
def get_rpaths_dev_tools(binary):
|
||||
command = "otool -l '%s' | grep -A2 LC_RPATH | grep path | grep \"Xcode\|CommandLineTools\"" % binary
|
||||
result = subprocess.check_output(command, shell = True, universal_newlines=True)
|
||||
pathRe = re.compile("^\s*path (.*) \(offset \d*\)$")
|
||||
output = []
|
||||
|
||||
for line in result.splitlines():
|
||||
output.append(pathRe.search(line).group(1).strip())
|
||||
|
||||
return output
|
||||
command = "otool -L %s | grep -e '\t' | awk '{ print $1 }'" % objfile
|
||||
output = subprocess.check_output(command, shell = True)
|
||||
return filter(partial(is_user_lib, objfile), output.split())
|
||||
|
||||
def install_name_tool_change(old, new, objfile):
|
||||
subprocess.call(["install_name_tool", "-change", old, new, objfile])
|
||||
@@ -45,15 +33,9 @@ def install_name_tool_change(old, new, objfile):
|
||||
def install_name_tool_id(name, objfile):
|
||||
subprocess.call(["install_name_tool", "-id", name, objfile])
|
||||
|
||||
def install_name_tool_add_rpath(rpath, binary):
|
||||
subprocess.call(["install_name_tool", "-add_rpath", rpath, binary])
|
||||
|
||||
def install_name_tool_delete_rpath(rpath, binary):
|
||||
subprocess.call(["install_name_tool", "-delete_rpath", rpath, binary])
|
||||
|
||||
def libraries(objfile, result = dict()):
|
||||
libs_list = otool(objfile)
|
||||
result[objfile] = libs_list
|
||||
result[objfile] = set(libs_list)
|
||||
|
||||
for lib in libs_list:
|
||||
if lib not in result:
|
||||
@@ -61,19 +43,29 @@ def libraries(objfile, result = dict()):
|
||||
|
||||
return result
|
||||
|
||||
def leafs(libs_dict, processed = []):
|
||||
result = []
|
||||
processed = set(processed)
|
||||
|
||||
for objfile, libs in libs_dict.items():
|
||||
if libs <= processed:
|
||||
result.append(objfile)
|
||||
|
||||
return result
|
||||
|
||||
def lib_path(binary):
|
||||
return os.path.join(os.path.dirname(binary), 'lib')
|
||||
|
||||
def lib_name(lib):
|
||||
return os.path.join("@executable_path", "lib", os.path.basename(lib))
|
||||
|
||||
def process_libraries(libs_dict, binary):
|
||||
libs_set = set(libs_dict)
|
||||
# Remove binary from libs_set to prevent a duplicate of the binary being
|
||||
# added to the libs directory.
|
||||
libs_set.remove(binary)
|
||||
def process_libraries(libs_dict, binary, processed = []):
|
||||
ls = leafs(libs_dict, processed)
|
||||
diff = set(ls) - set(processed)
|
||||
if diff == set([binary]):
|
||||
return
|
||||
|
||||
for src in libs_set:
|
||||
for src in diff:
|
||||
name = lib_name(src)
|
||||
dst = os.path.join(lib_path(binary), os.path.basename(src))
|
||||
|
||||
@@ -84,47 +76,19 @@ def process_libraries(libs_dict, binary):
|
||||
if src in libs_dict[binary]:
|
||||
install_name_tool_change(src, name, binary)
|
||||
|
||||
for p in libs_set:
|
||||
for p in processed:
|
||||
if p in libs_dict[src]:
|
||||
install_name_tool_change(p, lib_name(p), dst)
|
||||
|
||||
def process_swift_libraries(binary):
|
||||
command = ['xcrun', '--find', 'swift-stdlib-tool']
|
||||
swiftStdlibTool = subprocess.check_output(command, universal_newlines=True).strip()
|
||||
# from xcode11 on the dynamic swift libs reside in a separate directory from
|
||||
# the std one, might need versioned paths for future swift versions
|
||||
swiftLibPath = os.path.join(swiftStdlibTool, '../../lib/swift-5.0/macosx')
|
||||
swiftLibPath = os.path.abspath(swiftLibPath)
|
||||
|
||||
command = [swiftStdlibTool, '--copy', '--platform', 'macosx', '--scan-executable', binary, '--destination', lib_path(binary)]
|
||||
|
||||
if os.path.exists(swiftLibPath):
|
||||
command.extend(['--source-libraries', swiftLibPath])
|
||||
|
||||
subprocess.check_output(command, universal_newlines=True)
|
||||
|
||||
print(">> setting additional rpath for swift libraries")
|
||||
install_name_tool_add_rpath("@executable_path/lib", binary)
|
||||
|
||||
def remove_dev_tools_rapths(binary):
|
||||
for path in get_rpaths_dev_tools(binary):
|
||||
install_name_tool_delete_rpath(path, binary)
|
||||
process_libraries(libs_dict, binary, ls)
|
||||
|
||||
def main():
|
||||
binary = os.path.abspath(sys.argv[1])
|
||||
if not os.path.exists(lib_path(binary)):
|
||||
os.makedirs(lib_path(binary))
|
||||
print(">> gathering all linked libraries")
|
||||
libs = libraries(binary)
|
||||
|
||||
print(">> copying and processing all linked libraries")
|
||||
print(libs)
|
||||
process_libraries(libs, binary)
|
||||
|
||||
print(">> removing rpath definitions towards dev tools")
|
||||
remove_dev_tools_rapths(binary)
|
||||
|
||||
print(">> copying and processing swift libraries")
|
||||
process_swift_libraries(binary)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Convert the contents of a file into a C string constant.
|
||||
# Note that the compiler will implicitly add an extra 0 byte at the end
|
||||
@@ -22,21 +22,26 @@
|
||||
# License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import sys
|
||||
|
||||
# Indexing a byte string yields int on Python 3.x, and a str on Python 2.x
|
||||
def pord(c):
|
||||
return ord(c) if type(c) == str else c
|
||||
|
||||
def file2string(infilename, infile, outfile):
|
||||
outfile.write("// Generated from %s\n\n" % infilename)
|
||||
|
||||
conv = ["\\%03o" % c for c in range(256)]
|
||||
conv = ['\\' + ("%03o" % c) for c in range(256)]
|
||||
safe_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" \
|
||||
"0123456789!#%&'()*+,-./:;<=>[]^_{|}~ "
|
||||
"0123456789!#%&'()*+,-./:;<=>?[]^_{|}~ "
|
||||
|
||||
for c in safe_chars:
|
||||
conv[ord(c)] = c
|
||||
for c, esc in ("\nn", "\tt", r"\\", '""'):
|
||||
conv[ord(c)] = '\\' + esc
|
||||
for line in infile:
|
||||
outfile.write('"' + ''.join(conv[c] for c in line) + '"\n')
|
||||
outfile.write('"' + ''.join(conv[pord(c)] for c in line) + '"\n')
|
||||
|
||||
if __name__ == "__main__":
|
||||
with open(sys.argv[1], 'rb') as infile:
|
||||
|
||||
+85
-293
@@ -1,322 +1,114 @@
|
||||
--[[
|
||||
This script uses the lavfi cropdetect filter to automatically
|
||||
insert a crop filter with appropriate parameters for the
|
||||
currently playing video.
|
||||
|
||||
It will automatically crop the video, when playback starts.
|
||||
|
||||
Also It registers the key-binding "C" (shift+c). You can manually
|
||||
crop the video by pressing the "C" (shift+c) key.
|
||||
|
||||
If the "C" key is pressed again, the crop filter is removed
|
||||
restoring playback to its original state.
|
||||
|
||||
The workflow is as follows: First, it inserts the filter
|
||||
vf=lavfi=cropdetect. After <detect_seconds> (default is 1)
|
||||
seconds, it then inserts the filter vf=crop=w:h:x:y, where
|
||||
w,h,x,y are determined from the vf-metadata gathered by
|
||||
cropdetect. The cropdetect filter is removed immediately after
|
||||
the crop filter is inserted as it is no longer needed.
|
||||
|
||||
Since the crop parameters are determined from the 1 second of
|
||||
video between inserting the cropdetect and crop filters, the "C"
|
||||
key should be pressed at a position in the video where the crop
|
||||
region is unambiguous (i.e., not a black frame, black background
|
||||
title card, or dark scene).
|
||||
|
||||
The default options can be overridden by adding
|
||||
script-opts-append=autocrop-<parameter>=<value> into mpv.conf
|
||||
|
||||
List of available parameters (For default values, see <options>):
|
||||
|
||||
auto: bool - Whether to automatically apply crop at the start of
|
||||
playback. If you don't want to crop automatically, set it to
|
||||
false or add "script-opts-append=autocrop-auto=no" into
|
||||
mpv.conf.
|
||||
|
||||
auto_delay: seconds - Delay before starting crop in auto mode.
|
||||
You can try to increase this value to avoid dark scene or
|
||||
fade in at beginning. Automatic cropping will not occur if
|
||||
the value is larger than the remaining playback time.
|
||||
|
||||
detect_limit: number[0-255] - Black threshold for cropdetect.
|
||||
Smaller values will generally result in less cropping.
|
||||
See limit of https://ffmpeg.org/ffmpeg-filters.html#cropdetect
|
||||
|
||||
detect_round: number[2^n] - The value which the width/height
|
||||
should be divisible by. Smaller values have better detection
|
||||
accuracy. If you have problems with other filters,
|
||||
you can try to set it to 4 or 16.
|
||||
See round of https://ffmpeg.org/ffmpeg-filters.html#cropdetect
|
||||
|
||||
detect_min_ratio: number[0.0-1.0] - The ratio of the minimum clip
|
||||
size to the original. If the picture is over cropped or under
|
||||
cropped, try adjusting this value.
|
||||
|
||||
detect_seconds: seconds - How long to gather cropdetect data.
|
||||
Increasing this may be desirable to allow cropdetect more
|
||||
time to collect data.
|
||||
--]]
|
||||
|
||||
-- This script uses the lavfi cropdetect filter to automatically
|
||||
-- insert a crop filter with appropriate parameters for the currently
|
||||
-- playing video.
|
||||
--
|
||||
-- It registers the key-binding "C" (shift+c), which when pressed,
|
||||
-- inserts the filter vf=lavfi=cropdetect. After 1 second, it then
|
||||
-- inserts the filter vf=crop=w:h:x:y, where w,h,x,y are determined
|
||||
-- from the vf-metadata gathered by cropdetect. The cropdetect filter
|
||||
-- is removed immediately after the crop filter is inserted as it is
|
||||
-- no longer needed.
|
||||
--
|
||||
-- If the "C" key is pressed again, the crop filter is removed
|
||||
-- restoring playback to its original state.
|
||||
--
|
||||
-- Since the crop parameters are determined from the 1 second of video
|
||||
-- between inserting the cropdetect and crop filters, the "C" key
|
||||
-- should be pressed at a position in the video where the crop region
|
||||
-- is unambiguous (i.e., not a black frame, black background title
|
||||
-- card, or dark scene).
|
||||
--
|
||||
-- The default delay between insertion of the cropdetect and
|
||||
-- crop filters may be overridden by adding
|
||||
--
|
||||
-- --script-opts=autocrop.detect_seconds=<number of seconds>
|
||||
--
|
||||
-- to mpv's arguments. This may be desirable to allow cropdetect more
|
||||
-- time to collect data.
|
||||
require "mp.msg"
|
||||
require 'mp.options'
|
||||
|
||||
local options = {
|
||||
auto = true,
|
||||
auto_delay = 4,
|
||||
detect_limit = "24/255",
|
||||
detect_round = 2,
|
||||
detect_min_ratio = 0.5,
|
||||
script_name = mp.get_script_name()
|
||||
cropdetect_label = string.format("%s-cropdetect", script_name)
|
||||
crop_label = string.format("%s-crop", script_name)
|
||||
|
||||
-- number of seconds to gather cropdetect data
|
||||
detect_seconds = tonumber(mp.get_opt(string.format("%s.detect_seconds", script_name)))
|
||||
if not detect_seconds then
|
||||
detect_seconds = 1
|
||||
}
|
||||
read_options(options)
|
||||
end
|
||||
|
||||
local label_prefix = mp.get_script_name()
|
||||
local labels = {
|
||||
crop = string.format("%s-crop", label_prefix),
|
||||
cropdetect = string.format("%s-cropdetect", label_prefix)
|
||||
}
|
||||
|
||||
timers = {
|
||||
auto_delay = nil,
|
||||
detect_crop = nil
|
||||
}
|
||||
|
||||
function is_filter_present(label)
|
||||
local filters = mp.get_property_native("vf")
|
||||
for index, filter in pairs(filters) do
|
||||
if filter["label"] == label then
|
||||
function del_filter_if_present(label)
|
||||
-- necessary because mp.command('vf del @label:filter') raises an
|
||||
-- error if the filter doesn't exist
|
||||
local vfs = mp.get_property_native("vf")
|
||||
for i,vf in pairs(vfs) do
|
||||
if vf["label"] == label then
|
||||
table.remove(vfs, i)
|
||||
mp.set_property_native("vf", vfs)
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function is_enough_time(seconds)
|
||||
|
||||
-- Plus 1 second for deviation.
|
||||
local time_needed = seconds + 1
|
||||
local playtime_remaining = mp.get_property_native("playtime-remaining")
|
||||
|
||||
return playtime_remaining and time_needed < playtime_remaining
|
||||
end
|
||||
|
||||
function is_cropable()
|
||||
local vid = mp.get_property_native("vid")
|
||||
local is_album = vid and mp.get_property_native(
|
||||
string.format("track-list/%d/albumart", vid)
|
||||
) or false
|
||||
|
||||
return vid and not is_album
|
||||
end
|
||||
|
||||
function remove_filter(label)
|
||||
if is_filter_present(label) then
|
||||
mp.command(string.format('vf remove @%s', label))
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function cleanup()
|
||||
|
||||
-- Remove all existing filters.
|
||||
for key, value in pairs(labels) do
|
||||
remove_filter(value)
|
||||
end
|
||||
|
||||
-- Kill all timers.
|
||||
for index, timer in pairs(timers) do
|
||||
if timer then
|
||||
timer:kill()
|
||||
timer = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function detect_crop()
|
||||
|
||||
-- If it's not cropable, exit.
|
||||
if not is_cropable() then
|
||||
mp.msg.warn("autocrop only works for videos.")
|
||||
function autocrop_start()
|
||||
-- exit if cropdetection is already in progress
|
||||
if timer then
|
||||
mp.msg.warn("already cropdetecting!")
|
||||
return
|
||||
end
|
||||
|
||||
-- Verify if there is enough time to detect crop.
|
||||
local time_needed = options.detect_seconds
|
||||
|
||||
if not is_enough_time(time_needed) then
|
||||
mp.msg.warn("Not enough time to detect crop.")
|
||||
-- if there's a crop filter, remove it and exit
|
||||
if del_filter_if_present(crop_label) then
|
||||
return
|
||||
end
|
||||
|
||||
-- Insert the cropdetect filter.
|
||||
local limit = options.detect_limit
|
||||
local round = options.detect_round
|
||||
|
||||
mp.command(
|
||||
-- insert the cropdetect filter
|
||||
ret=mp.command(
|
||||
string.format(
|
||||
'vf pre @%s:cropdetect=limit=%s:round=%d:reset=0',
|
||||
labels.cropdetect, limit, round
|
||||
'vf add @%s:cropdetect=limit=%f:round=2:reset=0',
|
||||
cropdetect_label, 24/255
|
||||
)
|
||||
)
|
||||
|
||||
-- Wait to gather data.
|
||||
timers.detect_crop = mp.add_timeout(time_needed, detect_end)
|
||||
-- wait to gather data
|
||||
timer=mp.add_timeout(detect_seconds, do_crop)
|
||||
end
|
||||
|
||||
function detect_end()
|
||||
|
||||
-- Get the metadata and remove the cropdetect filter.
|
||||
local cropdetect_metadata =
|
||||
mp.get_property_native(
|
||||
string.format("vf-metadata/%s",
|
||||
labels.cropdetect
|
||||
)
|
||||
function do_crop()
|
||||
-- get the metadata
|
||||
local cropdetect_metadata = mp.get_property_native(
|
||||
string.format("vf-metadata/%s", cropdetect_label)
|
||||
)
|
||||
remove_filter(labels.cropdetect)
|
||||
|
||||
-- Remove the timer of detect crop.
|
||||
if timers.detect_crop then
|
||||
timers.detect_crop:kill()
|
||||
timers.detect_crop = nil
|
||||
end
|
||||
|
||||
local meta = {}
|
||||
|
||||
-- Verify the existence of metadata.
|
||||
-- use it to crop if its valid
|
||||
if cropdetect_metadata then
|
||||
meta = {
|
||||
w = cropdetect_metadata["lavfi.cropdetect.w"],
|
||||
h = cropdetect_metadata["lavfi.cropdetect.h"],
|
||||
x = cropdetect_metadata["lavfi.cropdetect.x"],
|
||||
y = cropdetect_metadata["lavfi.cropdetect.y"],
|
||||
}
|
||||
else
|
||||
mp.msg.error("No crop data.")
|
||||
mp.msg.info("Was the cropdetect filter successfully inserted?")
|
||||
mp.msg.info("Does your version of ffmpeg/libav support AVFrame metadata?")
|
||||
return
|
||||
end
|
||||
|
||||
-- Verify that the metadata meets the requirements and convert it.
|
||||
if meta.w and meta.h and meta.x and meta.y then
|
||||
local width = mp.get_property_native("width")
|
||||
local height = mp.get_property_native("height")
|
||||
|
||||
meta = {
|
||||
w = tonumber(meta.w),
|
||||
h = tonumber(meta.h),
|
||||
x = tonumber(meta.x),
|
||||
y = tonumber(meta.y),
|
||||
min_w = width * options.detect_min_ratio,
|
||||
min_h = height * options.detect_min_ratio,
|
||||
max_w = width,
|
||||
max_h = height
|
||||
}
|
||||
else
|
||||
mp.msg.error("Got empty crop data.")
|
||||
mp.msg.info("You might need to increase detect_seconds.")
|
||||
return
|
||||
end
|
||||
|
||||
apply_crop(meta)
|
||||
end
|
||||
|
||||
function apply_crop(meta)
|
||||
|
||||
-- Verify if it is necessary to crop.
|
||||
local is_effective = meta.x > 0 or meta.y > 0
|
||||
or meta.w < meta.max_w or meta.h < meta.max_h
|
||||
|
||||
if not is_effective then
|
||||
mp.msg.info("No area detected for cropping.")
|
||||
return
|
||||
end
|
||||
|
||||
-- Verify it is not over cropped.
|
||||
local is_excessive = meta.w < meta.min_w and meta.h < meta.min_h
|
||||
|
||||
if is_excessive then
|
||||
mp.msg.info("The area to be cropped is too large.")
|
||||
mp.msg.info("You might need to decrease detect_min_ratio.")
|
||||
return
|
||||
end
|
||||
|
||||
-- Remove existing crop.
|
||||
remove_filter(labels.crop)
|
||||
|
||||
-- Apply crop.
|
||||
mp.command(
|
||||
string.format("vf pre @%s:lavfi-crop=w=%s:h=%s:x=%s:y=%s",
|
||||
labels.crop, meta.w, meta.h, meta.x, meta.y
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
function on_start()
|
||||
|
||||
-- Clean up at the beginning.
|
||||
cleanup()
|
||||
|
||||
-- If auto is not true, exit.
|
||||
if not options.auto then
|
||||
return
|
||||
end
|
||||
|
||||
-- If it is the beginning, wait for detect_crop
|
||||
-- after auto_delay seconds, otherwise immediately.
|
||||
local playback_time = mp.get_property_native("playback-time")
|
||||
local is_delay_needed = playback_time
|
||||
and options.auto_delay > playback_time
|
||||
|
||||
if is_delay_needed then
|
||||
|
||||
-- Verify if there is enough time for autocrop.
|
||||
local time_needed = options.auto_delay + options.detect_seconds
|
||||
|
||||
if not is_enough_time(time_needed) then
|
||||
mp.msg.warn("Not enough time for autocrop.")
|
||||
return
|
||||
if cropdetect_metadata["lavfi.cropdetect.w"]
|
||||
and cropdetect_metadata["lavfi.cropdetect.h"]
|
||||
and cropdetect_metadata["lavfi.cropdetect.x"]
|
||||
and cropdetect_metadata["lavfi.cropdetect.y"]
|
||||
then
|
||||
mp.command(string.format("vf add @%s:lavfi-crop=w=%s:h=%s:x=%s:y=%s",
|
||||
crop_label,
|
||||
cropdetect_metadata["lavfi.cropdetect.w"],
|
||||
cropdetect_metadata["lavfi.cropdetect.h"],
|
||||
cropdetect_metadata["lavfi.cropdetect.x"],
|
||||
cropdetect_metadata["lavfi.cropdetect.y"]))
|
||||
else
|
||||
mp.msg.error(
|
||||
"Got empty crop data. You might need to increase detect_seconds."
|
||||
)
|
||||
end
|
||||
|
||||
timers.auto_delay = mp.add_timeout(time_needed,
|
||||
function()
|
||||
detect_crop()
|
||||
|
||||
-- Remove the timer of auto delay.
|
||||
timers.auto_delay:kill()
|
||||
timers.auto_delay = nil
|
||||
end
|
||||
)
|
||||
else
|
||||
detect_crop()
|
||||
mp.msg.error(
|
||||
"No crop data. Was the cropdetect filter successfully inserted?"
|
||||
)
|
||||
mp.msg.error(
|
||||
"Does your version of ffmpeg/libav support AVFrame metadata?"
|
||||
)
|
||||
end
|
||||
-- remove the cropdetect filter
|
||||
del_filter_if_present(cropdetect_label)
|
||||
timer=nil
|
||||
end
|
||||
|
||||
function on_toggle()
|
||||
|
||||
-- If it is during auto_delay, kill the timer.
|
||||
if timers.auto_delay then
|
||||
timers.auto_delay:kill()
|
||||
timers.auto_delay = nil
|
||||
end
|
||||
|
||||
-- Cropped => Remove it.
|
||||
if remove_filter(labels.crop) then
|
||||
return
|
||||
end
|
||||
|
||||
-- Detecting => Leave it.
|
||||
if timers.detect_crop then
|
||||
mp.msg.warn("Already cropdetecting!")
|
||||
return
|
||||
end
|
||||
|
||||
-- Neither => Do delectcrop.
|
||||
detect_crop()
|
||||
end
|
||||
|
||||
mp.add_key_binding("C", "toggle_crop", on_toggle)
|
||||
mp.register_event("end-file", cleanup)
|
||||
mp.register_event("file-loaded", on_start)
|
||||
mp.add_key_binding("C", "auto_crop", autocrop_start)
|
||||
+15
-67
@@ -2,34 +2,15 @@
|
||||
-- the currently played file. It does so by scanning the directory a file is
|
||||
-- located in when starting playback. It sorts the directory entries
|
||||
-- alphabetically, and adds entries before and after the current file to
|
||||
-- the internal playlist. (It stops if it would add an already existing
|
||||
-- the internal playlist. (It stops if the it would add an already existing
|
||||
-- playlist entry at the same position - this makes it "stable".)
|
||||
-- Add at most 5000 * 2 files when starting a file (before + after).
|
||||
|
||||
--[[
|
||||
To configure this script use file autoload.conf in directory script-opts (the "script-opts"
|
||||
directory must be in the mpv configuration directory, typically ~/.config/mpv/).
|
||||
|
||||
Example configuration would be:
|
||||
|
||||
disabled=no
|
||||
images=no
|
||||
videos=yes
|
||||
audio=yes
|
||||
|
||||
--]]
|
||||
|
||||
MAXENTRIES = 5000
|
||||
|
||||
local msg = require 'mp.msg'
|
||||
local options = require 'mp.options'
|
||||
local utils = require 'mp.utils'
|
||||
|
||||
o = {
|
||||
disabled = false,
|
||||
images = true,
|
||||
videos = true,
|
||||
audio = true
|
||||
disabled = false
|
||||
}
|
||||
options.read_options(o)
|
||||
|
||||
@@ -39,29 +20,12 @@ function Set (t)
|
||||
return set
|
||||
end
|
||||
|
||||
function SetUnion (a,b)
|
||||
local res = {}
|
||||
for k in pairs(a) do res[k] = true end
|
||||
for k in pairs(b) do res[k] = true end
|
||||
return res
|
||||
end
|
||||
|
||||
EXTENSIONS_VIDEO = Set {
|
||||
'mkv', 'avi', 'mp4', 'ogv', 'webm', 'rmvb', 'flv', 'wmv', 'mpeg', 'mpg', 'm4v', '3gp'
|
||||
EXTENSIONS = Set {
|
||||
'mkv', 'avi', 'mp4', 'ogv', 'webm', 'rmvb', 'flv', 'wmv', 'mpeg', 'mpg', 'm4v', '3gp',
|
||||
'mp3', 'wav', 'ogm', 'flac', 'm4a', 'wma', 'ogg', 'opus',
|
||||
}
|
||||
|
||||
EXTENSIONS_AUDIO = Set {
|
||||
'mp3', 'wav', 'ogm', 'flac', 'm4a', 'wma', 'ogg', 'opus'
|
||||
}
|
||||
|
||||
EXTENSIONS_IMAGES = Set {
|
||||
'jpg', 'jpeg', 'png', 'tif', 'tiff', 'gif', 'webp', 'svg', 'bmp'
|
||||
}
|
||||
|
||||
EXTENSIONS = Set {}
|
||||
if o.videos then EXTENSIONS = SetUnion(EXTENSIONS, EXTENSIONS_VIDEO) end
|
||||
if o.audio then EXTENSIONS = SetUnion(EXTENSIONS, EXTENSIONS_AUDIO) end
|
||||
if o.images then EXTENSIONS = SetUnion(EXTENSIONS, EXTENSIONS_IMAGES) end
|
||||
mputils = require 'mp.utils'
|
||||
|
||||
function add_files_at(index, files)
|
||||
index = index - 1
|
||||
@@ -120,38 +84,22 @@ function alnumcomp(x, y)
|
||||
return #xt < #yt
|
||||
end
|
||||
|
||||
local autoloaded = nil
|
||||
|
||||
function find_and_add_entries()
|
||||
local path = mp.get_property("path", "")
|
||||
local dir, filename = utils.split_path(path)
|
||||
msg.trace(("dir: %s, filename: %s"):format(dir, filename))
|
||||
if o.disabled then
|
||||
msg.verbose("stopping: autoload disabled")
|
||||
return
|
||||
elseif #dir == 0 then
|
||||
msg.verbose("stopping: not a local path")
|
||||
local dir, filename = mputils.split_path(path)
|
||||
if o.disabled or #dir == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local pl_count = mp.get_property_number("playlist-count", 1)
|
||||
-- check if this is a manually made playlist
|
||||
if (pl_count > 1 and autoloaded == nil) or
|
||||
if (pl_count > 1 and autoload == nil) or
|
||||
(pl_count == 1 and EXTENSIONS[string.lower(get_extension(filename))] == nil) then
|
||||
msg.verbose("stopping: manually made playlist")
|
||||
return
|
||||
else
|
||||
autoloaded = true
|
||||
autoload = true
|
||||
end
|
||||
|
||||
local pl = mp.get_property_native("playlist", {})
|
||||
local pl_current = mp.get_property_number("playlist-pos-1", 1)
|
||||
msg.trace(("playlist-pos-1: %s, playlist: %s"):format(pl_current,
|
||||
utils.to_string(pl)))
|
||||
|
||||
local files = utils.readdir(dir, "files")
|
||||
local files = mputils.readdir(dir, "files")
|
||||
if files == nil then
|
||||
msg.verbose("no other files in directory")
|
||||
return
|
||||
end
|
||||
table.filter(files, function (v, k)
|
||||
@@ -170,6 +118,8 @@ function find_and_add_entries()
|
||||
dir = ""
|
||||
end
|
||||
|
||||
local pl = mp.get_property_native("playlist", {})
|
||||
local pl_current = mp.get_property_number("playlist-pos", 0) + 1
|
||||
-- Find the current pl entry (dir+"/"+filename) in the sorted dir list
|
||||
local current
|
||||
for i = 1, #files do
|
||||
@@ -181,7 +131,6 @@ function find_and_add_entries()
|
||||
if current == nil then
|
||||
return
|
||||
end
|
||||
msg.trace("current file position in files: "..current)
|
||||
|
||||
local append = {[-1] = {}, [1] = {}}
|
||||
for direction = -1, 1, 2 do -- 2 iterations, with direction = -1 and +1
|
||||
@@ -195,7 +144,6 @@ function find_and_add_entries()
|
||||
local filepath = dir .. file
|
||||
if pl_e then
|
||||
-- If there's a playlist entry, and it's the same file, stop.
|
||||
msg.trace(pl_e.filename.." == "..filepath.." ?")
|
||||
if pl_e.filename == filepath then
|
||||
break
|
||||
end
|
||||
@@ -203,11 +151,11 @@ function find_and_add_entries()
|
||||
|
||||
if direction == -1 then
|
||||
if pl_current == 1 then -- never add additional entries in the middle
|
||||
msg.info("Prepending " .. file)
|
||||
mp.msg.info("Prepending " .. file)
|
||||
table.insert(append[-1], 1, filepath)
|
||||
end
|
||||
else
|
||||
msg.info("Adding " .. file)
|
||||
mp.msg.info("Adding " .. file)
|
||||
table.insert(append[1], filepath)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
-- Test script for some command API details.
|
||||
|
||||
local utils = require("mp.utils")
|
||||
|
||||
function join(sep, arr, count)
|
||||
local r = ""
|
||||
if count == nil then
|
||||
count = #arr
|
||||
end
|
||||
for i = 1, count do
|
||||
if i > 1 then
|
||||
r = r .. sep
|
||||
end
|
||||
r = r .. utils.to_string(arr[i])
|
||||
end
|
||||
return r
|
||||
end
|
||||
|
||||
mp.observe_property("vo-configured", "bool", function(_, v)
|
||||
if v ~= true then
|
||||
return
|
||||
end
|
||||
|
||||
print("async expand-text")
|
||||
mp.command_native_async({"expand-text", "hello ${path}!"},
|
||||
function(res, val, err)
|
||||
print("done async expand-text: " .. join(" ", {res, val, err}))
|
||||
end)
|
||||
|
||||
-- make screenshot writing very slow
|
||||
mp.set_property("screenshot-format", "png")
|
||||
mp.set_property("screenshot-png-compression", "9")
|
||||
|
||||
timer = mp.add_periodic_timer(0.1, function() print("I'm alive") end)
|
||||
timer:resume()
|
||||
|
||||
print("Slow screenshot command...")
|
||||
res, err = mp.command_native({"screenshot"})
|
||||
print("done, res: " .. utils.to_string(res))
|
||||
|
||||
print("Slow screenshot async command...")
|
||||
res, err = mp.command_native_async({"screenshot"}, function(res)
|
||||
print("done (async), res: " .. utils.to_string(res))
|
||||
timer:kill()
|
||||
end)
|
||||
print("done (sending), res: " .. utils.to_string(res))
|
||||
|
||||
print("Broken screenshot async command...")
|
||||
mp.command_native_async({"screenshot-to-file", "/nonexistent/bogus.png"},
|
||||
function(res, val, err)
|
||||
print("done err scr.: " .. join(" ", {res, val, err}))
|
||||
end)
|
||||
|
||||
mp.command_native_async({name = "subprocess", args = {"sh", "-c", "echo hi && sleep 10s"}, capture_stdout = true},
|
||||
function(res, val, err)
|
||||
print("done subprocess: " .. join(" ", {res, val, err}))
|
||||
end)
|
||||
|
||||
local x = mp.command_native_async({name = "subprocess", args = {"sleep", "inf"}},
|
||||
function(res, val, err)
|
||||
print("done sleep inf subprocess: " .. join(" ", {res, val, err}))
|
||||
end)
|
||||
mp.add_timeout(15, function()
|
||||
print("aborting sleep inf subprocess after timeout")
|
||||
mp.abort_async_command(x)
|
||||
end)
|
||||
|
||||
-- (assuming this "freezes")
|
||||
local y = mp.command_native_async({name = "sub-add", url = "-"},
|
||||
function(res, val, err)
|
||||
print("done sub-add stdin: " .. join(" ", {res, val, err}))
|
||||
end)
|
||||
mp.add_timeout(20, function()
|
||||
print("aborting sub-add stdin after timeout")
|
||||
mp.abort_async_command(y)
|
||||
end)
|
||||
|
||||
|
||||
mp.command_native_async({name = "subprocess", args = {"wc", "-c"},
|
||||
stdin_data = "hello", capture_stdout = true},
|
||||
function(res, val, err)
|
||||
print("Should be '5': " .. val.stdout)
|
||||
end)
|
||||
-- blocking stdin by default
|
||||
mp.command_native_async({name = "subprocess", args = {"cat"},
|
||||
capture_stdout = true},
|
||||
function(res, val, err)
|
||||
print("Should be 0: " .. #val.stdout)
|
||||
end)
|
||||
-- stdin + detached
|
||||
mp.command_native_async({name = "subprocess",
|
||||
args = {"bash", "-c", "(sleep 5s ; cat)"},
|
||||
stdin_data = "this should appear after 5s.\n",
|
||||
detach = true},
|
||||
function(res, val, err)
|
||||
print("5s test: " .. val.status)
|
||||
end)
|
||||
|
||||
-- This should get killed on script exit.
|
||||
mp.command_native_async({name = "subprocess", playback_only = false,
|
||||
args = {"sleep", "inf"}}, function()end)
|
||||
|
||||
-- Runs detached; should be killed on player exit (forces timeout)
|
||||
mp.command_native({_flags={"async"}, name = "subprocess",
|
||||
playback_only = false, args = {"sleep", "inf"}})
|
||||
end)
|
||||
|
||||
function freeze_test(playback_only)
|
||||
-- This "freezes" the script, should be killed via timeout.
|
||||
counter = counter and counter + 1 or 0
|
||||
print("freeze! " .. counter)
|
||||
local x = mp.command_native({name = "subprocess",
|
||||
playback_only = playback_only,
|
||||
args = {"sleep", "inf"}})
|
||||
print("done, killed=" .. utils.to_string(x.killed_by_us))
|
||||
end
|
||||
|
||||
mp.register_event("shutdown", function()
|
||||
freeze_test(false)
|
||||
end)
|
||||
|
||||
mp.register_event("idle", function()
|
||||
freeze_test(true)
|
||||
end)
|
||||
@@ -1,37 +0,0 @@
|
||||
-- Test a float property which internally uses NaN.
|
||||
-- Run with --no-config (or just scale-param1 not set).
|
||||
|
||||
local utils = require 'mp.utils'
|
||||
|
||||
prop_name = "scale-param1"
|
||||
|
||||
-- internal NaN, return string "default" instead of NaN
|
||||
v = mp.get_property_native(prop_name, "fail")
|
||||
print("Exp:", "string", "\"default\"")
|
||||
print("Got:", type(v), utils.to_string(v))
|
||||
|
||||
v = mp.get_property(prop_name)
|
||||
print("Exp:", "default")
|
||||
print("Got:", v)
|
||||
|
||||
-- not representable -> return provided fallback value
|
||||
v = mp.get_property_number(prop_name, -100)
|
||||
print("Exp:", -100)
|
||||
print("Got:", v)
|
||||
|
||||
mp.set_property_native(prop_name, 123)
|
||||
v = mp.get_property_number(prop_name, -100)
|
||||
print("Exp:", "number", 123)
|
||||
print("Got:", type(v), utils.to_string(v))
|
||||
|
||||
-- try to set an actual NaN
|
||||
st, msg = mp.set_property_number(prop_name, 0.0/0)
|
||||
print("Exp:", nil, "<message>")
|
||||
print("Got:", st, msg)
|
||||
|
||||
-- set default
|
||||
mp.set_property(prop_name, "default")
|
||||
|
||||
v = mp.get_property(prop_name)
|
||||
print("Exp:", "default")
|
||||
print("Got:", v)
|
||||
@@ -1,35 +0,0 @@
|
||||
local assdraw = require 'mp.assdraw'
|
||||
local utils = require 'mp.utils'
|
||||
|
||||
things = {}
|
||||
for i = 1, 2 do
|
||||
things[i] = {
|
||||
osd1 = mp.create_osd_overlay("ass-events"),
|
||||
osd2 = mp.create_osd_overlay("ass-events")
|
||||
}
|
||||
end
|
||||
things[1].text = "{\\an5}hello\\Nworld"
|
||||
things[2].text = "{\\pos(400, 200)}something something"
|
||||
|
||||
mp.add_periodic_timer(2, function()
|
||||
for i, thing in ipairs(things) do
|
||||
thing.osd1.data = thing.text
|
||||
thing.osd1.compute_bounds = true
|
||||
--thing.osd1.hidden = true
|
||||
local res = thing.osd1:update()
|
||||
print("res " .. i .. ": " .. utils.to_string(res))
|
||||
|
||||
thing.osd2.hidden = true
|
||||
if res ~= nil and res.x0 ~= nil then
|
||||
local draw = assdraw.ass_new()
|
||||
draw:append("{\\alpha&H80}")
|
||||
draw:draw_start()
|
||||
draw:pos(0, 0)
|
||||
draw:rect_cw(res.x0, res.y0, res.x1, res.y1)
|
||||
draw:draw_stop()
|
||||
thing.osd2.hidden = false
|
||||
thing.osd2.data = draw.text
|
||||
end
|
||||
thing.osd2:update()
|
||||
end
|
||||
end)
|
||||
@@ -1,265 +0,0 @@
|
||||
--[[
|
||||
|
||||
Automatically skip in files if video frames with pre-supplied fingerprints are
|
||||
detected. This will skip ahead by a pre-configured amount of time if a matching
|
||||
video frame is detected.
|
||||
|
||||
This requires the vf_fingerprint video filter to be compiled in. Read the
|
||||
documentation of this filter for caveats (which will automatically apply to
|
||||
this script as well), such as no support for zero-copy hardware decoding.
|
||||
|
||||
You need to manually gather and provide fingerprints for video frames and add
|
||||
them to a configuration file in script-opts/skip-logo.conf (the "script-opts"
|
||||
directory must be in the mpv configuration directory, typically ~/.config/mpv/).
|
||||
|
||||
Example script-opts/skip-logo.conf:
|
||||
|
||||
|
||||
cases = {
|
||||
{
|
||||
-- Skip ahead 10 seconds if a black frame was detected
|
||||
-- Note: this is dangerous non-sense. It's just for demonstration.
|
||||
name = "black frame", -- print if matched
|
||||
skip = 10, -- number of seconds to skip forward
|
||||
score = 0.3, -- required score
|
||||
fingerprint = "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||
},
|
||||
{
|
||||
-- Skip ahead 20 seconds if a white frame was detected
|
||||
-- Note: this is dangerous non-sense. It's just for demonstration.
|
||||
name = "fun2",
|
||||
skip = 20,
|
||||
fingerprint = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
},
|
||||
}
|
||||
|
||||
This is actually a lua file. Lua was chosen because it seemed less of a pain to
|
||||
parse. Future versions of this script may change the format.
|
||||
|
||||
The fingerprint is a video frame, converted to "gray" (8 bit per pixels), full
|
||||
range, each pixel concatenated into an array, converted to a hex string. You
|
||||
can produce these fingerprints by running this manually:
|
||||
|
||||
mpv --vf=fingerprint:print yourfile.mkv
|
||||
|
||||
This will log the fingerprint of each video frame to the console, along with its
|
||||
timestamp. You find the fingerprint of a unique-enough looking frame, and add
|
||||
it as entry to skip-logo.conf.
|
||||
|
||||
You can provide a score for "fuzziness". If no score is provided, a default
|
||||
value of 0.3 is used. The score is inverse: 0 means exactly the same, while a
|
||||
higher score means a higher difference. Currently, the score is computed as
|
||||
euclidean distance between the video frame and the pre-provided fingerprint,
|
||||
thus the highest score is 16. You probably want a score lower than 1 at least.
|
||||
(This algorithm is very primitive, but also simple and fast to compute.)
|
||||
|
||||
There's always the danger of false positives, which might be quite annoying.
|
||||
It's up to you what you hate more, the logo, or random skips if false positives
|
||||
are detected. Also, it's always active, and might eat too much CPU with files
|
||||
that have a high resolution or framerate. To temporarily disable the script,
|
||||
having a keybind like this in your input.conf will be helpful:
|
||||
|
||||
ctrl+k vf toggle @skip-logo
|
||||
|
||||
This will disable/enable the fingerprint filter, which the script automatically
|
||||
adds at start.
|
||||
|
||||
Another important caveat is that the script currently disables matching during
|
||||
seeking or playback initialization, which means it cannot match the first few
|
||||
frames of a video. This could be fixed, but the author was too lazy to do so.
|
||||
|
||||
--]]
|
||||
|
||||
local utils = require "mp.utils"
|
||||
local msg = require "mp.msg"
|
||||
|
||||
local label = "skip-logo"
|
||||
local meta_property = string.format("vf-metadata/%s", label)
|
||||
|
||||
local config = {}
|
||||
local cases = {}
|
||||
local cur_bmp
|
||||
local seeking = false
|
||||
local playback_start_pts = nil
|
||||
|
||||
-- Convert a hex string to an array. Convert each byte to a [0,1] float by
|
||||
-- interpreting it as normalized uint8_t.
|
||||
-- The data parameter, if not nil, may be used as storage (avoiding garbage).
|
||||
local function hex_to_norm8(hex, data)
|
||||
local size = math.floor(#hex / 2)
|
||||
if #hex ~= size * 2 then
|
||||
return nil
|
||||
end
|
||||
local res
|
||||
if (data ~= nil) and (#data == size) then
|
||||
res = data
|
||||
else
|
||||
res = {}
|
||||
end
|
||||
for i = 1, size do
|
||||
local num = tonumber(hex:sub(i * 2, i * 2 + 1), 16)
|
||||
if num == nil then
|
||||
return nil
|
||||
end
|
||||
res[i] = num / 255.0
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
local function compare_bmp(a, b)
|
||||
if #a ~= #b then
|
||||
return nil -- can't compare
|
||||
end
|
||||
local sum = 0
|
||||
for i = 1, #a do
|
||||
local diff = a[i] - b[i]
|
||||
sum = sum + diff * diff
|
||||
end
|
||||
return math.sqrt(sum)
|
||||
end
|
||||
|
||||
local function load_config()
|
||||
local conf_file = mp.find_config_file("script-opts/skip-logo.conf")
|
||||
local conf_fn
|
||||
local err = nil
|
||||
if conf_file then
|
||||
if setfenv then
|
||||
conf_fn, err = loadfile(conf_file)
|
||||
if conf_fn then
|
||||
setfenv(conf_fn, config)
|
||||
end
|
||||
else
|
||||
conf_fn, err = loadfile(conf_file, "t", config)
|
||||
end
|
||||
else
|
||||
err = "config file not found"
|
||||
end
|
||||
|
||||
if conf_fn and (not err) then
|
||||
local ok, err2 = pcall(conf_fn)
|
||||
err = err2
|
||||
end
|
||||
|
||||
if err then
|
||||
msg.error("Failed to load config file:", err)
|
||||
end
|
||||
|
||||
if config.cases then
|
||||
for n, case in ipairs(config.cases) do
|
||||
local err = nil
|
||||
case.bitmap = hex_to_norm8(case.fingerprint)
|
||||
if case.bitmap == nil then
|
||||
err = "invalid or missing fingerprint field"
|
||||
end
|
||||
if case.score == nil then
|
||||
case.score = 0.3
|
||||
end
|
||||
if type(case.score) ~= "number" then
|
||||
err = "score field is not a number"
|
||||
end
|
||||
if type(case.skip) ~= "number" then
|
||||
err = "skip field is not a number or missing"
|
||||
end
|
||||
if case.name == nil then
|
||||
case.name = ("Entry %d"):format(n)
|
||||
end
|
||||
if err == nil then
|
||||
cases[#cases + 1] = case
|
||||
else
|
||||
msg.error(("Entry %s: %s, ignoring."):format(case.name, err))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
load_config()
|
||||
|
||||
-- Returns true on match and if something was done.
|
||||
local function check_fingerprint(hex, pts)
|
||||
local bmp = hex_to_norm8(hex, cur_bmp)
|
||||
cur_bmp = bmp
|
||||
|
||||
-- If parsing the filter's result failed (well, it shouldn't).
|
||||
assert(bmp ~= nil, "filter returned nonsense")
|
||||
|
||||
for _, case in ipairs(cases) do
|
||||
local score = compare_bmp(case.bitmap, bmp)
|
||||
if (score ~= nil) and (score <= case.score) then
|
||||
msg.warn(("Matching %s: score=%f (required: %f) at %s, skipping %f seconds"):
|
||||
format(case.name, score, case.score, mp.format_time(pts), case.skip))
|
||||
mp.commandv("seek", pts + case.skip, "absolute+exact")
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
local function read_frames()
|
||||
local result = mp.get_property_native(meta_property)
|
||||
if result == nil then
|
||||
return
|
||||
end
|
||||
|
||||
-- Try to get all entries. Out of laziness, assume that there are at most
|
||||
-- 100 entries. (In fact, vf_fingerprint limits it to 10.)
|
||||
for i = 0, 99 do
|
||||
local prefix = string.format("fp%d.", i)
|
||||
local hex = result[prefix .. "hex"]
|
||||
|
||||
local pts = tonumber(result[prefix .. "pts"])
|
||||
if (hex == nil) or (pts == nil) then
|
||||
break
|
||||
end
|
||||
|
||||
local skip = false -- blame Lua for not having "continue" or "goto", not me
|
||||
|
||||
-- If seeking just stopped, there will be frames before the seek target,
|
||||
-- ignore them by checking the timestamps.
|
||||
if playback_start_pts ~= nil then
|
||||
if pts >= playback_start_pts then
|
||||
playback_start_pts = nil -- just for robustness
|
||||
else
|
||||
skip = true
|
||||
end
|
||||
end
|
||||
|
||||
if not skip then
|
||||
if check_fingerprint(hex, pts) then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
mp.observe_property(meta_property, "none", function()
|
||||
-- Ignore frames that are decoded/filtered during seeking.
|
||||
if seeking then
|
||||
return
|
||||
end
|
||||
|
||||
read_frames()
|
||||
end)
|
||||
|
||||
mp.observe_property("seeking", "bool", function(name, val)
|
||||
seeking = val
|
||||
if seeking == false then
|
||||
playback_start_pts = mp.get_property_number("playback-time")
|
||||
read_frames()
|
||||
end
|
||||
end)
|
||||
|
||||
local filters = mp.get_property_native("option-info/vf/choices", {})
|
||||
local found = false
|
||||
for _, f in ipairs(filters) do
|
||||
if f == "fingerprint" then
|
||||
found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if found then
|
||||
mp.command(("no-osd vf add @%s:fingerprint"):format(label, filter))
|
||||
else
|
||||
msg.warn("vf_fingerprint not found")
|
||||
end
|
||||
@@ -1,32 +0,0 @@
|
||||
local utils = require("mp.utils")
|
||||
|
||||
function hardsleep()
|
||||
os.execute("sleep 1s")
|
||||
end
|
||||
|
||||
local hooks = {"on_before_start_file", "on_load", "on_load_fail",
|
||||
"on_preloaded", "on_unload", "on_after_end_file"}
|
||||
|
||||
for _, name in ipairs(hooks) do
|
||||
mp.add_hook(name, 0, function()
|
||||
print("--- hook: " .. name)
|
||||
hardsleep()
|
||||
print(" ... continue")
|
||||
end)
|
||||
end
|
||||
|
||||
local events = {"start-file", "end-file", "file-loaded", "seek",
|
||||
"playback-restart", "idle", "shutdown"}
|
||||
for _, name in ipairs(events) do
|
||||
mp.register_event(name, function()
|
||||
print("--- event: " .. name)
|
||||
end)
|
||||
end
|
||||
|
||||
local props = {"path", "metadata"}
|
||||
for _, name in ipairs(props) do
|
||||
mp.observe_property(name, "native", function(name, val)
|
||||
print("property '" .. name .. "' changed to '" ..
|
||||
utils.to_string(val) .. "'")
|
||||
end)
|
||||
end
|
||||
+2
-4
@@ -213,7 +213,6 @@ elements_matroska = (
|
||||
'Tag*, 7373, sub', (
|
||||
'Targets, 63c0, sub', (
|
||||
'TargetTypeValue, 68ca, uint',
|
||||
'TargetType, 63ca, str',
|
||||
'TargetTrackUID, 63c5, uint',
|
||||
'TargetEditionUID, 63c9, uint',
|
||||
'TargetChapterUID, 63c4, uint',
|
||||
@@ -222,8 +221,7 @@ elements_matroska = (
|
||||
'SimpleTag*, 67c8, sub', (
|
||||
'TagName, 45a3, str',
|
||||
'TagLanguage, 447a, str',
|
||||
'TagString, 4487, str',
|
||||
'TagDefault, 4484, uint',
|
||||
'TagString, 4487, str'
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -276,7 +274,7 @@ class MatroskaElement(object):
|
||||
|
||||
def add_subelements(self, subelements):
|
||||
self.subelements = subelements
|
||||
self.subids = {x[0].elid for x in subelements}
|
||||
self.subids = set(x[0].elid for x in subelements)
|
||||
|
||||
elementd = {}
|
||||
elementlist = []
|
||||
|
||||
@@ -22,7 +22,7 @@ OS2Version: 3
|
||||
OS2_WeightWidthSlopeOnly: 0
|
||||
OS2_UseTypoMetrics: 0
|
||||
CreationTime: 1408646554
|
||||
ModificationTime: 1576096543
|
||||
ModificationTime: 1492879730
|
||||
PfmFamily: 81
|
||||
TTFWeight: 400
|
||||
TTFWidth: 5
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
StartChar: uniE112
|
||||
Encoding: 57618 57618 30
|
||||
Width: 768
|
||||
VWidth: 1176
|
||||
Flags: HW
|
||||
LayerCount: 2
|
||||
Fore
|
||||
SplineSet
|
||||
512 40 m 1
|
||||
0 40 l 1
|
||||
0 168 l 1
|
||||
512 168 l 1
|
||||
512 40 l 1
|
||||
EndSplineSet
|
||||
EndChar
|
||||
@@ -1,20 +0,0 @@
|
||||
StartChar: uniE113
|
||||
Encoding: 57619 57619 31
|
||||
Width: 622
|
||||
VWidth: 1178
|
||||
Flags: HW
|
||||
LayerCount: 2
|
||||
Fore
|
||||
SplineSet
|
||||
768 42 m 5
|
||||
0 42 l 5
|
||||
0 746 l 5
|
||||
768 746 l 5
|
||||
768 42 l 5
|
||||
704 106 m 5
|
||||
704 618 l 5
|
||||
64 618 l 5
|
||||
64 106 l 5
|
||||
704 106 l 5
|
||||
EndSplineSet
|
||||
EndChar
|
||||
@@ -1,31 +0,0 @@
|
||||
StartChar: uniE114
|
||||
Encoding: 57620 57620 32
|
||||
Width: 896
|
||||
VWidth: 1178
|
||||
Flags: HW
|
||||
LayerCount: 2
|
||||
Fore
|
||||
SplineSet
|
||||
768 298 m 1
|
||||
576 298 l 1
|
||||
576 42 l 1
|
||||
0 42 l 1
|
||||
0 490 l 1
|
||||
192 490 l 1
|
||||
192 746 l 1
|
||||
768 746 l 1
|
||||
768 298 l 1
|
||||
704 362 m 1
|
||||
704 618 l 1
|
||||
256 618 l 1
|
||||
256 490 l 1
|
||||
576 490 l 1
|
||||
576 362 l 1
|
||||
704 362 l 1
|
||||
512 106 m 1
|
||||
512 362 l 1
|
||||
64 362 l 1
|
||||
64 106 l 1
|
||||
512 106 l 1
|
||||
EndSplineSet
|
||||
EndChar
|
||||
@@ -1,27 +0,0 @@
|
||||
StartChar: uniE115
|
||||
Encoding: 57621 57621 33
|
||||
Width: 844
|
||||
VWidth: 1112
|
||||
Flags: HW
|
||||
LayerCount: 2
|
||||
Fore
|
||||
SplineSet
|
||||
671 40 m 1
|
||||
575 40 l 1
|
||||
335 277 l 1
|
||||
98 40 l 1
|
||||
-1 40 l 1
|
||||
-1 136 l 1
|
||||
236 376 l 1
|
||||
-1 613 l 1
|
||||
-1 712 l 1
|
||||
98 712 l 1
|
||||
335 475 l 1
|
||||
575 712 l 1
|
||||
671 712 l 1
|
||||
671 613 l 1
|
||||
434 376 l 1
|
||||
671 139 l 1
|
||||
671 40 l 1
|
||||
EndSplineSet
|
||||
EndChar
|
||||
+4
-4
@@ -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,7 +37,7 @@ def copy_binary(binary_name):
|
||||
|
||||
def apply_plist_template(plist_file, version):
|
||||
for line in fileinput.input(plist_file, inplace=1):
|
||||
print(line.rstrip().replace('${VERSION}', version))
|
||||
print (line.rstrip().replace('${VERSION}', version))
|
||||
|
||||
def create_bundle_symlink(binary_name, symlink_name):
|
||||
os.symlink(os.path.basename(binary_name),
|
||||
@@ -80,7 +80,7 @@ def main():
|
||||
|
||||
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.")
|
||||
|
||||
|
||||
@@ -217,7 +217,6 @@
|
||||
<string>ftp</string>
|
||||
<string>udp</string>
|
||||
<string>smb</string>
|
||||
<string>srt</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
player-operation-mode=pseudo-gui
|
||||
log-file=~/Library/Logs/mpv.log
|
||||
+2
-2
@@ -147,8 +147,8 @@ if hasval:
|
||||
ax[1] = win.addPlot()
|
||||
ax[1].setXLink(ax[0])
|
||||
for cur in ax:
|
||||
if cur is not None:
|
||||
cur.addLegend(offset = (-1, 1))
|
||||
if cur is not None:
|
||||
cur.addLegend(offset = (-1, 1))
|
||||
for e in G.sevents:
|
||||
cur = ax[1 if e.type == "value" else 0]
|
||||
if not cur in G.curveno:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ "x$TARGET" != "xx86_64-w64-mingw32" ]; then
|
||||
if [ "x$LIBAV" != "xffmpeg-git" ] || [ "x$TRAVIS_OS_NAME" != "xlinux" ]; then
|
||||
# trigger build only on one of the matrix nodes
|
||||
exit;
|
||||
fi
|
||||
|
||||
+24
-15
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
This script emulates "unique application" functionality on Linux. When starting
|
||||
@@ -19,6 +19,10 @@ will typically just fill ~/.xsession-errors with garbage.
|
||||
mpv will terminate if there are no more files to play, and running the umpv
|
||||
script after that will start a new mpv instance.
|
||||
|
||||
Note that you can control the mpv instance by writing to the command fifo:
|
||||
|
||||
echo "cycle fullscreen" > ~/.umpv_fifo
|
||||
|
||||
Note: you can supply custom mpv path and options with the MPV environment
|
||||
variable. The environment variable will be split on whitespace, and the
|
||||
first item is used as path to mpv binary and the rest is passed as options
|
||||
@@ -28,7 +32,6 @@ Note: you can supply custom mpv path and options with the MPV environment
|
||||
|
||||
import sys
|
||||
import os
|
||||
import socket
|
||||
import errno
|
||||
import subprocess
|
||||
import fcntl
|
||||
@@ -54,34 +57,40 @@ def make_abs(filename):
|
||||
return filename
|
||||
files = [make_abs(f) for f in files]
|
||||
|
||||
SOCK = os.path.join(os.getenv("HOME"), ".umpv_socket")
|
||||
FIFO = os.path.join(os.getenv("HOME"), ".umpv_fifo")
|
||||
|
||||
sock = None
|
||||
fifo_fd = -1
|
||||
try:
|
||||
sock = socket.socket(socket.AF_UNIX)
|
||||
sock.connect(SOCK)
|
||||
except socket.error as e:
|
||||
if e.errno == errno.ECONNREFUSED:
|
||||
sock = None
|
||||
pass # abandoned socket
|
||||
fifo_fd = os.open(FIFO, os.O_NONBLOCK | os.O_WRONLY)
|
||||
except OSError as e:
|
||||
if e.errno == errno.ENXIO:
|
||||
pass # pipe has no writer
|
||||
elif e.errno == errno.ENOENT:
|
||||
sock = None
|
||||
pass # doesn't exist
|
||||
else:
|
||||
raise e
|
||||
|
||||
if sock:
|
||||
if fifo_fd >= 0:
|
||||
# Unhandled race condition: what if mpv is terminating right now?
|
||||
fcntl.fcntl(fifo_fd, fcntl.F_SETFL, 0) # set blocking mode
|
||||
fifo = os.fdopen(fifo_fd, "w")
|
||||
for f in files:
|
||||
# escape: \ \n "
|
||||
f = f.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n")
|
||||
f = "\"" + f + "\""
|
||||
sock.send(("raw loadfile " + f + " append\n").encode("utf-8"))
|
||||
fifo.write("raw loadfile " + f + " append\n")
|
||||
else:
|
||||
# Let mpv recreate socket if it doesn't already exist.
|
||||
# Recreate pipe if it doesn't already exist.
|
||||
# Also makes sure it's safe, and no other user can create a bogus pipe
|
||||
# that breaks security.
|
||||
try:
|
||||
os.unlink(FIFO)
|
||||
except OSError as e:
|
||||
pass
|
||||
os.mkfifo(FIFO, 0o600)
|
||||
|
||||
opts = (os.getenv("MPV") or "mpv").split()
|
||||
opts.extend(["--no-terminal", "--force-window", "--input-ipc-server=" + SOCK,
|
||||
opts.extend(["--no-terminal", "--force-window", "--input-file=" + FIFO,
|
||||
"--"])
|
||||
opts.extend(files)
|
||||
|
||||
|
||||
Executable
+283
@@ -0,0 +1,283 @@
|
||||
#!/usr/bin/perl
|
||||
|
||||
# Generate ZSH completion
|
||||
|
||||
#
|
||||
# This file is part of mpv.
|
||||
#
|
||||
# mpv is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2.1 of the License, or (at your option) any later version.
|
||||
#
|
||||
# mpv is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use warnings FATAL => 'uninitialized';
|
||||
|
||||
my $mpv = $ARGV[0] || 'mpv';
|
||||
|
||||
my @opts = parse_main_opts('--list-options', '^ (\-\-[^\s\*]*)\*?\s*(.*)');
|
||||
|
||||
die "Couldn't find any options" unless (@opts);
|
||||
|
||||
my @ao = parse_opts('--ao=help', '^ ([^\s\:]*)\s*: (.*)');
|
||||
my @vo = parse_opts('--vo=help', '^ ([^\s\:]*)\s*: (.*)');
|
||||
|
||||
my @af = parse_opts('--af=help', '^ ([^\s\:]*)\s*: (.*)');
|
||||
my @vf = parse_opts('--vf=help', '^ ([^\s\:]*)\s*: (.*)');
|
||||
|
||||
my @protos = parse_opts('--list-protocols', '^ ([^\s]*)');
|
||||
|
||||
my ($opts_str, $ao_str, $vo_str, $af_str, $vf_str, $protos_str);
|
||||
|
||||
$opts_str .= qq{ '$_' \\\n} foreach (@opts);
|
||||
chomp $opts_str;
|
||||
|
||||
$ao_str .= qq{ '$_' \\\n} foreach (@ao);
|
||||
chomp $ao_str;
|
||||
|
||||
$vo_str .= qq{ '$_' \\\n} foreach (@vo);
|
||||
chomp $vo_str;
|
||||
|
||||
$af_str .= qq{ '$_' \\\n} foreach (@af);
|
||||
chomp $af_str;
|
||||
|
||||
$vf_str .= qq{ '$_' \\\n} foreach (@vf);
|
||||
chomp $vf_str;
|
||||
|
||||
$protos_str = join(' ', @protos);
|
||||
|
||||
my $runtime_completions = <<'EOS';
|
||||
profile|show-profile)
|
||||
local -a profiles
|
||||
local current
|
||||
for current in "${(@f)$($words[1] --profile=help)}"; do
|
||||
current=${current//\*/\\\*}
|
||||
current=${current//\:/\\\:}
|
||||
current=${current//\[/\\\[}
|
||||
current=${current//\]/\\\]}
|
||||
if [[ $current =~ $'\t'([^$'\t']*)$'\t'(.*) ]]; then
|
||||
if [[ -n $match[2] ]]; then
|
||||
current="$match[1][$match[2]]"
|
||||
else
|
||||
current="$match[1]"
|
||||
fi
|
||||
profiles=($profiles $current)
|
||||
fi
|
||||
done
|
||||
if [[ $state == show-profile ]]; then
|
||||
# For --show-profile, only one allowed
|
||||
if (( ${#profiles} > 0 )); then
|
||||
_values 'profile' $profiles && rc=0
|
||||
fi
|
||||
else
|
||||
# For --profile, multiple allowed
|
||||
profiles=($profiles 'help[list profiles]')
|
||||
_values -s , 'profile(s)' $profiles && rc=0
|
||||
fi
|
||||
;;
|
||||
|
||||
audio-device)
|
||||
local -a audio_devices
|
||||
local current
|
||||
for current in "${(@f)$($words[1] --audio-device=help)}"; do
|
||||
current=${current//\*/\\\*}
|
||||
current=${current//\:/\\\:}
|
||||
current=${current//\[/\\\[}
|
||||
current=${current//\]/\\\]}
|
||||
if [[ $current =~ ' '\'([^\']*)\'' \('(.*)'\)' ]]; then
|
||||
audio_devices=($audio_devices "$match[1][$match[2]]")
|
||||
fi
|
||||
done
|
||||
audio_devices=($audio_devices 'help[list audio devices]')
|
||||
_values 'audio device' $audio_devices && rc=0
|
||||
;;
|
||||
EOS
|
||||
chomp $runtime_completions;
|
||||
|
||||
my $tmpl = <<"EOS";
|
||||
#compdef mpv
|
||||
|
||||
# For customization, see:
|
||||
# https://github.com/mpv-player/mpv/wiki/Zsh-completion-customization
|
||||
|
||||
local curcontext="\$curcontext" state state_descr line
|
||||
typeset -A opt_args
|
||||
|
||||
local -a match mbegin mend
|
||||
local MATCH MBEGIN MEND
|
||||
|
||||
# By default, don't complete URLs unless no files match
|
||||
local -a tag_order
|
||||
zstyle -a ":completion:*:*:\$service:*" tag-order tag_order || \
|
||||
zstyle ":completion:*:*:\$service:*" tag-order '!urls'
|
||||
|
||||
local rc=1
|
||||
|
||||
_arguments -C -S \\
|
||||
$opts_str
|
||||
'*:files:->mfiles' && rc=0
|
||||
|
||||
case \$state in
|
||||
ao)
|
||||
_values -s , 'audio outputs' \\
|
||||
$ao_str
|
||||
&& rc=0
|
||||
;;
|
||||
|
||||
vo)
|
||||
_values -s , 'video outputs' \\
|
||||
$vo_str
|
||||
&& rc=0
|
||||
;;
|
||||
|
||||
af)
|
||||
_values -s , 'audio filters' \\
|
||||
$af_str
|
||||
&& rc=0
|
||||
;;
|
||||
|
||||
vf)
|
||||
_values -s , 'video filters' \\
|
||||
$vf_str
|
||||
&& rc=0
|
||||
;;
|
||||
|
||||
$runtime_completions
|
||||
|
||||
files)
|
||||
compset -P '*,'
|
||||
compset -S ',*'
|
||||
_files -r ',/ \\t\\n\\-' && rc=0
|
||||
;;
|
||||
|
||||
mfiles)
|
||||
local expl
|
||||
_tags files urls
|
||||
while _tags; do
|
||||
_requested files expl 'media file' _files && rc=0
|
||||
if _requested urls; then
|
||||
while _next_label urls expl URL; do
|
||||
_urls "\$expl[@]" && rc=0
|
||||
compadd -S '' "\$expl[@]" $protos_str && rc=0
|
||||
done
|
||||
fi
|
||||
(( rc )) || return 0
|
||||
done
|
||||
;;
|
||||
esac
|
||||
|
||||
return rc
|
||||
EOS
|
||||
|
||||
print $tmpl;
|
||||
|
||||
sub parse_main_opts {
|
||||
my ($cmd, $regex) = @_;
|
||||
|
||||
my @list;
|
||||
my @lines = call_mpv($cmd);
|
||||
|
||||
foreach my $line (@lines) {
|
||||
my ($name, $desc) = ($line =~ /^$regex/) or next;
|
||||
|
||||
next if ($desc eq 'removed' || $desc eq 'alias');
|
||||
|
||||
if ($desc =~ /^Flag/) {
|
||||
|
||||
push @list, $name;
|
||||
|
||||
$name =~ /^--(.*)/;
|
||||
if ($1 !~ /^(\{|\}|v|list-options|really-quiet|no-.*)$/) {
|
||||
push @list, "--no-$1";
|
||||
}
|
||||
|
||||
} elsif ($desc =~ /^Print/) {
|
||||
|
||||
push @list, $name;
|
||||
|
||||
} else {
|
||||
|
||||
# Option takes argument
|
||||
|
||||
my $entry = $name;
|
||||
|
||||
$desc =~ s/\:/\\:/g;
|
||||
$entry .= "=-:$desc:";
|
||||
|
||||
if ($desc =~ /^Choices\\: ([^(]*)/) {
|
||||
my $choices = $1;
|
||||
$choices =~ s/ +$//; # strip trailing space
|
||||
$entry .= "($choices)";
|
||||
|
||||
# If "no" is one of the choices, it can also be
|
||||
# negated like a flag (--no-whatever).
|
||||
if ($choices =~ /\bno\b/) {
|
||||
$name =~ s/^--/--no-/;
|
||||
push @list, $name;
|
||||
}
|
||||
} elsif ($line =~ /\[file\]/) {
|
||||
$entry .= '->files';
|
||||
} elsif ($name =~ /^--(ao|vo|af|vf|profile|show-profile|audio-device)$/) {
|
||||
$entry .= "->$1";
|
||||
}
|
||||
push @list, $entry;
|
||||
}
|
||||
}
|
||||
|
||||
# Sort longest first, because zsh won't complete an option listed
|
||||
# after one that's a prefix of it.
|
||||
@list = sort {
|
||||
$a =~ /([^=]*)/; my $ma = $1;
|
||||
$b =~ /([^=]*)/; my $mb = $1;
|
||||
|
||||
length($mb) <=> length($ma)
|
||||
} @list;
|
||||
|
||||
return @list;
|
||||
}
|
||||
|
||||
sub parse_opts {
|
||||
my ($cmd, $regex) = @_;
|
||||
|
||||
my @list;
|
||||
my @lines = call_mpv($cmd);
|
||||
|
||||
foreach my $line (@lines) {
|
||||
if ($line !~ /^$regex/) {
|
||||
next;
|
||||
}
|
||||
|
||||
my $entry = $1;
|
||||
|
||||
if (defined $2) {
|
||||
my $desc = $2;
|
||||
$desc =~ s/\:/\\:/g;
|
||||
$entry .= "[$desc]";
|
||||
}
|
||||
|
||||
push @list, $entry
|
||||
}
|
||||
|
||||
return @list;
|
||||
}
|
||||
|
||||
sub call_mpv {
|
||||
my ($cmd) = @_;
|
||||
my $output = `"$mpv" --no-config $cmd`;
|
||||
if ($? == -1) {
|
||||
die "Could not run mpv: $!";
|
||||
} elsif ((my $exit_code = $? >> 8) != 0) {
|
||||
die "mpv returned $exit_code with output:\n$output";
|
||||
}
|
||||
return split /\n/, $output;
|
||||
}
|
||||
+3
-18
@@ -2,7 +2,6 @@ branches:
|
||||
only:
|
||||
- master
|
||||
- appveyor
|
||||
- ci
|
||||
|
||||
environment:
|
||||
global:
|
||||
@@ -15,26 +14,12 @@ 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 base-devel 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"
|
||||
build_script:
|
||||
|
||||
@@ -20,31 +20,52 @@
|
||||
#include <libavutil/samplefmt.h>
|
||||
#include <libavutil/channel_layout.h>
|
||||
#include <libavutil/mathematics.h>
|
||||
#include <libswresample/swresample.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "audio/aframe.h"
|
||||
#include "audio/fmt-conversion.h"
|
||||
#include "audio/format.h"
|
||||
#include "common/common.h"
|
||||
#include "common/av_common.h"
|
||||
#include "common/msg.h"
|
||||
#include "options/m_config.h"
|
||||
#include "options/m_option.h"
|
||||
#include "aconverter.h"
|
||||
#include "aframe.h"
|
||||
#include "fmt-conversion.h"
|
||||
#include "format.h"
|
||||
|
||||
#include "f_swresample.h"
|
||||
#include "filter_internal.h"
|
||||
#define HAVE_LIBSWRESAMPLE (!HAVE_LIBAV)
|
||||
#define HAVE_LIBAVRESAMPLE HAVE_LIBAV
|
||||
|
||||
struct priv {
|
||||
#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
|
||||
|
||||
struct mp_aconverter {
|
||||
struct mp_log *log;
|
||||
struct mpv_global *global;
|
||||
double playback_speed;
|
||||
bool is_resampling;
|
||||
struct SwrContext *avrctx;
|
||||
bool passthrough_mode;
|
||||
struct AVAudioResampleContext *avrctx;
|
||||
struct mp_aframe *avrctx_fmt; // output format of avrctx
|
||||
struct mp_aframe *pool_fmt; // format used to allocate frames for avrctx output
|
||||
struct mp_aframe *pre_out_fmt; // format before final conversion
|
||||
struct SwrContext *avrctx_out; // for output channel reordering
|
||||
struct mp_resample_opts *opts; // opts requested by the user
|
||||
struct AVAudioResampleContext *avrctx_out; // for output channel reordering
|
||||
const struct mp_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];
|
||||
@@ -59,46 +80,42 @@ struct priv {
|
||||
int out_format;
|
||||
struct mp_chmap out_channels;
|
||||
|
||||
double current_pts;
|
||||
struct mp_aframe *input;
|
||||
|
||||
double cmd_speed;
|
||||
double speed;
|
||||
|
||||
struct mp_swresample public;
|
||||
struct mp_aframe *input; // queued input frame
|
||||
bool input_eof; // queued input EOF
|
||||
struct mp_aframe *output; // queued output frame
|
||||
bool output_eof; // queued output EOF
|
||||
};
|
||||
|
||||
#define OPT_BASE_STRUCT struct mp_resample_opts
|
||||
const struct m_sub_options resample_conf = {
|
||||
.opts = (const m_option_t[]) {
|
||||
{"audio-resample-filter-size", OPT_INT(filter_size), M_RANGE(0, 32)},
|
||||
{"audio-resample-phase-shift", OPT_INT(phase_shift), M_RANGE(0, 30)},
|
||||
{"audio-resample-linear", OPT_FLAG(linear)},
|
||||
{"audio-resample-cutoff", OPT_DOUBLE(cutoff), M_RANGE(0, 1)},
|
||||
{"audio-normalize-downmix", OPT_FLAG(normalize)},
|
||||
{"audio-resample-max-output-size", OPT_DOUBLE(max_output_frame_size)},
|
||||
{"audio-swresample-o", OPT_KEYVALUELIST(avopts)},
|
||||
{0}
|
||||
},
|
||||
.size = sizeof(struct mp_resample_opts),
|
||||
.defaults = &(const struct mp_resample_opts)MP_RESAMPLE_OPTS_DEF,
|
||||
.change_flags = UPDATE_AUDIO,
|
||||
};
|
||||
|
||||
static double get_delay(struct priv *p)
|
||||
#if HAVE_LIBAVRESAMPLE
|
||||
static double get_delay(struct mp_aconverter *p)
|
||||
{
|
||||
return avresample_get_delay(p->avrctx) / (double)p->in_rate +
|
||||
avresample_available(p->avrctx) / (double)p->out_rate;
|
||||
}
|
||||
static int get_out_samples(struct mp_aconverter *p, int in_samples)
|
||||
{
|
||||
return avresample_get_out_samples(p->avrctx, in_samples);
|
||||
}
|
||||
#else
|
||||
static double get_delay(struct mp_aconverter *p)
|
||||
{
|
||||
int64_t base = p->in_rate * (int64_t)p->out_rate;
|
||||
return swr_get_delay(p->avrctx, base) / (double)base;
|
||||
}
|
||||
static int get_out_samples(struct priv *p, int in_samples)
|
||||
static int get_out_samples(struct mp_aconverter *p, int in_samples)
|
||||
{
|
||||
return swr_get_out_samples(p->avrctx, in_samples);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void close_lavrr(struct priv *p)
|
||||
static void close_lavrr(struct mp_aconverter *p)
|
||||
{
|
||||
swr_free(&p->avrctx);
|
||||
swr_free(&p->avrctx_out);
|
||||
if (p->avrctx)
|
||||
avresample_close(p->avrctx);
|
||||
avresample_free(&p->avrctx);
|
||||
if (p->avrctx_out)
|
||||
avresample_close(p->avrctx_out);
|
||||
avresample_free(&p->avrctx_out);
|
||||
|
||||
TA_FREEP(&p->pre_out_fmt);
|
||||
TA_FREEP(&p->avrctx_fmt);
|
||||
@@ -122,7 +139,7 @@ static struct mp_chmap fudge_pairs[][2] = {
|
||||
// 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 priv *p,
|
||||
static uint64_t fudge_layout_conversion(struct mp_aconverter *p,
|
||||
uint64_t in, uint64_t out)
|
||||
{
|
||||
for (int n = 0; n < MP_ARRAY_SIZE(fudge_pairs); n++) {
|
||||
@@ -157,20 +174,22 @@ static void transpose_order(int *map, int num)
|
||||
memcpy(map, nmap, sizeof(nmap));
|
||||
}
|
||||
|
||||
static bool configure_lavrr(struct priv *p, bool verbose)
|
||||
static bool configure_lavrr(struct mp_aconverter *p, bool verbose)
|
||||
{
|
||||
close_lavrr(p);
|
||||
|
||||
p->in_rate = rate_from_speed(p->in_rate_user, p->speed);
|
||||
p->in_rate = rate_from_speed(p->in_rate_user, p->playback_speed);
|
||||
|
||||
MP_VERBOSE(p, "%dHz %s %s -> %dHz %s %s\n",
|
||||
p->in_rate, mp_chmap_to_str(&p->in_channels),
|
||||
af_fmt_to_str(p->in_format),
|
||||
p->out_rate, mp_chmap_to_str(&p->out_channels),
|
||||
af_fmt_to_str(p->out_format));
|
||||
p->passthrough_mode = p->opts->allow_passthrough &&
|
||||
p->in_rate == p->out_rate &&
|
||||
p->in_format == p->out_format &&
|
||||
mp_chmap_equals(&p->in_channels, &p->out_channels);
|
||||
|
||||
p->avrctx = swr_alloc();
|
||||
p->avrctx_out = swr_alloc();
|
||||
if (p->passthrough_mode)
|
||||
return true;
|
||||
|
||||
p->avrctx = avresample_alloc_context();
|
||||
p->avrctx_out = avresample_alloc_context();
|
||||
if (!p->avrctx || !p->avrctx_out)
|
||||
goto error;
|
||||
|
||||
@@ -181,11 +200,7 @@ static bool configure_lavrr(struct priv *p, bool verbose)
|
||||
if (in_samplefmt == AV_SAMPLE_FMT_NONE ||
|
||||
out_samplefmt == AV_SAMPLE_FMT_NONE ||
|
||||
out_samplefmtp == AV_SAMPLE_FMT_NONE)
|
||||
{
|
||||
MP_ERR(p, "unsupported conversion: %s -> %s\n",
|
||||
af_fmt_to_str(p->in_format), af_fmt_to_str(p->out_format));
|
||||
goto error;
|
||||
}
|
||||
|
||||
av_opt_set_int(p->avrctx, "filter_size", p->opts->filter_size, 0);
|
||||
av_opt_set_int(p->avrctx, "phase_shift", p->opts->phase_shift, 0);
|
||||
@@ -196,8 +211,17 @@ static bool configure_lavrr(struct priv *p, bool verbose)
|
||||
cutoff = MPMAX(1.0 - 6.5 / (p->opts->filter_size + 8), 0.80);
|
||||
av_opt_set_double(p->avrctx, "cutoff", cutoff, 0);
|
||||
|
||||
int global_normalize;
|
||||
mp_read_option_raw(p->global, "audio-normalize-downmix", &m_option_type_flag,
|
||||
&global_normalize);
|
||||
int normalize = p->opts->normalize;
|
||||
if (normalize < 0)
|
||||
normalize = global_normalize;
|
||||
#if HAVE_LIBSWRESAMPLE
|
||||
av_opt_set_double(p->avrctx, "rematrix_maxval", normalize ? 1 : 1000, 0);
|
||||
#else
|
||||
av_opt_set_int(p->avrctx, "normalize_mix_level", !!normalize, 0);
|
||||
#endif
|
||||
|
||||
if (mp_set_avopts(p->log, p->avrctx, p->opts->avopts) < 0)
|
||||
goto error;
|
||||
@@ -296,11 +320,11 @@ static bool configure_lavrr(struct priv *p, bool verbose)
|
||||
// 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.
|
||||
swr_set_channel_mapping(p->avrctx, p->reorder_in);
|
||||
avresample_set_channel_mapping(p->avrctx, p->reorder_in);
|
||||
|
||||
p->is_resampling = false;
|
||||
|
||||
if (swr_init(p->avrctx) < 0 || swr_init(p->avrctx_out) < 0) {
|
||||
if (avresample_open(p->avrctx) < 0 || avresample_open(p->avrctx_out) < 0) {
|
||||
MP_ERR(p, "Cannot open Libavresample context.\n");
|
||||
goto error;
|
||||
}
|
||||
@@ -308,23 +332,47 @@ static bool configure_lavrr(struct priv *p, bool verbose)
|
||||
|
||||
error:
|
||||
close_lavrr(p);
|
||||
mp_filter_internal_mark_failed(p->public.f);
|
||||
MP_FATAL(p, "libswresample failed to initialize.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
static void reset(struct mp_filter *f)
|
||||
bool mp_aconverter_reconfig(struct mp_aconverter *p,
|
||||
int in_rate, int in_format, struct mp_chmap in_channels,
|
||||
int out_rate, int out_format, struct mp_chmap out_channels)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
close_lavrr(p);
|
||||
|
||||
p->current_pts = MP_NOPTS_VALUE;
|
||||
TA_FREEP(&p->input);
|
||||
TA_FREEP(&p->output);
|
||||
p->input_eof = p->output_eof = false;
|
||||
|
||||
p->playback_speed = 1.0;
|
||||
|
||||
p->in_rate_user = in_rate;
|
||||
p->in_format = in_format;
|
||||
p->in_channels = in_channels;
|
||||
p->out_rate = out_rate;
|
||||
p->out_format = out_format;
|
||||
p->out_channels = out_channels;
|
||||
|
||||
return configure_lavrr(p, true);
|
||||
}
|
||||
|
||||
void mp_aconverter_flush(struct mp_aconverter *p)
|
||||
{
|
||||
if (!p->avrctx)
|
||||
return;
|
||||
#if HAVE_LIBSWRESAMPLE
|
||||
swr_close(p->avrctx);
|
||||
if (swr_init(p->avrctx) < 0)
|
||||
close_lavrr(p);
|
||||
#else
|
||||
while (avresample_read(p->avrctx, NULL, 1000) > 0) {}
|
||||
#endif
|
||||
}
|
||||
|
||||
void mp_aconverter_set_speed(struct mp_aconverter *p, double speed)
|
||||
{
|
||||
p->playback_speed = speed;
|
||||
}
|
||||
|
||||
static void extra_output_conversion(struct mp_aframe *mpa)
|
||||
@@ -354,7 +402,7 @@ static bool reorder_planes(struct mp_aframe *mpa, int *reorder,
|
||||
if (!mp_aframe_set_chmap(mpa, newmap))
|
||||
return false;
|
||||
|
||||
int num_planes = mp_aframe_get_planes(mpa);
|
||||
int num_planes = newmap->num;
|
||||
uint8_t **planes = mp_aframe_get_data_rw(mpa);
|
||||
uint8_t *old_planes[MP_NUM_CHANNELS];
|
||||
assert(num_planes <= MP_NUM_CHANNELS);
|
||||
@@ -383,39 +431,31 @@ static bool reorder_planes(struct mp_aframe *mpa, int *reorder,
|
||||
return true;
|
||||
}
|
||||
|
||||
static int resample_frame(struct SwrContext *r,
|
||||
struct mp_aframe *out, struct mp_aframe *in,
|
||||
int consume_in)
|
||||
static int resample_frame(struct AVAudioResampleContext *r,
|
||||
struct mp_aframe *out, struct mp_aframe *in)
|
||||
{
|
||||
// Be aware that the channel layout and count can be different for in and
|
||||
// out frames. In some situations the caller will fix up the frames before
|
||||
// or after conversion. The sample rates can also be different.
|
||||
AVFrame *av_i = in ? mp_aframe_get_raw_avframe(in) : NULL;
|
||||
AVFrame *av_o = out ? mp_aframe_get_raw_avframe(out) : NULL;
|
||||
return swr_convert(r,
|
||||
return avresample_convert(r,
|
||||
av_o ? av_o->extended_data : NULL,
|
||||
av_o ? av_o->linesize[0] : 0,
|
||||
av_o ? av_o->nb_samples : 0,
|
||||
(const uint8_t **)(av_i ? av_i->extended_data : NULL),
|
||||
av_i ? MPMIN(av_i->nb_samples, consume_in) : 0);
|
||||
av_i ? av_i->extended_data : NULL,
|
||||
av_i ? av_i->linesize[0] : 0,
|
||||
av_i ? av_i->nb_samples : 0);
|
||||
}
|
||||
|
||||
static struct mp_frame filter_resample_output(struct priv *p,
|
||||
struct mp_aframe *in)
|
||||
static void filter_resample(struct mp_aconverter *p, struct mp_aframe *in)
|
||||
{
|
||||
struct mp_aframe *out = NULL;
|
||||
|
||||
if (!p->avrctx)
|
||||
goto error;
|
||||
|
||||
// Limit the filtered data size for better latency when changing speed.
|
||||
// Avoid buffering data within the resampler => restrict input size.
|
||||
// p->in_rate already includes the speed factor.
|
||||
double s = p->opts->max_output_frame_size / 1000 * p->in_rate;
|
||||
int max_in = lrint(MPCLAMP(s, 128, INT_MAX));
|
||||
int consume_in = in ? mp_aframe_get_size(in) : 0;
|
||||
consume_in = MPMIN(consume_in, max_in);
|
||||
|
||||
int samples = get_out_samples(p, consume_in);
|
||||
int samples = get_out_samples(p, in ? mp_aframe_get_size(in) : 0);
|
||||
out = mp_aframe_create();
|
||||
mp_aframe_config_copy(out, p->pool_fmt);
|
||||
if (mp_aframe_pool_allocate(p->out_pool, out, samples) < 0)
|
||||
@@ -423,7 +463,7 @@ static struct mp_frame filter_resample_output(struct priv *p,
|
||||
|
||||
int out_samples = 0;
|
||||
if (samples) {
|
||||
out_samples = resample_frame(p->avrctx, out, in, consume_in);
|
||||
out_samples = resample_frame(p->avrctx, out, in);
|
||||
if (out_samples < 0 || out_samples > samples)
|
||||
goto error;
|
||||
mp_aframe_set_size(out, out_samples);
|
||||
@@ -444,7 +484,7 @@ static struct mp_frame filter_resample_output(struct priv *p,
|
||||
}
|
||||
int got = 0;
|
||||
if (out_samples)
|
||||
got = resample_frame(p->avrctx_out, new, out, out_samples);
|
||||
got = resample_frame(p->avrctx_out, new, out);
|
||||
talloc_free(out);
|
||||
out = new;
|
||||
if (got != out_samples)
|
||||
@@ -453,141 +493,44 @@ static struct mp_frame filter_resample_output(struct priv *p,
|
||||
|
||||
extra_output_conversion(out);
|
||||
|
||||
if (in) {
|
||||
if (in)
|
||||
mp_aframe_copy_attributes(out, in);
|
||||
p->current_pts = mp_aframe_end_pts(in);
|
||||
mp_aframe_skip_samples(in, consume_in);
|
||||
}
|
||||
|
||||
if (out_samples) {
|
||||
if (p->current_pts != MP_NOPTS_VALUE) {
|
||||
double delay = get_delay(p) * mp_aframe_get_speed(out) +
|
||||
mp_aframe_duration(out) +
|
||||
(p->input ? mp_aframe_duration(p->input) : 0);
|
||||
mp_aframe_set_pts(out, p->current_pts - delay);
|
||||
mp_aframe_mul_speed(out, p->speed);
|
||||
}
|
||||
p->output = out;
|
||||
} else {
|
||||
TA_FREEP(&out);
|
||||
talloc_free(out);
|
||||
}
|
||||
p->output_eof = !in; // we've read everything
|
||||
|
||||
return out ? MAKE_FRAME(MP_FRAME_AUDIO, out) : MP_NO_FRAME;
|
||||
return;
|
||||
error:
|
||||
talloc_free(out);
|
||||
MP_ERR(p, "Error on resampling.\n");
|
||||
mp_filter_internal_mark_failed(p->public.f);
|
||||
return MP_NO_FRAME;
|
||||
}
|
||||
|
||||
static void process(struct mp_filter *f)
|
||||
static void filter(struct mp_aconverter *p)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
|
||||
if (!mp_pin_in_needs_data(f->ppins[1]))
|
||||
if (p->output || p->output_eof || !(p->input || p->input_eof))
|
||||
return;
|
||||
|
||||
p->speed = p->cmd_speed * p->public.speed;
|
||||
int new_rate = rate_from_speed(p->in_rate_user, p->playback_speed);
|
||||
|
||||
struct mp_aframe *input = NULL;
|
||||
if (!p->input) {
|
||||
struct mp_frame frame = mp_pin_out_read(f->ppins[0]);
|
||||
if (p->passthrough_mode && new_rate != p->in_rate)
|
||||
configure_lavrr(p, false);
|
||||
|
||||
if (frame.type == MP_FRAME_AUDIO) {
|
||||
input = frame.data;
|
||||
} else if (!frame.type) {
|
||||
return; // no new data
|
||||
} else if (frame.type != MP_FRAME_EOF) {
|
||||
MP_ERR(p, "Unsupported frame type.\n");
|
||||
mp_frame_unref(&frame);
|
||||
mp_filter_internal_mark_failed(f);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!input && !p->avrctx) {
|
||||
// Obviously no draining needed.
|
||||
mp_pin_in_write(f->ppins[1], MP_EOF_FRAME);
|
||||
return;
|
||||
}
|
||||
if (p->passthrough_mode) {
|
||||
p->output = p->input;
|
||||
p->input = NULL;
|
||||
p->output_eof = p->input_eof;
|
||||
p->input_eof = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (input) {
|
||||
assert(!p->input);
|
||||
|
||||
struct mp_swresample *s = &p->public;
|
||||
|
||||
int in_rate = mp_aframe_get_rate(input);
|
||||
int in_format = mp_aframe_get_format(input);
|
||||
struct mp_chmap in_channels = {0};
|
||||
mp_aframe_get_chmap(input, &in_channels);
|
||||
|
||||
if (!in_rate || !in_format || !in_channels.num) {
|
||||
MP_ERR(p, "Frame with invalid format unsupported\n");
|
||||
talloc_free(input);
|
||||
mp_filter_internal_mark_failed(f);
|
||||
return;
|
||||
}
|
||||
|
||||
int out_rate = s->out_rate ? s->out_rate : in_rate;
|
||||
int out_format = s->out_format ? s->out_format : in_format;
|
||||
struct mp_chmap out_channels =
|
||||
s->out_channels.num ? s->out_channels : in_channels;
|
||||
|
||||
if (p->in_rate_user != in_rate ||
|
||||
p->in_format != in_format ||
|
||||
!mp_chmap_equals(&p->in_channels, &in_channels) ||
|
||||
p->out_rate != out_rate ||
|
||||
p->out_format != out_format ||
|
||||
!mp_chmap_equals(&p->out_channels, &out_channels) ||
|
||||
!p->avrctx)
|
||||
{
|
||||
if (p->avrctx) {
|
||||
// drain remaining audio
|
||||
struct mp_frame out = filter_resample_output(p, NULL);
|
||||
if (out.type) {
|
||||
mp_pin_in_write(f->ppins[1], out);
|
||||
// continue filtering next time.
|
||||
mp_pin_out_unread(f->ppins[0],
|
||||
MAKE_FRAME(MP_FRAME_AUDIO, input));
|
||||
input = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
MP_VERBOSE(p, "format change, reinitializing resampler\n");
|
||||
|
||||
p->in_rate_user = in_rate;
|
||||
p->in_format = in_format;
|
||||
p->in_channels = in_channels;
|
||||
p->out_rate = out_rate;
|
||||
p->out_format = out_format;
|
||||
p->out_channels = out_channels;
|
||||
|
||||
if (!configure_lavrr(p, true)) {
|
||||
talloc_free(input);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!input) {
|
||||
// continue filtering next time
|
||||
mp_filter_internal_mark_progress(f);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
p->input = input;
|
||||
}
|
||||
|
||||
int new_rate = rate_from_speed(p->in_rate_user, p->speed);
|
||||
bool exact_rate = new_rate == p->in_rate;
|
||||
bool use_comp = fabs(new_rate / (double)p->in_rate - 1) <= 0.01;
|
||||
// If we've never used compensation, avoid setting it - even if it's in
|
||||
// theory a NOP, libswresample will enable resampling. _If_ we're
|
||||
// resampling, we might have to disable previously enabled compensation.
|
||||
if (exact_rate && !p->is_resampling)
|
||||
use_comp = false;
|
||||
if (p->avrctx && use_comp) {
|
||||
AVRational r =
|
||||
av_d2q(p->speed * p->in_rate_user / p->in_rate, INT_MAX / 2);
|
||||
// Essentially, swr_set_compensation() does 2 things:
|
||||
if (p->avrctx && !(!p->is_resampling && new_rate == p->in_rate)) {
|
||||
AVRational r = av_d2q(p->playback_speed * p->in_rate_user / p->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
|
||||
@@ -595,105 +538,104 @@ static void process(struct mp_filter *f)
|
||||
// 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 (r.den == r.num)
|
||||
r = (AVRational){0}; // fully disable
|
||||
if (swr_set_compensation(p->avrctx, r.den - r.num, r.den) >= 0) {
|
||||
exact_rate = true;
|
||||
p->is_resampling = true; // libswresample can auto-enable it
|
||||
if (avresample_set_compensation(p->avrctx, r.den - r.num, r.den) >= 0) {
|
||||
new_rate = p->in_rate;
|
||||
p->is_resampling = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!exact_rate) {
|
||||
bool need_reinit = fabs(new_rate / (double)p->in_rate - 1) > 0.01;
|
||||
if (need_reinit && new_rate != p->in_rate) {
|
||||
// Before reconfiguring, drain the audio that is still buffered
|
||||
// in the resampler.
|
||||
struct mp_frame out = filter_resample_output(p, NULL);
|
||||
bool need_drain = !!out.type;
|
||||
if (need_drain)
|
||||
mp_pin_in_write(f->ppins[1], out);
|
||||
filter_resample(p, NULL);
|
||||
// Reinitialize resampler.
|
||||
configure_lavrr(p, false);
|
||||
// If we've written output, we must continue filtering next time.
|
||||
if (need_drain)
|
||||
return;
|
||||
p->output_eof = false;
|
||||
if (p->output)
|
||||
return; // need to read output before continuing filtering
|
||||
}
|
||||
|
||||
struct mp_frame out = filter_resample_output(p, p->input);
|
||||
filter_resample(p, p->input);
|
||||
TA_FREEP(&p->input);
|
||||
p->input_eof = false;
|
||||
}
|
||||
|
||||
if (out.type) {
|
||||
mp_pin_in_write(f->ppins[1], out);
|
||||
if (!p->input)
|
||||
mp_pin_out_repeat_eof(f->ppins[0]);
|
||||
} else if (p->input) {
|
||||
mp_filter_internal_mark_progress(f); // try to consume more input
|
||||
} else {
|
||||
mp_pin_in_write(f->ppins[1], MP_EOF_FRAME);
|
||||
// Queue input. If true, ownership of in passes to mp_aconverted and the input
|
||||
// was accepted. Otherwise, return false and reject in.
|
||||
// in==NULL means trigger EOF.
|
||||
bool mp_aconverter_write_input(struct mp_aconverter *p, struct mp_aframe *in)
|
||||
{
|
||||
if (p->input || p->input_eof)
|
||||
return false;
|
||||
|
||||
p->input = in;
|
||||
p->input_eof = !in;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Return output frame, or NULL if nothing available.
|
||||
// *eof is set to true if NULL is returned, and it was due to EOF.
|
||||
struct mp_aframe *mp_aconverter_read_output(struct mp_aconverter *p, bool *eof)
|
||||
{
|
||||
*eof = false;
|
||||
|
||||
filter(p);
|
||||
|
||||
if (p->output) {
|
||||
struct mp_aframe *out = p->output;
|
||||
p->output = NULL;
|
||||
return out;
|
||||
}
|
||||
|
||||
if (p->input && !mp_aframe_get_size(p->input))
|
||||
TA_FREEP(&p->input);
|
||||
*eof = p->output_eof;
|
||||
p->output_eof = false;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
double mp_swresample_get_delay(struct mp_swresample *s)
|
||||
double mp_aconverter_get_latency(struct mp_aconverter *p)
|
||||
{
|
||||
struct priv *p = s->f->priv;
|
||||
double delay = get_delay(p);
|
||||
|
||||
return get_delay(p);
|
||||
if (p->input)
|
||||
delay += mp_aframe_duration(p->input);
|
||||
|
||||
// In theory this is influenced by playback speed, but other parts of the
|
||||
// player get it wrong anyway.
|
||||
if (p->output)
|
||||
delay += mp_aframe_duration(p->output);
|
||||
|
||||
return delay;
|
||||
}
|
||||
|
||||
static bool command(struct mp_filter *f, struct mp_filter_command *cmd)
|
||||
static void destroy_aconverter(void *ptr)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
|
||||
if (cmd->type == MP_FILTER_COMMAND_SET_SPEED_RESAMPLE) {
|
||||
p->cmd_speed = cmd->speed;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void destroy(struct mp_filter *f)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
struct mp_aconverter *p = ptr;
|
||||
|
||||
close_lavrr(p);
|
||||
TA_FREEP(&p->input);
|
||||
|
||||
talloc_free(p->input);
|
||||
talloc_free(p->output);
|
||||
}
|
||||
|
||||
static const struct mp_filter_info swresample_filter = {
|
||||
.name = "swresample",
|
||||
.priv_size = sizeof(struct priv),
|
||||
.process = process,
|
||||
.command = command,
|
||||
.reset = reset,
|
||||
.destroy = destroy,
|
||||
};
|
||||
|
||||
struct mp_swresample *mp_swresample_create(struct mp_filter *parent,
|
||||
struct mp_resample_opts *opts)
|
||||
// If opts is not NULL, the pointer must be valid for the lifetime of the
|
||||
// mp_aconverter.
|
||||
struct mp_aconverter *mp_aconverter_create(struct mpv_global *global,
|
||||
struct mp_log *log,
|
||||
const struct mp_resample_opts *opts)
|
||||
{
|
||||
struct mp_filter *f = mp_filter_create(parent, &swresample_filter);
|
||||
if (!f)
|
||||
return NULL;
|
||||
struct mp_aconverter *p = talloc_zero(NULL, struct mp_aconverter);
|
||||
p->log = log;
|
||||
p->global = global;
|
||||
|
||||
mp_filter_add_pin(f, MP_PIN_IN, "in");
|
||||
mp_filter_add_pin(f, MP_PIN_OUT, "out");
|
||||
static const struct mp_resample_opts defs = MP_RESAMPLE_OPTS_DEF;
|
||||
|
||||
struct priv *p = f->priv;
|
||||
p->public.f = f;
|
||||
p->public.speed = 1.0;
|
||||
p->cmd_speed = 1.0;
|
||||
p->log = f->log;
|
||||
|
||||
if (opts) {
|
||||
p->opts = talloc_dup(p, opts);
|
||||
p->opts->avopts = mp_dup_str_array(p, p->opts->avopts);
|
||||
} else {
|
||||
p->opts = mp_get_config_group(p, f->global, &resample_conf);
|
||||
}
|
||||
p->opts = opts ? opts : &defs;
|
||||
|
||||
p->reorder_buffer = mp_aframe_pool_create(p);
|
||||
p->out_pool = mp_aframe_pool_create(p);
|
||||
|
||||
return &p->public;
|
||||
talloc_set_destructor(p, destroy_aconverter);
|
||||
|
||||
return p;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "chmap.h"
|
||||
|
||||
struct mp_aconverter;
|
||||
struct mp_aframe;
|
||||
struct mpv_global;
|
||||
struct mp_log;
|
||||
|
||||
struct mp_resample_opts {
|
||||
int filter_size;
|
||||
int phase_shift;
|
||||
int linear;
|
||||
double cutoff;
|
||||
int normalize;
|
||||
int allow_passthrough;
|
||||
char **avopts;
|
||||
};
|
||||
|
||||
#define MP_RESAMPLE_OPTS_DEF { \
|
||||
.filter_size = 16, \
|
||||
.cutoff = 0.0, \
|
||||
.phase_shift = 10, \
|
||||
.normalize = -1, \
|
||||
}
|
||||
|
||||
struct mp_aconverter *mp_aconverter_create(struct mpv_global *global,
|
||||
struct mp_log *log,
|
||||
const struct mp_resample_opts *opts);
|
||||
bool mp_aconverter_reconfig(struct mp_aconverter *p,
|
||||
int in_rate, int in_format, struct mp_chmap in_channels,
|
||||
int out_rate, int out_format, struct mp_chmap out_channels);
|
||||
void mp_aconverter_flush(struct mp_aconverter *p);
|
||||
void mp_aconverter_set_speed(struct mp_aconverter *p, double speed);
|
||||
bool mp_aconverter_write_input(struct mp_aconverter *p, struct mp_aframe *in);
|
||||
struct mp_aframe *mp_aconverter_read_output(struct mp_aconverter *p, bool *eof);
|
||||
double mp_aconverter_get_latency(struct mp_aconverter *p);
|
||||
+18
-204
@@ -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);
|
||||
}
|
||||
|
||||
@@ -205,8 +172,10 @@ void mp_aframe_config_copy(struct mp_aframe *dst, struct mp_aframe *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;
|
||||
#if LIBAVUTIL_VERSION_MICRO >= 100
|
||||
// FFmpeg being a stupid POS again
|
||||
dst->av_frame->channels = src->av_frame->channels;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Copy "soft" attributes from src to dst, excluding things which affect
|
||||
@@ -214,7 +183,6 @@ void mp_aframe_config_copy(struct mp_aframe *dst, struct mp_aframe *src)
|
||||
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;
|
||||
|
||||
@@ -320,8 +288,10 @@ 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;
|
||||
}
|
||||
|
||||
@@ -346,37 +316,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)
|
||||
{
|
||||
@@ -400,65 +339,46 @@ int mp_aframe_get_total_plane_samples(struct mp_aframe *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 +386,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 +397,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 +405,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 +427,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 +461,6 @@ int mp_aframe_pool_allocate(struct mp_aframe_pool *pool, struct mp_aframe *frame
|
||||
av_frame->linesize[0] = samples * sstride;
|
||||
for (int n = 0; n < planes; n++)
|
||||
av_frame->extended_data[n] = av_frame->buf[0]->data + n * plane_size;
|
||||
for (int n = 0; n < MPMIN(planes, AV_NUM_DATA_POINTERS); n++)
|
||||
av_frame->data[n] = av_frame->extended_data[n];
|
||||
av_frame->nb_samples = samples;
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
struct mp_aframe;
|
||||
struct AVFrame;
|
||||
@@ -20,7 +18,6 @@ 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);
|
||||
@@ -37,36 +34,21 @@ 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);
|
||||
|
||||
+625
@@ -0,0 +1,625 @@
|
||||
/*
|
||||
* 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 != avframe->channels)
|
||||
mp_chmap_from_channels(&lavc_chmap, avframe->channels);
|
||||
#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)
|
||||
{
|
||||
if (!aframe)
|
||||
return NULL;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
struct mp_aframe *mp_audio_to_aframe(struct mp_audio *mpa)
|
||||
{
|
||||
if (!mpa)
|
||||
return NULL;
|
||||
|
||||
struct mp_aframe *aframe = mp_aframe_create();
|
||||
struct AVFrame *av = mp_aframe_get_raw_avframe(aframe);
|
||||
mp_aframe_set_format(aframe, mpa->format);
|
||||
mp_aframe_set_chmap(aframe, &mpa->channels);
|
||||
mp_aframe_set_rate(aframe, mpa->rate);
|
||||
|
||||
// bullshit it into ffmpeg-compatible parameters
|
||||
struct mp_audio mpb = *mpa;
|
||||
struct mp_chmap chmap;
|
||||
mp_chmap_set_unknown(&chmap, mpb.channels.num);
|
||||
mp_audio_set_channels(&mpb, &chmap);
|
||||
if (af_fmt_is_spdif(mpb.format))
|
||||
mp_audio_set_format(&mpb, AF_FORMAT_S16);
|
||||
|
||||
// put the reference into av, which magically puts it into aframe
|
||||
// aframe keeps its parameters, so the bullshit doesn't matter
|
||||
if (mp_audio_to_avframe(&mpb, av) < 0) {
|
||||
talloc_free(aframe);
|
||||
return NULL;
|
||||
}
|
||||
return aframe;
|
||||
}
|
||||
|
||||
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
|
||||
avframe->channels = 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;
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* 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_aframe *mp_audio_to_aframe(struct mp_audio *mpa);
|
||||
|
||||
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);
|
||||
|
||||
#include "filter/af.h"
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* 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 "chmap.h"
|
||||
#include "audio_buffer.h"
|
||||
#include "format.h"
|
||||
|
||||
struct mp_audio_buffer {
|
||||
int format;
|
||||
struct mp_chmap channels;
|
||||
int srate;
|
||||
int sstride;
|
||||
int num_planes;
|
||||
uint8_t *data[MP_NUM_CHANNELS];
|
||||
int allocated;
|
||||
int num_samples;
|
||||
};
|
||||
|
||||
struct mp_audio_buffer *mp_audio_buffer_create(void *talloc_ctx)
|
||||
{
|
||||
return talloc_zero(talloc_ctx, struct mp_audio_buffer);
|
||||
}
|
||||
|
||||
// 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_fmt(struct mp_audio_buffer *ab, int format,
|
||||
const struct mp_chmap *channels, int srate)
|
||||
{
|
||||
for (int n = 0; n < MP_NUM_CHANNELS; n++)
|
||||
TA_FREEP(&ab->data[n]);
|
||||
ab->format = format;
|
||||
ab->channels = *channels;
|
||||
ab->srate = srate;
|
||||
ab->allocated = 0;
|
||||
ab->num_samples = 0;
|
||||
ab->sstride = af_fmt_to_bytes(ab->format);
|
||||
ab->num_planes = 1;
|
||||
if (af_fmt_is_planar(ab->format)) {
|
||||
ab->num_planes = ab->channels.num;
|
||||
} else {
|
||||
ab->sstride *= ab->channels.num;
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
if (samples > ab->allocated) {
|
||||
for (int n = 0; n < ab->num_planes; n++) {
|
||||
ab->data[n] = talloc_realloc(ab, ab->data[n], char,
|
||||
ab->sstride * samples);
|
||||
}
|
||||
ab->allocated = 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 ab->allocated - ab->num_samples;
|
||||
}
|
||||
|
||||
// All integer parameters are in samples.
|
||||
// dst and src can overlap.
|
||||
static void copy_planes(struct mp_audio_buffer *ab,
|
||||
uint8_t **dst, int dst_offset,
|
||||
uint8_t **src, int src_offset, int length)
|
||||
{
|
||||
for (int n = 0; n < ab->num_planes; n++) {
|
||||
memmove((char *)dst[n] + dst_offset * ab->sstride,
|
||||
(char *)src[n] + src_offset * ab->sstride,
|
||||
length * ab->sstride);
|
||||
}
|
||||
}
|
||||
|
||||
// Append data to the end of the buffer.
|
||||
// If the buffer is not large enough, it is transparently resized.
|
||||
void mp_audio_buffer_append(struct mp_audio_buffer *ab, void **ptr, int samples)
|
||||
{
|
||||
mp_audio_buffer_preallocate_min(ab, ab->num_samples + samples);
|
||||
copy_planes(ab, ab->data, ab->num_samples, (uint8_t **)ptr, 0, samples);
|
||||
ab->num_samples += 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);
|
||||
mp_audio_buffer_preallocate_min(ab, ab->num_samples + samples);
|
||||
copy_planes(ab, ab->data, samples, ab->data, 0, ab->num_samples);
|
||||
ab->num_samples += samples;
|
||||
for (int n = 0; n < ab->num_planes; n++)
|
||||
af_fill_silence(ab->data[n], samples * ab->sstride, ab->format);
|
||||
}
|
||||
|
||||
void mp_audio_buffer_duplicate(struct mp_audio_buffer *ab, int samples)
|
||||
{
|
||||
assert(samples >= 0 && samples <= ab->num_samples);
|
||||
mp_audio_buffer_preallocate_min(ab, ab->num_samples + samples);
|
||||
copy_planes(ab, ab->data, ab->num_samples,
|
||||
ab->data, ab->num_samples - samples, samples);
|
||||
ab->num_samples += samples;
|
||||
}
|
||||
|
||||
// Get the start of the current readable buffer.
|
||||
void mp_audio_buffer_peek(struct mp_audio_buffer *ab, uint8_t ***ptr,
|
||||
int *samples)
|
||||
{
|
||||
*ptr = ab->data;
|
||||
*samples = ab->num_samples;
|
||||
}
|
||||
|
||||
// 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->num_samples);
|
||||
copy_planes(ab, ab->data, 0, ab->data, samples, ab->num_samples - samples);
|
||||
ab->num_samples -= samples;
|
||||
}
|
||||
|
||||
void mp_audio_buffer_clear(struct mp_audio_buffer *ab)
|
||||
{
|
||||
ab->num_samples = 0;
|
||||
}
|
||||
|
||||
// Return number of buffered audio samples
|
||||
int mp_audio_buffer_samples(struct mp_audio_buffer *ab)
|
||||
{
|
||||
return ab->num_samples;
|
||||
}
|
||||
|
||||
// Return amount of buffered audio in seconds.
|
||||
double mp_audio_buffer_seconds(struct mp_audio_buffer *ab)
|
||||
{
|
||||
return ab->num_samples / (double)ab->srate;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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_chmap;
|
||||
|
||||
struct mp_audio_buffer *mp_audio_buffer_create(void *talloc_ctx);
|
||||
void mp_audio_buffer_reinit_fmt(struct mp_audio_buffer *ab, int format,
|
||||
const struct mp_chmap *channels, int srate);
|
||||
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_append(struct mp_audio_buffer *ab, void **ptr, int samples);
|
||||
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, uint8_t ***ptr,
|
||||
int *samples);
|
||||
void mp_audio_buffer_skip(struct mp_audio_buffer *ab, int samples);
|
||||
void mp_audio_buffer_clear(struct mp_audio_buffer *ab);
|
||||
int mp_audio_buffer_samples(struct mp_audio_buffer *ab);
|
||||
double mp_audio_buffer_seconds(struct mp_audio_buffer *ab);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MPLAYER_AD_H
|
||||
#define MPLAYER_AD_H
|
||||
|
||||
#include "common/codecs.h"
|
||||
#include "demux/stheader.h"
|
||||
#include "demux/demux.h"
|
||||
|
||||
#include "audio/format.h"
|
||||
#include "audio/aframe.h"
|
||||
#include "dec_audio.h"
|
||||
|
||||
struct mp_decoder_list;
|
||||
|
||||
/* interface of audio decoder drivers */
|
||||
struct ad_functions {
|
||||
const char *name;
|
||||
void (*add_decoders)(struct mp_decoder_list *list);
|
||||
int (*init)(struct dec_audio *da, const char *decoder);
|
||||
void (*uninit)(struct dec_audio *da);
|
||||
int (*control)(struct dec_audio *da, int cmd, void *arg);
|
||||
// Return whether or not the packet has been consumed.
|
||||
bool (*send_packet)(struct dec_audio *da, struct demux_packet *pkt);
|
||||
// Return whether decoding is still going on (false if EOF was reached).
|
||||
// Never returns false & *out set, but can return true with !*out.
|
||||
bool (*receive_frame)(struct dec_audio *da, struct mp_aframe **out);
|
||||
};
|
||||
|
||||
enum ad_ctrl {
|
||||
ADCTRL_RESET = 1, // flush and reset state, e.g. after seeking
|
||||
};
|
||||
|
||||
#endif /* MPLAYER_AD_H */
|
||||
+76
-99
@@ -27,32 +27,28 @@
|
||||
#include <libavutil/intreadwrite.h>
|
||||
|
||||
#include "mpv_talloc.h"
|
||||
#include "audio/aframe.h"
|
||||
#include "audio/fmt-conversion.h"
|
||||
|
||||
#include "config.h"
|
||||
#include "common/av_common.h"
|
||||
#include "common/codecs.h"
|
||||
#include "common/global.h"
|
||||
#include "common/msg.h"
|
||||
#include "demux/packet.h"
|
||||
#include "demux/stheader.h"
|
||||
#include "filters/f_decoder_wrapper.h"
|
||||
#include "filters/filter_internal.h"
|
||||
#include "options/m_config.h"
|
||||
#include "options/options.h"
|
||||
|
||||
#include "ad.h"
|
||||
#include "audio/fmt-conversion.h"
|
||||
|
||||
struct priv {
|
||||
AVCodecContext *avctx;
|
||||
AVFrame *avframe;
|
||||
struct mp_chmap force_channel_map;
|
||||
bool force_channel_map;
|
||||
uint32_t skip_samples, trim_samples;
|
||||
bool preroll_done;
|
||||
double next_pts;
|
||||
AVRational codec_timebase;
|
||||
struct lavc_state state;
|
||||
|
||||
struct mp_decoder public;
|
||||
};
|
||||
|
||||
static void uninit(struct dec_audio *da);
|
||||
|
||||
#define OPT_BASE_STRUCT struct ad_lavc_params
|
||||
struct ad_lavc_params {
|
||||
float ac3drc;
|
||||
@@ -63,39 +59,40 @@ struct ad_lavc_params {
|
||||
|
||||
const struct m_sub_options ad_lavc_conf = {
|
||||
.opts = (const m_option_t[]) {
|
||||
{"ac3drc", OPT_FLOAT(ac3drc), M_RANGE(0, 6)},
|
||||
{"downmix", OPT_FLAG(downmix)},
|
||||
{"threads", OPT_INT(threads), M_RANGE(0, 16)},
|
||||
{"o", OPT_KEYVALUELIST(avopts)},
|
||||
OPT_FLOATRANGE("ac3drc", ac3drc, 0, 0, 6),
|
||||
OPT_FLAG("downmix", downmix, 0),
|
||||
OPT_INTRANGE("threads", threads, 0, 0, 16),
|
||||
OPT_KEYVALUELIST("o", avopts, 0),
|
||||
{0}
|
||||
},
|
||||
.size = sizeof(struct ad_lavc_params),
|
||||
.defaults = &(const struct ad_lavc_params){
|
||||
.ac3drc = 0,
|
||||
.downmix = 0,
|
||||
.downmix = 1,
|
||||
.threads = 1,
|
||||
},
|
||||
};
|
||||
|
||||
static bool init(struct mp_filter *da, struct mp_codec_params *codec,
|
||||
const char *decoder)
|
||||
static int init(struct dec_audio *da, const char *decoder)
|
||||
{
|
||||
struct priv *ctx = da->priv;
|
||||
struct MPOpts *mpopts = mp_get_config_group(ctx, da->global, &mp_opt_root);
|
||||
struct ad_lavc_params *opts =
|
||||
mp_get_config_group(ctx, da->global, &ad_lavc_conf);
|
||||
struct MPOpts *mpopts = da->opts;
|
||||
struct ad_lavc_params *opts = mpopts->ad_lavc_params;
|
||||
AVCodecContext *lavc_context;
|
||||
AVCodec *lavc_codec;
|
||||
struct mp_codec_params *c = da->codec;
|
||||
|
||||
ctx->codec_timebase = mp_get_codec_timebase(codec);
|
||||
struct priv *ctx = talloc_zero(NULL, struct priv);
|
||||
da->priv = ctx;
|
||||
|
||||
if (codec->force_channels)
|
||||
ctx->force_channel_map = codec->channels;
|
||||
ctx->codec_timebase = mp_get_codec_timebase(da->codec);
|
||||
|
||||
ctx->force_channel_map = c->force_channels;
|
||||
|
||||
lavc_codec = avcodec_find_decoder_by_name(decoder);
|
||||
if (!lavc_codec) {
|
||||
MP_ERR(da, "Cannot find codec '%s' in libavcodec...\n", decoder);
|
||||
return false;
|
||||
uninit(da);
|
||||
return 0;
|
||||
}
|
||||
|
||||
lavc_context = avcodec_alloc_context3(lavc_codec);
|
||||
@@ -103,7 +100,10 @@ static bool init(struct mp_filter *da, struct mp_codec_params *codec,
|
||||
ctx->avframe = av_frame_alloc();
|
||||
lavc_context->codec_type = AVMEDIA_TYPE_AUDIO;
|
||||
lavc_context->codec_id = lavc_codec->id;
|
||||
|
||||
#if LIBAVCODEC_VERSION_MICRO >= 100
|
||||
lavc_context->pkt_timebase = ctx->codec_timebase;
|
||||
#endif
|
||||
|
||||
if (opts->downmix && mpopts->audio_output_channels.num_chmaps == 1) {
|
||||
lavc_context->request_channel_layout =
|
||||
@@ -114,14 +114,17 @@ static bool init(struct mp_filter *da, struct mp_codec_params *codec,
|
||||
av_opt_set_double(lavc_context, "drc_scale", opts->ac3drc,
|
||||
AV_OPT_SEARCH_CHILDREN);
|
||||
|
||||
#if LIBAVCODEC_VERSION_MICRO >= 100
|
||||
// Let decoder add AV_FRAME_DATA_SKIP_SAMPLES.
|
||||
av_opt_set(lavc_context, "flags2", "+skip_manual", AV_OPT_SEARCH_CHILDREN);
|
||||
#endif
|
||||
|
||||
mp_set_avopts(da->log, lavc_context, opts->avopts);
|
||||
|
||||
if (mp_set_avctx_codec_headers(lavc_context, codec) < 0) {
|
||||
if (mp_set_avctx_codec_headers(lavc_context, c) < 0) {
|
||||
MP_ERR(da, "Could not set decoder parameters.\n");
|
||||
return false;
|
||||
uninit(da);
|
||||
return 0;
|
||||
}
|
||||
|
||||
mp_set_avcodec_threads(da->log, lavc_context, opts->threads);
|
||||
@@ -129,35 +132,41 @@ static bool init(struct mp_filter *da, struct mp_codec_params *codec,
|
||||
/* open it */
|
||||
if (avcodec_open2(lavc_context, lavc_codec, NULL) < 0) {
|
||||
MP_ERR(da, "Could not open codec.\n");
|
||||
return false;
|
||||
uninit(da);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ctx->next_pts = MP_NOPTS_VALUE;
|
||||
|
||||
return true;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void destroy(struct mp_filter *da)
|
||||
static void uninit(struct dec_audio *da)
|
||||
{
|
||||
struct priv *ctx = da->priv;
|
||||
if (!ctx)
|
||||
return;
|
||||
|
||||
avcodec_free_context(&ctx->avctx);
|
||||
av_frame_free(&ctx->avframe);
|
||||
}
|
||||
|
||||
static void reset(struct mp_filter *da)
|
||||
static int control(struct dec_audio *da, int cmd, void *arg)
|
||||
{
|
||||
struct priv *ctx = da->priv;
|
||||
|
||||
avcodec_flush_buffers(ctx->avctx);
|
||||
ctx->skip_samples = 0;
|
||||
ctx->trim_samples = 0;
|
||||
ctx->preroll_done = false;
|
||||
ctx->next_pts = MP_NOPTS_VALUE;
|
||||
ctx->state = (struct lavc_state){0};
|
||||
switch (cmd) {
|
||||
case ADCTRL_RESET:
|
||||
avcodec_flush_buffers(ctx->avctx);
|
||||
ctx->skip_samples = 0;
|
||||
ctx->trim_samples = 0;
|
||||
ctx->preroll_done = false;
|
||||
ctx->next_pts = MP_NOPTS_VALUE;
|
||||
return CONTROL_TRUE;
|
||||
}
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
|
||||
static int send_packet(struct mp_filter *da, struct demux_packet *mpkt)
|
||||
static bool send_packet(struct dec_audio *da, struct demux_packet *mpkt)
|
||||
{
|
||||
struct priv *priv = da->priv;
|
||||
AVCodecContext *avctx = priv->avctx;
|
||||
@@ -172,12 +181,16 @@ static int send_packet(struct mp_filter *da, struct demux_packet *mpkt)
|
||||
mp_set_av_packet(&pkt, mpkt, &priv->codec_timebase);
|
||||
|
||||
int ret = avcodec_send_packet(avctx, mpkt ? &pkt : NULL);
|
||||
|
||||
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
|
||||
return false;
|
||||
|
||||
if (ret < 0)
|
||||
MP_ERR(da, "Error decoding audio.\n");
|
||||
return ret;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int receive_frame(struct mp_filter *da, struct mp_frame *out)
|
||||
static bool receive_frame(struct dec_audio *da, struct mp_aframe **out)
|
||||
{
|
||||
struct priv *priv = da->priv;
|
||||
AVCodecContext *avctx = priv->avctx;
|
||||
@@ -187,29 +200,28 @@ static int receive_frame(struct mp_filter *da, struct mp_frame *out)
|
||||
if (ret == AVERROR_EOF) {
|
||||
// If flushing was initialized earlier and has ended now, make it start
|
||||
// over in case we get new packets at some point in the future.
|
||||
// (Dont' reset the filter itself, we want to keep other state.)
|
||||
avcodec_flush_buffers(priv->avctx);
|
||||
return ret;
|
||||
control(da, ADCTRL_RESET, NULL);
|
||||
return false;
|
||||
} else if (ret < 0 && ret != AVERROR(EAGAIN)) {
|
||||
MP_ERR(da, "Error decoding audio.\n");
|
||||
}
|
||||
|
||||
#if LIBAVCODEC_VERSION_MICRO >= 100
|
||||
if (priv->avframe->flags & AV_FRAME_FLAG_DISCARD)
|
||||
av_frame_unref(priv->avframe);
|
||||
#endif
|
||||
|
||||
if (!priv->avframe->buf[0])
|
||||
return ret;
|
||||
return true;
|
||||
|
||||
double out_pts = mp_pts_from_av(priv->avframe->pts, &priv->codec_timebase);
|
||||
|
||||
struct mp_aframe *mpframe = mp_aframe_from_avframe(priv->avframe);
|
||||
if (!mpframe) {
|
||||
MP_ERR(da, "Converting libavcodec frame to mpv frame failed.\n");
|
||||
return ret;
|
||||
}
|
||||
if (!mpframe)
|
||||
return true;
|
||||
|
||||
if (priv->force_channel_map.num)
|
||||
mp_aframe_set_chmap(mpframe, &priv->force_channel_map);
|
||||
if (priv->force_channel_map)
|
||||
mp_aframe_set_chmap(mpframe, &da->codec->channels);
|
||||
|
||||
if (out_pts == MP_NOPTS_VALUE)
|
||||
out_pts = priv->next_pts;
|
||||
@@ -217,6 +229,7 @@ static int receive_frame(struct mp_filter *da, struct mp_frame *out)
|
||||
|
||||
priv->next_pts = mp_aframe_end_pts(mpframe);
|
||||
|
||||
#if LIBAVCODEC_VERSION_MICRO >= 100
|
||||
AVFrameSideData *sd =
|
||||
av_frame_get_side_data(priv->avframe, AV_FRAME_DATA_SKIP_SAMPLES);
|
||||
if (sd && sd->size >= 10) {
|
||||
@@ -224,6 +237,7 @@ static int receive_frame(struct mp_filter *da, struct mp_frame *out)
|
||||
priv->skip_samples += AV_RL32(d + 0);
|
||||
priv->trim_samples += AV_RL32(d + 4);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!priv->preroll_done) {
|
||||
// Skip only if this isn't already handled by AV_FRAME_DATA_SKIP_SAMPLES.
|
||||
@@ -243,53 +257,11 @@ static int receive_frame(struct mp_filter *da, struct mp_frame *out)
|
||||
priv->trim_samples -= trim;
|
||||
}
|
||||
|
||||
if (mp_aframe_get_size(mpframe) > 0) {
|
||||
*out = MAKE_FRAME(MP_FRAME_AUDIO, mpframe);
|
||||
} else {
|
||||
talloc_free(mpframe);
|
||||
}
|
||||
*out = mpframe;
|
||||
|
||||
av_frame_unref(priv->avframe);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void process(struct mp_filter *ad)
|
||||
{
|
||||
struct priv *priv = ad->priv;
|
||||
|
||||
lavc_process(ad, &priv->state, send_packet, receive_frame);
|
||||
}
|
||||
|
||||
static const struct mp_filter_info ad_lavc_filter = {
|
||||
.name = "ad_lavc",
|
||||
.priv_size = sizeof(struct priv),
|
||||
.process = process,
|
||||
.reset = reset,
|
||||
.destroy = destroy,
|
||||
};
|
||||
|
||||
static struct mp_decoder *create(struct mp_filter *parent,
|
||||
struct mp_codec_params *codec,
|
||||
const char *decoder)
|
||||
{
|
||||
struct mp_filter *da = mp_filter_create(parent, &ad_lavc_filter);
|
||||
if (!da)
|
||||
return NULL;
|
||||
|
||||
mp_filter_add_pin(da, MP_PIN_IN, "in");
|
||||
mp_filter_add_pin(da, MP_PIN_OUT, "out");
|
||||
|
||||
da->log = mp_log_new(da, parent->log, NULL);
|
||||
|
||||
struct priv *priv = da->priv;
|
||||
priv->public.f = da;
|
||||
|
||||
if (!init(da, codec, decoder)) {
|
||||
talloc_free(da);
|
||||
return NULL;
|
||||
}
|
||||
return &priv->public;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void add_decoders(struct mp_decoder_list *list)
|
||||
@@ -297,7 +269,12 @@ static void add_decoders(struct mp_decoder_list *list)
|
||||
mp_add_lavc_decoders(list, AVMEDIA_TYPE_AUDIO);
|
||||
}
|
||||
|
||||
const struct mp_decoder_fns ad_lavc = {
|
||||
.create = create,
|
||||
const struct ad_functions ad_lavc = {
|
||||
.name = "lavc",
|
||||
.add_decoders = add_decoders,
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.control = control,
|
||||
.send_packet = send_packet,
|
||||
.receive_frame = receive_frame,
|
||||
};
|
||||
+82
-97
@@ -24,16 +24,11 @@
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavutil/opt.h>
|
||||
|
||||
#include "audio/aframe.h"
|
||||
#include "audio/format.h"
|
||||
#include "common/av_common.h"
|
||||
#include "common/codecs.h"
|
||||
#include "config.h"
|
||||
#include "common/msg.h"
|
||||
#include "demux/packet.h"
|
||||
#include "demux/stheader.h"
|
||||
#include "filters/f_decoder_wrapper.h"
|
||||
#include "filters/filter_internal.h"
|
||||
#include "common/av_common.h"
|
||||
#include "options/options.h"
|
||||
#include "ad.h"
|
||||
|
||||
#define OUTBUF_SIZE 65536
|
||||
|
||||
@@ -48,8 +43,8 @@ struct spdifContext {
|
||||
struct mp_aframe *fmt;
|
||||
int sstride;
|
||||
struct mp_aframe_pool *pool;
|
||||
|
||||
struct mp_decoder public;
|
||||
bool got_eof;
|
||||
struct demux_packet *queued_packet;
|
||||
};
|
||||
|
||||
static int write_packet(void *p, uint8_t *buf, int buf_size)
|
||||
@@ -67,8 +62,7 @@ static int write_packet(void *p, uint8_t *buf, int buf_size)
|
||||
return buf_size;
|
||||
}
|
||||
|
||||
// (called on both filter destruction _and_ if lavf fails to init)
|
||||
static void destroy(struct mp_filter *da)
|
||||
static void uninit(struct dec_audio *da)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
AVFormatContext *lavf_ctx = spdif_ctx->lavf_ctx;
|
||||
@@ -80,11 +74,27 @@ static void destroy(struct mp_filter *da)
|
||||
av_freep(&lavf_ctx->pb->buffer);
|
||||
av_freep(&lavf_ctx->pb);
|
||||
avformat_free_context(lavf_ctx);
|
||||
talloc_free(spdif_ctx->queued_packet);
|
||||
spdif_ctx->lavf_ctx = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void determine_codec_params(struct mp_filter *da, AVPacket *pkt,
|
||||
static int init(struct dec_audio *da, const char *decoder)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = talloc_zero(NULL, struct spdifContext);
|
||||
da->priv = spdif_ctx;
|
||||
spdif_ctx->log = da->log;
|
||||
spdif_ctx->use_dts_hd = da->opts->dtshd;
|
||||
spdif_ctx->pool = mp_aframe_pool_create(spdif_ctx);
|
||||
|
||||
if (strcmp(decoder, "spdif_dts_hd") == 0)
|
||||
spdif_ctx->use_dts_hd = true;
|
||||
|
||||
spdif_ctx->codec_id = mp_codec_to_av_codec_id(da->codec->codec);
|
||||
return spdif_ctx->codec_id != AV_CODEC_ID_NONE;
|
||||
}
|
||||
|
||||
static void determine_codec_params(struct dec_audio *da, AVPacket *pkt,
|
||||
int *out_profile, int *out_rate)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
@@ -147,7 +157,7 @@ done:
|
||||
MP_WARN(da, "Failed to parse codec profile.\n");
|
||||
}
|
||||
|
||||
static int init_filter(struct mp_filter *da, AVPacket *pkt)
|
||||
static int init_filter(struct dec_audio *da, AVPacket *pkt)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
|
||||
@@ -176,8 +186,10 @@ static int init_filter(struct mp_filter *da, AVPacket *pkt)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
// Request minimal buffering
|
||||
// Request minimal buffering (not available on Libav)
|
||||
#if LIBAVFORMAT_VERSION_MICRO >= 100
|
||||
lavf_ctx->pb->direct = 1;
|
||||
#endif
|
||||
|
||||
AVStream *stream = avformat_new_stream(lavf_ctx, 0);
|
||||
if (!stream)
|
||||
@@ -206,21 +218,13 @@ static int init_filter(struct mp_filter *da, AVPacket *pkt)
|
||||
break;
|
||||
case AV_CODEC_ID_DTS: {
|
||||
bool is_hd = profile == FF_PROFILE_DTS_HD_HRA ||
|
||||
profile == FF_PROFILE_DTS_HD_MA ||
|
||||
profile == FF_PROFILE_DTS_HD_MA ||
|
||||
profile == FF_PROFILE_UNKNOWN;
|
||||
|
||||
// Apparently, DTS-HD over SPDIF is specified to be 7.1 (8 channels)
|
||||
// for DTS-HD MA, and stereo (2 channels) for DTS-HD HRA. The bit
|
||||
// streaming rate as well as the signaled channel count are defined
|
||||
// based on this value.
|
||||
int dts_hd_spdif_channel_count = profile == FF_PROFILE_DTS_HD_HRA ?
|
||||
2 : 8;
|
||||
if (spdif_ctx->use_dts_hd && is_hd) {
|
||||
av_dict_set_int(&format_opts, "dtshd_rate",
|
||||
dts_hd_spdif_channel_count * 96000, 0);
|
||||
av_dict_set(&format_opts, "dtshd_rate", "768000", 0); // 4*192000
|
||||
sample_format = AF_FORMAT_S_DTSHD;
|
||||
samplerate = 192000;
|
||||
num_channels = dts_hd_spdif_channel_count;
|
||||
num_channels = 2*4;
|
||||
} else {
|
||||
sample_format = AF_FORMAT_S_DTS;
|
||||
samplerate = 48000;
|
||||
@@ -267,36 +271,39 @@ static int init_filter(struct mp_filter *da, AVPacket *pkt)
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
destroy(da);
|
||||
mp_filter_internal_mark_failed(da);
|
||||
uninit(da);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void process(struct mp_filter *da)
|
||||
|
||||
static bool send_packet(struct dec_audio *da, struct demux_packet *mpkt)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
|
||||
if (!mp_pin_can_transfer_data(da->ppins[1], da->ppins[0]))
|
||||
return;
|
||||
if (spdif_ctx->queued_packet || spdif_ctx->got_eof)
|
||||
return false;
|
||||
|
||||
struct mp_frame inframe = mp_pin_out_read(da->ppins[0]);
|
||||
if (inframe.type == MP_FRAME_EOF) {
|
||||
mp_pin_in_write(da->ppins[1], inframe);
|
||||
return;
|
||||
} else if (inframe.type != MP_FRAME_PACKET) {
|
||||
if (inframe.type) {
|
||||
MP_ERR(da, "unknown frame type\n");
|
||||
mp_filter_internal_mark_failed(da);
|
||||
}
|
||||
return;
|
||||
spdif_ctx->queued_packet = mpkt ? demux_copy_packet(mpkt) : NULL;
|
||||
spdif_ctx->got_eof = !mpkt;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool receive_frame(struct dec_audio *da, struct mp_aframe **out)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
|
||||
if (spdif_ctx->got_eof) {
|
||||
spdif_ctx->got_eof = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
struct demux_packet *mpkt = inframe.data;
|
||||
struct mp_aframe *out = NULL;
|
||||
double pts = mpkt->pts;
|
||||
if (!spdif_ctx->queued_packet)
|
||||
return true;
|
||||
|
||||
double pts = spdif_ctx->queued_packet->pts;
|
||||
|
||||
AVPacket pkt;
|
||||
mp_set_av_packet(&pkt, mpkt, NULL);
|
||||
mp_set_av_packet(&pkt, spdif_ctx->queued_packet, NULL);
|
||||
pkt.pts = pkt.dts = 0;
|
||||
if (!spdif_ctx->lavf_ctx) {
|
||||
if (init_filter(da, &pkt) < 0)
|
||||
@@ -310,29 +317,39 @@ static void process(struct mp_filter *da)
|
||||
goto done;
|
||||
}
|
||||
|
||||
out = mp_aframe_new_ref(spdif_ctx->fmt);
|
||||
*out = mp_aframe_new_ref(spdif_ctx->fmt);
|
||||
int samples = spdif_ctx->out_buffer_len / spdif_ctx->sstride;
|
||||
if (mp_aframe_pool_allocate(spdif_ctx->pool, out, samples) < 0) {
|
||||
TA_FREEP(&out);
|
||||
if (mp_aframe_pool_allocate(spdif_ctx->pool, *out, samples) < 0) {
|
||||
TA_FREEP(out);
|
||||
goto done;
|
||||
}
|
||||
|
||||
uint8_t **data = mp_aframe_get_data_rw(out);
|
||||
uint8_t **data = mp_aframe_get_data_rw(*out);
|
||||
if (!data) {
|
||||
TA_FREEP(&out);
|
||||
TA_FREEP(out);
|
||||
goto done;
|
||||
}
|
||||
|
||||
memcpy(data[0], spdif_ctx->out_buffer, spdif_ctx->out_buffer_len);
|
||||
mp_aframe_set_pts(out, pts);
|
||||
mp_aframe_set_pts(*out, pts);
|
||||
|
||||
done:
|
||||
talloc_free(mpkt);
|
||||
if (out) {
|
||||
mp_pin_in_write(da->ppins[1], MAKE_FRAME(MP_FRAME_AUDIO, out));
|
||||
} else {
|
||||
mp_filter_internal_mark_failed(da);
|
||||
talloc_free(spdif_ctx->queued_packet);
|
||||
spdif_ctx->queued_packet = NULL;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int control(struct dec_audio *da, int cmd, void *arg)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
switch (cmd) {
|
||||
case ADCTRL_RESET:
|
||||
talloc_free(spdif_ctx->queued_packet);
|
||||
spdif_ctx->queued_packet = NULL;
|
||||
spdif_ctx->got_eof = false;
|
||||
return CONTROL_TRUE;
|
||||
}
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
|
||||
static const int codecs[] = {
|
||||
@@ -384,49 +401,17 @@ struct mp_decoder_list *select_spdif_codec(const char *codec, const char *pref)
|
||||
const char *suffix_name = dts_hd_allowed ? "dts_hd" : codec;
|
||||
char name[80];
|
||||
snprintf(name, sizeof(name), "spdif_%s", suffix_name);
|
||||
mp_add_decoder(list, codec, name,
|
||||
mp_add_decoder(list, "spdif", codec, name,
|
||||
"libavformat/spdifenc audio pass-through decoder");
|
||||
return list;
|
||||
}
|
||||
|
||||
static const struct mp_filter_info ad_spdif_filter = {
|
||||
.name = "ad_spdif",
|
||||
.priv_size = sizeof(struct spdifContext),
|
||||
.process = process,
|
||||
.destroy = destroy,
|
||||
};
|
||||
|
||||
static struct mp_decoder *create(struct mp_filter *parent,
|
||||
struct mp_codec_params *codec,
|
||||
const char *decoder)
|
||||
{
|
||||
struct mp_filter *da = mp_filter_create(parent, &ad_spdif_filter);
|
||||
if (!da)
|
||||
return NULL;
|
||||
|
||||
mp_filter_add_pin(da, MP_PIN_IN, "in");
|
||||
mp_filter_add_pin(da, MP_PIN_OUT, "out");
|
||||
|
||||
da->log = mp_log_new(da, parent->log, NULL);
|
||||
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
spdif_ctx->log = da->log;
|
||||
spdif_ctx->pool = mp_aframe_pool_create(spdif_ctx);
|
||||
spdif_ctx->public.f = da;
|
||||
|
||||
if (strcmp(decoder, "spdif_dts_hd") == 0)
|
||||
spdif_ctx->use_dts_hd = true;
|
||||
|
||||
spdif_ctx->codec_id = mp_codec_to_av_codec_id(codec->codec);
|
||||
|
||||
|
||||
if (spdif_ctx->codec_id == AV_CODEC_ID_NONE) {
|
||||
talloc_free(da);
|
||||
return NULL;
|
||||
}
|
||||
return &spdif_ctx->public;
|
||||
}
|
||||
|
||||
const struct mp_decoder_fns ad_spdif = {
|
||||
.create = create,
|
||||
const struct ad_functions ad_spdif = {
|
||||
.name = "spdif",
|
||||
.add_decoders = NULL,
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.control = control,
|
||||
.send_packet = send_packet,
|
||||
.receive_frame = receive_frame,
|
||||
};
|
||||
@@ -0,0 +1,290 @@
|
||||
/*
|
||||
* 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 "options/options.h"
|
||||
|
||||
#include "stream/stream.h"
|
||||
#include "demux/demux.h"
|
||||
|
||||
#include "demux/stheader.h"
|
||||
|
||||
#include "dec_audio.h"
|
||||
#include "ad.h"
|
||||
#include "audio/format.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);
|
||||
}
|
||||
|
||||
static bool is_new_segment(struct dec_audio *da, struct demux_packet *p)
|
||||
{
|
||||
return p->segmented &&
|
||||
(p->start != da->start || p->end != da->end || p->codec != da->codec);
|
||||
}
|
||||
|
||||
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 && is_new_segment(da, da->packet)) {
|
||||
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;
|
||||
|
||||
da->packet = new_segment;
|
||||
da->current_state = DATA_AGAIN;
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch an audio frame decoded with audio_work(). Returns one of:
|
||||
// DATA_OK: *out_frame is set to a new image
|
||||
// DATA_WAIT: waiting for demuxer; will receive a wakeup signal
|
||||
// DATA_EOF: end of file, no more frames to be expected
|
||||
// DATA_AGAIN: dropped frame or something similar
|
||||
int audio_get_frame(struct dec_audio *da, struct mp_aframe **out_frame)
|
||||
{
|
||||
*out_frame = NULL;
|
||||
if (da->current_frame) {
|
||||
*out_frame = da->current_frame;
|
||||
da->current_frame = NULL;
|
||||
return DATA_OK;
|
||||
}
|
||||
if (da->current_state == DATA_OK)
|
||||
return DATA_AGAIN;
|
||||
return da->current_state;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MPLAYER_DEC_AUDIO_H
|
||||
#define MPLAYER_DEC_AUDIO_H
|
||||
|
||||
#include "audio/chmap.h"
|
||||
#include "audio/aframe.h"
|
||||
#include "demux/demux.h"
|
||||
#include "demux/stheader.h"
|
||||
|
||||
struct mp_decoder_list;
|
||||
|
||||
struct dec_audio {
|
||||
struct mp_log *log;
|
||||
struct MPOpts *opts;
|
||||
struct mpv_global *global;
|
||||
const struct ad_functions *ad_driver;
|
||||
struct sh_stream *header;
|
||||
struct mp_codec_params *codec;
|
||||
char *decoder_desc;
|
||||
|
||||
bool try_spdif;
|
||||
|
||||
struct mp_recorder_sink *recorder_sink;
|
||||
|
||||
// For free use by the ad_driver
|
||||
void *priv;
|
||||
|
||||
// Strictly internal (dec_audio.c).
|
||||
|
||||
double pts; // endpts of previous frame
|
||||
double start, end;
|
||||
struct demux_packet *packet;
|
||||
struct demux_packet *new_segment;
|
||||
struct mp_aframe *current_frame;
|
||||
int current_state;
|
||||
};
|
||||
|
||||
struct mp_decoder_list *audio_decoder_list(void);
|
||||
int audio_init_best_codec(struct dec_audio *d_audio);
|
||||
void audio_uninit(struct dec_audio *d_audio);
|
||||
|
||||
void audio_work(struct dec_audio *d_audio);
|
||||
int audio_get_frame(struct dec_audio *d_audio, struct mp_aframe **out_frame);
|
||||
|
||||
void audio_reset_decoding(struct dec_audio *d_audio);
|
||||
|
||||
// ad_spdif.c
|
||||
struct mp_decoder_list *select_spdif_codec(const char *codec, const char *pref);
|
||||
|
||||
#endif /* MPLAYER_DEC_AUDIO_H */
|
||||
@@ -0,0 +1,821 @@
|
||||
/*
|
||||
* 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_format;
|
||||
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_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_format,
|
||||
&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,
|
||||
.global = s->global,
|
||||
.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;
|
||||
s->global = global;
|
||||
return s;
|
||||
}
|
||||
|
||||
void af_destroy(struct af_stream *s)
|
||||
{
|
||||
af_uninit(s);
|
||||
talloc_free(s);
|
||||
}
|
||||
|
||||
/* Initialize the stream "s". This function creates a new filter list
|
||||
if necessary according to the values set in input and output. Input
|
||||
and output should contain the format of the current movie and the
|
||||
format of the preferred output respectively. The function is
|
||||
reentrant i.e. if called with an already initialized stream the
|
||||
stream will be reinitialized.
|
||||
If one of the preferred output parameters is 0 the one that needs
|
||||
no conversion is used (i.e. the output format in the last filter).
|
||||
The return value is 0 if success and -1 if failure */
|
||||
int af_init(struct af_stream *s)
|
||||
{
|
||||
// Precaution in case caller is misbehaving
|
||||
mp_audio_set_null_data(&s->input);
|
||||
mp_audio_set_null_data(&s->output);
|
||||
|
||||
// Check if this is the first call
|
||||
if (s->first->next == s->last) {
|
||||
// Add all filters in the list (if there are any)
|
||||
struct m_obj_settings *list = s->opts->af_settings;
|
||||
for (int i = 0; list && list[i].name; i++) {
|
||||
if (!list[i].enabled)
|
||||
continue;
|
||||
struct af_instance *af =
|
||||
af_prepend(s, s->last, list[i].name, list[i].attribs);
|
||||
if (!af) {
|
||||
af_uninit(s);
|
||||
s->initialized = -1;
|
||||
return -1;
|
||||
}
|
||||
af->label = talloc_strdup(af, list[i].label);
|
||||
}
|
||||
}
|
||||
|
||||
if (af_reinit(s) != AF_OK) {
|
||||
// Something is stuffed audio out will not work
|
||||
MP_ERR(s, "Could not create audio filter chain.\n");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Add filter during execution. This function adds the filter "name"
|
||||
to the stream s. The filter will be inserted somewhere nice in the
|
||||
list of filters. The return value is a pointer to the new filter,
|
||||
If the filter couldn't be added the return value is NULL. */
|
||||
struct af_instance *af_add(struct af_stream *s, char *name, char *label,
|
||||
char **args)
|
||||
{
|
||||
assert(label);
|
||||
|
||||
if (af_find_by_label(s, label))
|
||||
return NULL;
|
||||
|
||||
struct af_instance *new = af_prepend(s, s->last, name, args);
|
||||
if (!new)
|
||||
return NULL;
|
||||
new->label = talloc_strdup(new, label);
|
||||
|
||||
// Reinitialize the filter list
|
||||
if (af_reinit(s) != AF_OK) {
|
||||
af_remove_by_label(s, label);
|
||||
return NULL;
|
||||
}
|
||||
return af_find_by_label(s, label);
|
||||
}
|
||||
|
||||
struct af_instance *af_find_by_label(struct af_stream *s, char *label)
|
||||
{
|
||||
for (struct af_instance *af = s->first; af; af = af->next) {
|
||||
if (af->label && strcmp(af->label, label) == 0)
|
||||
return af;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Remove the first filter that matches this name. Return number of filters
|
||||
* removed (0, 1), or a negative error code if reinit after removing failed.
|
||||
*/
|
||||
int af_remove_by_label(struct af_stream *s, char *label)
|
||||
{
|
||||
struct af_instance *af = af_find_by_label(s, label);
|
||||
if (!af)
|
||||
return 0;
|
||||
af_remove(s, af);
|
||||
if (af_reinit(s) != AF_OK) {
|
||||
af_uninit(s);
|
||||
af_init(s);
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Calculate the total delay [seconds of output] caused by the filters */
|
||||
double af_calc_delay(struct af_stream *s)
|
||||
{
|
||||
struct af_instance *af = s->first;
|
||||
double delay = 0.0;
|
||||
while (af) {
|
||||
delay += af->delay;
|
||||
for (int n = 0; n < af->num_out_queued; n++)
|
||||
delay += af->out_queued[n]->samples / (double)af->data->rate;
|
||||
af = af->next;
|
||||
}
|
||||
return delay;
|
||||
}
|
||||
|
||||
/* Send control to all filters, starting with the last until one accepts the
|
||||
* command with AF_OK. Return the accepting filter. */
|
||||
struct af_instance *af_control_any_rev(struct af_stream *s, int cmd, void *arg)
|
||||
{
|
||||
int res = AF_UNKNOWN;
|
||||
struct af_instance *filt = s->last;
|
||||
while (filt) {
|
||||
res = filt->control(filt, cmd, arg);
|
||||
if (res == AF_OK)
|
||||
return filt;
|
||||
filt = filt->prev;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Send control to all filters. Never stop, even if a filter returns AF_OK. */
|
||||
void af_control_all(struct af_stream *s, int cmd, void *arg)
|
||||
{
|
||||
for (struct af_instance *af = s->first; af; af = af->next)
|
||||
af->control(af, cmd, arg);
|
||||
}
|
||||
|
||||
int af_control_by_label(struct af_stream *s, int cmd, void *arg, bstr label)
|
||||
{
|
||||
char *label_str = bstrdup0(NULL, label);
|
||||
struct af_instance *cur = af_find_by_label(s, label_str);
|
||||
talloc_free(label_str);
|
||||
if (cur) {
|
||||
return cur->control ? cur->control(cur, cmd, arg) : CONTROL_NA;
|
||||
} else {
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
int af_send_command(struct af_stream *s, char *label, char *cmd, char *arg)
|
||||
{
|
||||
char *args[2] = {cmd, arg};
|
||||
if (strcmp(label, "all") == 0) {
|
||||
af_control_all(s, AF_CONTROL_COMMAND, args);
|
||||
return 0;
|
||||
} else {
|
||||
return af_control_by_label(s, AF_CONTROL_COMMAND, args, bstr0(label));
|
||||
}
|
||||
}
|
||||
|
||||
// Used by filters to add a filtered frame to the output queue.
|
||||
// Ownership of frame is transferred from caller to the filter chain.
|
||||
void af_add_output_frame(struct af_instance *af, struct mp_audio *frame)
|
||||
{
|
||||
if (frame) {
|
||||
assert(mp_audio_config_equals(&af->fmt_out, frame));
|
||||
MP_TARRAY_APPEND(af, af->out_queued, af->num_out_queued, frame);
|
||||
}
|
||||
}
|
||||
|
||||
static bool af_has_output_frame(struct af_instance *af)
|
||||
{
|
||||
if (!af->num_out_queued && af->filter_out) {
|
||||
if (af->filter_out(af) < 0)
|
||||
MP_ERR(af, "Error filtering frame.\n");
|
||||
}
|
||||
return af->num_out_queued > 0;
|
||||
}
|
||||
|
||||
static struct mp_audio *af_dequeue_output_frame(struct af_instance *af)
|
||||
{
|
||||
struct mp_audio *res = NULL;
|
||||
if (af_has_output_frame(af)) {
|
||||
res = af->out_queued[0];
|
||||
MP_TARRAY_REMOVE_AT(af->out_queued, af->num_out_queued, 0);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static void read_remaining(struct af_instance *af)
|
||||
{
|
||||
int num_frames;
|
||||
do {
|
||||
num_frames = af->num_out_queued;
|
||||
if (!af->filter_out || af->filter_out(af) < 0)
|
||||
break;
|
||||
} while (num_frames != af->num_out_queued);
|
||||
}
|
||||
|
||||
static int af_do_filter(struct af_instance *af, struct mp_audio *frame)
|
||||
{
|
||||
if (frame)
|
||||
assert(mp_audio_config_equals(&af->fmt_in, frame));
|
||||
int r = af->filter_frame(af, frame);
|
||||
if (r < 0)
|
||||
MP_ERR(af, "Error filtering frame.\n");
|
||||
return r;
|
||||
}
|
||||
|
||||
// Input a frame into the filter chain. Ownership of frame is transferred.
|
||||
// Return >= 0 on success, < 0 on failure (even if output frames were produced)
|
||||
int af_filter_frame(struct af_stream *s, struct mp_audio *frame)
|
||||
{
|
||||
assert(frame);
|
||||
if (s->initialized < 1) {
|
||||
talloc_free(frame);
|
||||
return -1;
|
||||
}
|
||||
return af_do_filter(s->first, frame);
|
||||
}
|
||||
|
||||
// Output the next queued frame (if any) from the full filter chain.
|
||||
// The frame can be retrieved with af_read_output_frame().
|
||||
// eof: if set, assume there's no more input i.e. af_filter_frame() will
|
||||
// not be called (until reset) - flush all internally delayed frames
|
||||
// returns: -1: error, 0: no output, 1: output available
|
||||
int af_output_frame(struct af_stream *s, bool eof)
|
||||
{
|
||||
if (s->last->num_out_queued)
|
||||
return 1;
|
||||
if (s->initialized < 1)
|
||||
return -1;
|
||||
while (1) {
|
||||
struct af_instance *last = NULL;
|
||||
for (struct af_instance * cur = s->first; cur; cur = cur->next) {
|
||||
// Flush remaining frames on EOF, but do that only if the previous
|
||||
// filters have been flushed (i.e. they have no more output).
|
||||
if (eof && !last) {
|
||||
read_remaining(cur);
|
||||
int r = af_do_filter(cur, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
if (af_has_output_frame(cur))
|
||||
last = cur;
|
||||
}
|
||||
if (!last)
|
||||
return 0;
|
||||
if (!last->next)
|
||||
return 1;
|
||||
int r = af_do_filter(last->next, af_dequeue_output_frame(last));
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
struct mp_audio *af_read_output_frame(struct af_stream *s)
|
||||
{
|
||||
if (!s->last->num_out_queued)
|
||||
af_output_frame(s, false);
|
||||
return af_dequeue_output_frame(s->last);
|
||||
}
|
||||
|
||||
void af_unread_output_frame(struct af_stream *s, struct mp_audio *frame)
|
||||
{
|
||||
struct af_instance *af = s->last;
|
||||
MP_TARRAY_INSERT_AT(af, af->out_queued, af->num_out_queued, 0, frame);
|
||||
}
|
||||
|
||||
// Make sure the caller can change data referenced by the frame.
|
||||
// Return negative error code on failure (i.e. you can't write).
|
||||
int af_make_writeable(struct af_instance *af, struct mp_audio *frame)
|
||||
{
|
||||
return mp_audio_pool_make_writeable(af->out_pool, frame);
|
||||
}
|
||||
|
||||
void af_seek_reset(struct af_stream *s)
|
||||
{
|
||||
af_control_all(s, AF_CONTROL_RESET, NULL);
|
||||
af_chain_forget_frames(s);
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MPLAYER_AF_H
|
||||
#define MPLAYER_AF_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "config.h"
|
||||
#if !(HAVE_LIBAF && HAVE_GPL)
|
||||
#error "libaf/GPL disabled"
|
||||
#endif
|
||||
|
||||
#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 mpv_global *global;
|
||||
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 mpv_global *global;
|
||||
};
|
||||
|
||||
// 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_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 */
|
||||
@@ -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,
|
||||
};
|
||||
+87
-96
@@ -15,14 +15,18 @@
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "audio/aframe.h"
|
||||
#include "audio/format.h"
|
||||
#include "filters/f_autoconvert.h"
|
||||
#include "filters/filter_internal.h"
|
||||
#include "filters/user_filters.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <libavutil/common.h>
|
||||
|
||||
#include "options/m_option.h"
|
||||
|
||||
struct f_opts {
|
||||
#include "audio/format.h"
|
||||
#include "af.h"
|
||||
|
||||
struct priv {
|
||||
struct m_config *config;
|
||||
|
||||
int in_format;
|
||||
int in_srate;
|
||||
struct m_channels in_channels;
|
||||
@@ -33,111 +37,98 @@ struct f_opts {
|
||||
int fail;
|
||||
};
|
||||
|
||||
struct priv {
|
||||
struct f_opts *opts;
|
||||
struct mp_pin *in_pin;
|
||||
};
|
||||
|
||||
static void process(struct mp_filter *f)
|
||||
static void force_in_params(struct af_instance *af, struct mp_audio *in)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
struct priv *priv = af->priv;
|
||||
|
||||
if (!mp_pin_can_transfer_data(f->ppins[1], p->in_pin))
|
||||
return;
|
||||
if (priv->in_format != AF_FORMAT_UNKNOWN)
|
||||
mp_audio_set_format(in, priv->in_format);
|
||||
|
||||
struct mp_frame frame = mp_pin_out_read(p->in_pin);
|
||||
if (priv->in_channels.num_chmaps > 0)
|
||||
mp_audio_set_channels(in, &priv->in_channels.chmaps[0]);
|
||||
|
||||
if (p->opts->fail) {
|
||||
MP_ERR(f, "Failing on purpose.\n");
|
||||
goto error;
|
||||
}
|
||||
if (priv->in_srate)
|
||||
in->rate = priv->in_srate;
|
||||
}
|
||||
|
||||
if (frame.type == MP_FRAME_EOF) {
|
||||
mp_pin_in_write(f->ppins[1], frame);
|
||||
return;
|
||||
}
|
||||
static void force_out_params(struct af_instance *af, struct mp_audio *out)
|
||||
{
|
||||
struct priv *priv = af->priv;
|
||||
|
||||
if (frame.type != MP_FRAME_AUDIO) {
|
||||
MP_ERR(f, "audio frame expected\n");
|
||||
goto error;
|
||||
}
|
||||
if (priv->out_format != AF_FORMAT_UNKNOWN)
|
||||
mp_audio_set_format(out, priv->out_format);
|
||||
|
||||
struct mp_aframe *in = frame.data;
|
||||
if (priv->out_channels.num_chmaps > 0)
|
||||
mp_audio_set_channels(out, &priv->out_channels.chmaps[0]);
|
||||
|
||||
if (p->opts->out_channels.num_chmaps > 0) {
|
||||
if (!mp_aframe_set_chmap(in, &p->opts->out_channels.chmaps[0])) {
|
||||
MP_ERR(f, "could not force output channels\n");
|
||||
goto error;
|
||||
if (priv->out_srate)
|
||||
out->rate = priv->out_srate;
|
||||
}
|
||||
|
||||
static int control(struct af_instance *af, int cmd, void *arg)
|
||||
{
|
||||
struct priv *priv = af->priv;
|
||||
|
||||
switch (cmd) {
|
||||
case AF_CONTROL_REINIT: {
|
||||
struct mp_audio *in = arg;
|
||||
struct mp_audio orig_in = *in;
|
||||
struct mp_audio *out = af->data;
|
||||
|
||||
force_in_params(af, in);
|
||||
mp_audio_copy_config(out, in);
|
||||
force_out_params(af, out);
|
||||
|
||||
if (in->nch != out->nch || in->bps != out->bps) {
|
||||
MP_ERR(af, "Forced input/output formats are incompatible.\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
if (priv->fail) {
|
||||
MP_ERR(af, "Failing on purpose.\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
return mp_audio_config_equals(in, &orig_in) ? AF_OK : AF_FALSE;
|
||||
}
|
||||
|
||||
if (p->opts->out_srate)
|
||||
mp_aframe_set_rate(in, p->opts->out_srate);
|
||||
|
||||
mp_pin_in_write(f->ppins[1], frame);
|
||||
return;
|
||||
|
||||
error:
|
||||
mp_frame_unref(&frame);
|
||||
mp_filter_internal_mark_failed(f);
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static const struct mp_filter_info af_format_filter = {
|
||||
.name = "format",
|
||||
.priv_size = sizeof(struct priv),
|
||||
.process = process,
|
||||
};
|
||||
|
||||
static struct mp_filter *af_format_create(struct mp_filter *parent,
|
||||
void *options)
|
||||
static int filter(struct af_instance *af, struct mp_audio *data)
|
||||
{
|
||||
struct mp_filter *f = mp_filter_create(parent, &af_format_filter);
|
||||
if (!f) {
|
||||
talloc_free(options);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct priv *p = f->priv;
|
||||
p->opts = talloc_steal(p, options);
|
||||
|
||||
mp_filter_add_pin(f, MP_PIN_IN, "in");
|
||||
mp_filter_add_pin(f, MP_PIN_OUT, "out");
|
||||
|
||||
struct mp_autoconvert *conv = mp_autoconvert_create(f);
|
||||
if (!conv)
|
||||
abort();
|
||||
|
||||
if (p->opts->in_format)
|
||||
mp_autoconvert_add_afmt(conv, p->opts->in_format);
|
||||
if (p->opts->in_srate)
|
||||
mp_autoconvert_add_srate(conv, p->opts->in_srate);
|
||||
if (p->opts->in_channels.num_chmaps > 0)
|
||||
mp_autoconvert_add_chmap(conv, &p->opts->in_channels.chmaps[0]);
|
||||
|
||||
mp_pin_connect(conv->f->pins[0], f->ppins[0]);
|
||||
p->in_pin = conv->f->pins[1];
|
||||
|
||||
return f;
|
||||
if (data)
|
||||
mp_audio_copy_config(data, af->data);
|
||||
af_add_output_frame(af, data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct f_opts
|
||||
static int af_open(struct af_instance *af)
|
||||
{
|
||||
af->control = control;
|
||||
af->filter_frame = filter;
|
||||
|
||||
const struct mp_user_filter_entry af_format = {
|
||||
.desc = {
|
||||
.name = "format",
|
||||
.description = "Force audio format",
|
||||
.priv_size = sizeof(struct f_opts),
|
||||
.options = (const struct m_option[]) {
|
||||
{"format", OPT_AUDIOFORMAT(in_format)},
|
||||
{"srate", OPT_INT(in_srate), M_RANGE(1000, 8*48000)},
|
||||
{"channels", OPT_CHANNELS(in_channels),
|
||||
.flags = M_OPT_CHANNELS_LIMITED},
|
||||
{"out-srate", OPT_INT(out_srate), M_RANGE(1000, 8*48000)},
|
||||
{"out-channels", OPT_CHANNELS(out_channels),
|
||||
.flags = M_OPT_CHANNELS_LIMITED},
|
||||
{"fail", OPT_FLAG(fail)},
|
||||
{0}
|
||||
},
|
||||
force_in_params(af, af->data);
|
||||
force_out_params(af, af->data);
|
||||
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
const struct af_info af_info_format = {
|
||||
.info = "Force audio format",
|
||||
.name = "format",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_AUDIOFORMAT("format", in_format, 0),
|
||||
OPT_INTRANGE("srate", in_srate, 0, 1000, 8*48000),
|
||||
OPT_CHANNELS("channels", in_channels, 0, .min = 1),
|
||||
OPT_AUDIOFORMAT("out-format", out_format, 0),
|
||||
OPT_INTRANGE("out-srate", out_srate, 0, 1000, 8*48000),
|
||||
OPT_CHANNELS("out-channels", out_channels, 0, .min = 1),
|
||||
OPT_FLAG("fail", fail, 0),
|
||||
{0}
|
||||
},
|
||||
.create = af_format_create,
|
||||
};
|
||||
+280
-234
@@ -31,17 +31,14 @@
|
||||
#include <libavutil/bswap.h>
|
||||
#include <libavutil/mem.h>
|
||||
|
||||
#include "audio/aframe.h"
|
||||
#include "audio/chmap_sel.h"
|
||||
#include "audio/fmt-conversion.h"
|
||||
#include "audio/format.h"
|
||||
#include "config.h"
|
||||
|
||||
#include "common/av_common.h"
|
||||
#include "common/common.h"
|
||||
#include "filters/f_autoconvert.h"
|
||||
#include "filters/f_utils.h"
|
||||
#include "filters/filter_internal.h"
|
||||
#include "filters/user_filters.h"
|
||||
#include "options/m_option.h"
|
||||
#include "af.h"
|
||||
#include "audio/audio_buffer.h"
|
||||
#include "audio/chmap_sel.h"
|
||||
#include "audio/fmt-conversion.h"
|
||||
|
||||
|
||||
#define AC3_MAX_CHANNELS 6
|
||||
@@ -52,89 +49,173 @@ const uint16_t ac3_bitrate_tab[19] = {
|
||||
160, 192, 224, 256, 320, 384, 448, 512, 576, 640
|
||||
};
|
||||
|
||||
struct f_opts {
|
||||
int add_iec61937_header;
|
||||
int bit_rate;
|
||||
int min_channel_num;
|
||||
char *encoder;
|
||||
char **avopts;
|
||||
};
|
||||
|
||||
struct priv {
|
||||
struct f_opts *opts;
|
||||
|
||||
struct mp_pin *in_pin;
|
||||
struct mp_aframe *cur_format;
|
||||
struct mp_aframe *in_frame;
|
||||
struct mp_aframe_pool *out_pool;
|
||||
|
||||
// Data for specific instances of this filter
|
||||
typedef struct af_ac3enc_s {
|
||||
struct AVCodec *lavc_acodec;
|
||||
struct AVCodecContext *lavc_actx;
|
||||
int bit_rate;
|
||||
struct mp_audio *input; // frame passed to libavcodec
|
||||
struct mp_audio *pending; // unconsumed input data
|
||||
int in_samples; // samples of input per AC3 frame
|
||||
int out_samples; // upper bound on encoded output per AC3 frame
|
||||
};
|
||||
int64_t encoder_buffered;
|
||||
|
||||
static bool reinit(struct mp_filter *f)
|
||||
int cfg_add_iec61937_header;
|
||||
int cfg_bit_rate;
|
||||
int cfg_min_channel_num;
|
||||
char *cfg_encoder;
|
||||
char **cfg_avopts;
|
||||
} af_ac3enc_t;
|
||||
|
||||
// fmt carries the input format. Change it to the best next-possible format
|
||||
// the encoder likely accepts.
|
||||
static void select_encode_format(AVCodecContext *c, struct mp_audio *fmt)
|
||||
{
|
||||
struct priv *s = f->priv;
|
||||
int formats[AF_FORMAT_COUNT];
|
||||
af_get_best_sample_formats(fmt->format, formats);
|
||||
|
||||
mp_aframe_reset(s->cur_format);
|
||||
for (int n = 0; formats[n]; n++) {
|
||||
const enum AVSampleFormat *lf = c->codec->sample_fmts;
|
||||
for (int i = 0; lf && lf[i] != AV_SAMPLE_FMT_NONE; i++) {
|
||||
int mpfmt = af_from_avformat(lf[i]);
|
||||
if (mpfmt && mpfmt == formats[n]) {
|
||||
mp_audio_set_format(fmt, mpfmt);
|
||||
goto done_fmt;
|
||||
}
|
||||
}
|
||||
}
|
||||
done_fmt: ;
|
||||
|
||||
int rate =
|
||||
af_select_best_samplerate(fmt->rate, c->codec->supported_samplerates);
|
||||
if (rate > 0)
|
||||
fmt->rate = rate;
|
||||
|
||||
struct mp_chmap_sel sel = {0};
|
||||
const uint64_t *lch = c->codec->channel_layouts;
|
||||
for (int n = 0; lch && lch[n]; n++) {
|
||||
struct mp_chmap chmap = {0};
|
||||
mp_chmap_from_lavc(&chmap, lch[n]);
|
||||
mp_chmap_sel_add_map(&sel, &chmap);
|
||||
}
|
||||
struct mp_chmap res = fmt->channels;
|
||||
mp_chmap_sel_adjust(&sel, &res);
|
||||
if (!mp_chmap_is_empty(&res))
|
||||
mp_audio_set_channels(fmt, &res);
|
||||
}
|
||||
|
||||
// Initialization and runtime control
|
||||
static int control(struct af_instance *af, int cmd, void *arg)
|
||||
{
|
||||
af_ac3enc_t *s = af->priv;
|
||||
static const int default_bit_rate[AC3_MAX_CHANNELS+1] = \
|
||||
{0, 96000, 192000, 256000, 384000, 448000, 448000};
|
||||
|
||||
if (s->opts->add_iec61937_header) {
|
||||
s->out_samples = AC3_FRAME_SIZE;
|
||||
} else {
|
||||
s->out_samples = AC3_MAX_CODED_FRAME_SIZE /
|
||||
mp_aframe_get_sstride(s->in_frame);
|
||||
switch (cmd){
|
||||
case AF_CONTROL_REINIT: {
|
||||
struct mp_audio *in = arg;
|
||||
struct mp_audio orig_in = *in;
|
||||
|
||||
if (!af_fmt_is_pcm(in->format) || in->nch < s->cfg_min_channel_num)
|
||||
return AF_DETACH;
|
||||
|
||||
// At least currently, the AC3 encoder doesn't export sample rates.
|
||||
in->rate = 48000;
|
||||
select_encode_format(s->lavc_actx, in);
|
||||
|
||||
af->data->rate = in->rate;
|
||||
mp_audio_set_format(af->data, AF_FORMAT_S_AC3);
|
||||
mp_audio_set_num_channels(af->data, 2);
|
||||
|
||||
if (!mp_audio_config_equals(in, &orig_in))
|
||||
return AF_FALSE;
|
||||
|
||||
if (s->cfg_add_iec61937_header) {
|
||||
s->out_samples = AC3_FRAME_SIZE;
|
||||
} else {
|
||||
s->out_samples = AC3_MAX_CODED_FRAME_SIZE / af->data->sstride;
|
||||
}
|
||||
|
||||
mp_audio_copy_config(s->input, in);
|
||||
|
||||
talloc_free(s->pending);
|
||||
s->pending = NULL;
|
||||
|
||||
MP_DBG(af, "reinit: %d, %d, %d.\n", in->nch, in->rate, s->in_samples);
|
||||
|
||||
int bit_rate = s->bit_rate ? s->bit_rate : default_bit_rate[in->nch];
|
||||
|
||||
if (s->lavc_actx->channels != in->nch ||
|
||||
s->lavc_actx->sample_rate != in->rate ||
|
||||
s->lavc_actx->bit_rate != bit_rate)
|
||||
{
|
||||
avcodec_close(s->lavc_actx);
|
||||
|
||||
// Put sample parameters
|
||||
s->lavc_actx->sample_fmt = af_to_avformat(in->format);
|
||||
s->lavc_actx->channels = in->nch;
|
||||
s->lavc_actx->channel_layout = mp_chmap_to_lavc(&in->channels);
|
||||
s->lavc_actx->sample_rate = in->rate;
|
||||
s->lavc_actx->bit_rate = bit_rate;
|
||||
|
||||
if (avcodec_open2(s->lavc_actx, s->lavc_acodec, NULL) < 0) {
|
||||
MP_ERR(af, "Couldn't open codec %s, br=%d.\n", "ac3", bit_rate);
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
if (s->lavc_actx->frame_size < 1) {
|
||||
MP_ERR(af, "encoder didn't specify input frame size\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
}
|
||||
s->in_samples = s->lavc_actx->frame_size;
|
||||
mp_audio_realloc(s->input, s->in_samples);
|
||||
s->input->samples = 0;
|
||||
s->encoder_buffered = 0;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
int format = mp_aframe_get_format(s->in_frame);
|
||||
int rate = mp_aframe_get_rate(s->in_frame);
|
||||
struct mp_chmap chmap = {0};
|
||||
mp_aframe_get_chmap(s->in_frame, &chmap);
|
||||
|
||||
int bit_rate = s->bit_rate;
|
||||
if (!bit_rate && chmap.num < AC3_MAX_CHANNELS + 1)
|
||||
bit_rate = default_bit_rate[chmap.num];
|
||||
|
||||
avcodec_close(s->lavc_actx);
|
||||
|
||||
// Put sample parameters
|
||||
s->lavc_actx->sample_fmt = af_to_avformat(format);
|
||||
s->lavc_actx->channels = chmap.num;
|
||||
s->lavc_actx->channel_layout = mp_chmap_to_lavc(&chmap);
|
||||
s->lavc_actx->sample_rate = rate;
|
||||
s->lavc_actx->bit_rate = bit_rate;
|
||||
|
||||
if (avcodec_open2(s->lavc_actx, s->lavc_acodec, NULL) < 0) {
|
||||
MP_ERR(f, "Couldn't open codec %s, br=%d.\n", "ac3", bit_rate);
|
||||
return false;
|
||||
case AF_CONTROL_RESET:
|
||||
if (avcodec_is_open(s->lavc_actx))
|
||||
avcodec_flush_buffers(s->lavc_actx);
|
||||
talloc_free(s->pending);
|
||||
s->pending = NULL;
|
||||
s->input->samples = 0;
|
||||
s->encoder_buffered = 0;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
if (s->lavc_actx->frame_size < 1) {
|
||||
MP_ERR(f, "encoder didn't specify input frame size\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
mp_aframe_config_copy(s->cur_format, s->in_frame);
|
||||
return true;
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static void reset(struct mp_filter *f)
|
||||
// Deallocate memory
|
||||
static void uninit(struct af_instance* af)
|
||||
{
|
||||
struct priv *s = f->priv;
|
||||
af_ac3enc_t *s = af->priv;
|
||||
|
||||
TA_FREEP(&s->in_frame);
|
||||
if (s) {
|
||||
avcodec_free_context(&s->lavc_actx);
|
||||
talloc_free(s->pending);
|
||||
}
|
||||
}
|
||||
|
||||
static void destroy(struct mp_filter *f)
|
||||
static void update_delay(struct af_instance *af)
|
||||
{
|
||||
struct priv *s = f->priv;
|
||||
af_ac3enc_t *s = af->priv;
|
||||
af->delay = ((s->pending ? s->pending->samples : 0) + s->input->samples +
|
||||
s->encoder_buffered) / (double)s->input->rate;
|
||||
}
|
||||
|
||||
reset(f);
|
||||
avcodec_free_context(&s->lavc_actx);
|
||||
static int filter_frame(struct af_instance *af, struct mp_audio *audio)
|
||||
{
|
||||
af_ac3enc_t *s = af->priv;
|
||||
|
||||
// filter_output must have been called until no output was produced.
|
||||
if (s->pending && s->pending->samples)
|
||||
MP_ERR(af, "broken data flow\n");
|
||||
|
||||
talloc_free(s->pending);
|
||||
s->pending = audio;
|
||||
update_delay(af);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void swap_16(uint16_t *ptr, size_t size)
|
||||
@@ -143,84 +224,105 @@ static void swap_16(uint16_t *ptr, size_t size)
|
||||
ptr[n] = av_bswap16(ptr[n]);
|
||||
}
|
||||
|
||||
static void process(struct mp_filter *f)
|
||||
// Copy data from input frame to encode frame (because libavcodec wants a full
|
||||
// AC3 frame for encoding, while filter input frames can be smaller or larger).
|
||||
// Return true if the frame is complete.
|
||||
static bool fill_buffer(struct af_instance *af)
|
||||
{
|
||||
struct priv *s = f->priv;
|
||||
af_ac3enc_t *s = af->priv;
|
||||
|
||||
if (!mp_pin_in_needs_data(f->ppins[1]))
|
||||
return;
|
||||
af->delay = 0;
|
||||
|
||||
if (s->pending) {
|
||||
if (!mp_audio_is_writeable(s->input))
|
||||
assert(s->input->samples == 0); // we can't have sent a partial frame
|
||||
mp_audio_realloc_min(s->input, s->in_samples);
|
||||
int copy = MPMIN(s->in_samples - s->input->samples, s->pending->samples);
|
||||
s->input->samples += copy;
|
||||
mp_audio_copy(s->input, s->input->samples - copy, s->pending, 0, copy);
|
||||
mp_audio_skip_samples(s->pending, copy);
|
||||
}
|
||||
update_delay(af);
|
||||
return s->input->samples >= s->in_samples;
|
||||
}
|
||||
|
||||
// Return <0 on error, 0 on need more input, 1 on success (and *frame set).
|
||||
// To actually advance the read pointer, set s->input->samples=0 afterwards.
|
||||
static int read_input_frame(struct af_instance *af, AVFrame *frame)
|
||||
{
|
||||
af_ac3enc_t *s = af->priv;
|
||||
if (!fill_buffer(af))
|
||||
return 0; // need more input
|
||||
|
||||
if (mp_audio_to_avframe(s->input, frame) < 0)
|
||||
return -1;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int filter_out(struct af_instance *af)
|
||||
{
|
||||
af_ac3enc_t *s = af->priv;
|
||||
|
||||
if (!s->pending)
|
||||
return 0;
|
||||
|
||||
AVFrame *frame = av_frame_alloc();
|
||||
if (!frame) {
|
||||
MP_FATAL(af, "Could not allocate memory \n");
|
||||
return -1;
|
||||
}
|
||||
int err = -1;
|
||||
|
||||
bool err = true;
|
||||
struct mp_aframe *out = NULL;
|
||||
AVPacket pkt = {0};
|
||||
av_init_packet(&pkt);
|
||||
|
||||
// Send input as long as it wants.
|
||||
while (1) {
|
||||
if (avcodec_is_open(s->lavc_actx)) {
|
||||
int lavc_ret = avcodec_receive_packet(s->lavc_actx, &pkt);
|
||||
if (lavc_ret >= 0)
|
||||
break;
|
||||
if (lavc_ret < 0 && lavc_ret != AVERROR(EAGAIN)) {
|
||||
MP_FATAL(f, "Encode failed (receive).\n");
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
AVFrame *frame = NULL;
|
||||
struct mp_frame input = mp_pin_out_read(s->in_pin);
|
||||
// The following code assumes no sample data buffering in the encoder.
|
||||
if (input.type == MP_FRAME_EOF) {
|
||||
mp_pin_in_write(f->ppins[1], input);
|
||||
return;
|
||||
} else if (input.type == MP_FRAME_AUDIO) {
|
||||
TA_FREEP(&s->in_frame);
|
||||
s->in_frame = input.data;
|
||||
frame = mp_frame_to_av(input, NULL);
|
||||
if (!frame)
|
||||
goto done;
|
||||
if (mp_aframe_get_channels(s->in_frame) < s->opts->min_channel_num) {
|
||||
// Just pass it through.
|
||||
s->in_frame = NULL;
|
||||
mp_pin_in_write(f->ppins[1], input);
|
||||
return;
|
||||
}
|
||||
if (!mp_aframe_config_equals(s->in_frame, s->cur_format)) {
|
||||
if (!reinit(f))
|
||||
goto done;
|
||||
}
|
||||
} else if (input.type) {
|
||||
err = read_input_frame(af, frame);
|
||||
if (err < 0)
|
||||
goto done;
|
||||
} else {
|
||||
return; // no data yet
|
||||
}
|
||||
if (err == 0)
|
||||
break;
|
||||
err = -1;
|
||||
int lavc_ret = avcodec_send_frame(s->lavc_actx, frame);
|
||||
av_frame_free(&frame);
|
||||
if (lavc_ret < 0 && lavc_ret != AVERROR(EAGAIN)) {
|
||||
MP_FATAL(f, "Encode failed (send).\n");
|
||||
// On EAGAIN, we're supposed to read remaining output.
|
||||
if (lavc_ret == AVERROR(EAGAIN))
|
||||
break;
|
||||
if (lavc_ret < 0) {
|
||||
MP_FATAL(af, "Encode failed.\n");
|
||||
goto done;
|
||||
}
|
||||
s->encoder_buffered += s->input->samples;
|
||||
s->input->samples = 0;
|
||||
}
|
||||
int lavc_ret = avcodec_receive_packet(s->lavc_actx, &pkt);
|
||||
if (lavc_ret == AVERROR(EAGAIN)) {
|
||||
// Need to buffer more input.
|
||||
err = 0;
|
||||
goto done;
|
||||
}
|
||||
if (lavc_ret < 0) {
|
||||
MP_FATAL(af, "Encode failed.\n");
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (!s->in_frame)
|
||||
MP_DBG(af, "avcodec_encode_audio got %d, pending %d.\n",
|
||||
pkt.size, s->pending->samples + s->input->samples);
|
||||
|
||||
s->encoder_buffered -= AC3_FRAME_SIZE;
|
||||
|
||||
struct mp_audio *out =
|
||||
mp_audio_pool_get(af->out_pool, af->data, s->out_samples);
|
||||
if (!out)
|
||||
goto done;
|
||||
|
||||
out = mp_aframe_create();
|
||||
mp_aframe_set_format(out, AF_FORMAT_S_AC3);
|
||||
mp_aframe_set_chmap(out, &(struct mp_chmap)MP_CHMAP_INIT_STEREO);
|
||||
mp_aframe_set_rate(out, 48000);
|
||||
|
||||
if (mp_aframe_pool_allocate(s->out_pool, out, s->out_samples) < 0)
|
||||
goto done;
|
||||
|
||||
int sstride = mp_aframe_get_sstride(out);
|
||||
|
||||
mp_aframe_copy_attributes(out, s->in_frame);
|
||||
mp_audio_copy_attributes(out, s->pending);
|
||||
|
||||
int frame_size = pkt.size;
|
||||
int header_len = 0;
|
||||
char hdr[8];
|
||||
|
||||
if (s->opts->add_iec61937_header && pkt.size > 5) {
|
||||
if (s->cfg_add_iec61937_header && pkt.size > 5) {
|
||||
int bsmod = pkt.data[5] & 0x7;
|
||||
int len = frame_size;
|
||||
|
||||
@@ -234,69 +336,48 @@ static void process(struct mp_filter *f)
|
||||
AV_WL16(hdr + 6, len << 3); // number of bits in payload
|
||||
}
|
||||
|
||||
if (frame_size > s->out_samples * sstride)
|
||||
if (frame_size > out->samples * out->sstride)
|
||||
abort();
|
||||
|
||||
uint8_t **planes = mp_aframe_get_data_rw(out);
|
||||
if (!planes)
|
||||
goto done;
|
||||
char *buf = planes[0];
|
||||
char *buf = (char *)out->planes[0];
|
||||
memcpy(buf, hdr, header_len);
|
||||
memcpy(buf + header_len, pkt.data, pkt.size);
|
||||
memset(buf + header_len + pkt.size, 0,
|
||||
frame_size - (header_len + pkt.size));
|
||||
swap_16((uint16_t *)(buf + header_len), pkt.size / 2);
|
||||
mp_aframe_set_size(out, frame_size / sstride);
|
||||
mp_pin_in_write(f->ppins[1], MAKE_FRAME(MP_FRAME_AUDIO, out));
|
||||
out = NULL;
|
||||
out->samples = frame_size / out->sstride;
|
||||
af_add_output_frame(af, out);
|
||||
|
||||
err = 0;
|
||||
done:
|
||||
av_packet_unref(&pkt);
|
||||
talloc_free(out);
|
||||
if (err)
|
||||
mp_filter_internal_mark_failed(f);
|
||||
av_frame_free(&frame);
|
||||
update_delay(af);
|
||||
return err;
|
||||
}
|
||||
|
||||
static const struct mp_filter_info af_lavcac3enc_filter = {
|
||||
.name = "lavcac3enc",
|
||||
.priv_size = sizeof(struct priv),
|
||||
.process = process,
|
||||
.reset = reset,
|
||||
.destroy = destroy,
|
||||
};
|
||||
static int af_open(struct af_instance* af){
|
||||
|
||||
static struct mp_filter *af_lavcac3enc_create(struct mp_filter *parent,
|
||||
void *options)
|
||||
{
|
||||
struct mp_filter *f = mp_filter_create(parent, &af_lavcac3enc_filter);
|
||||
if (!f) {
|
||||
talloc_free(options);
|
||||
return NULL;
|
||||
}
|
||||
af_ac3enc_t *s = af->priv;
|
||||
af->control=control;
|
||||
af->uninit=uninit;
|
||||
af->filter_frame = filter_frame;
|
||||
af->filter_out = filter_out;
|
||||
|
||||
mp_filter_add_pin(f, MP_PIN_IN, "in");
|
||||
mp_filter_add_pin(f, MP_PIN_OUT, "out");
|
||||
|
||||
struct priv *s = f->priv;
|
||||
s->opts = talloc_steal(s, options);
|
||||
s->cur_format = talloc_steal(s, mp_aframe_create());
|
||||
s->out_pool = mp_aframe_pool_create(s);
|
||||
|
||||
s->lavc_acodec = avcodec_find_encoder_by_name(s->opts->encoder);
|
||||
s->lavc_acodec = avcodec_find_encoder_by_name(s->cfg_encoder);
|
||||
if (!s->lavc_acodec) {
|
||||
MP_ERR(f, "Couldn't find encoder %s.\n", s->opts->encoder);
|
||||
goto error;
|
||||
MP_ERR(af, "Couldn't find encoder %s.\n", s->cfg_encoder);
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
s->lavc_actx = avcodec_alloc_context3(s->lavc_acodec);
|
||||
if (!s->lavc_actx) {
|
||||
MP_ERR(f, "Audio LAVC, couldn't allocate context!\n");
|
||||
goto error;
|
||||
MP_ERR(af, "Audio LAVC, couldn't allocate context!\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
if (mp_set_avopts(f->log, s->lavc_actx, s->opts->avopts) < 0)
|
||||
goto error;
|
||||
if (mp_set_avopts(af->log, s->lavc_actx, s->cfg_avopts) < 0)
|
||||
return AF_ERROR;
|
||||
|
||||
// For this one, we require the decoder to expert lists of all supported
|
||||
// parameters. (Not all decoders do that, but the ones we're interested
|
||||
@@ -304,85 +385,50 @@ static struct mp_filter *af_lavcac3enc_create(struct mp_filter *parent,
|
||||
if (!s->lavc_acodec->sample_fmts ||
|
||||
!s->lavc_acodec->channel_layouts)
|
||||
{
|
||||
MP_ERR(f, "Audio encoder doesn't list supported parameters.\n");
|
||||
goto error;
|
||||
MP_ERR(af, "Audio encoder doesn't list supported parameters.\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
if (s->opts->bit_rate) {
|
||||
s->input = talloc_zero(s, struct mp_audio);
|
||||
|
||||
if (s->cfg_bit_rate) {
|
||||
int i;
|
||||
for (i = 0; i < 19; i++) {
|
||||
if (ac3_bitrate_tab[i] == s->opts->bit_rate) {
|
||||
if (ac3_bitrate_tab[i] == s->cfg_bit_rate) {
|
||||
s->bit_rate = ac3_bitrate_tab[i] * 1000;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i >= 19) {
|
||||
MP_WARN(f, "unable set unsupported bitrate %d, using default "
|
||||
MP_WARN(af, "unable set unsupported bitrate %d, using default "
|
||||
"bitrate (check manpage to see supported bitrates).\n",
|
||||
s->opts->bit_rate);
|
||||
s->cfg_bit_rate);
|
||||
}
|
||||
}
|
||||
|
||||
struct mp_autoconvert *conv = mp_autoconvert_create(f);
|
||||
if (!conv)
|
||||
abort();
|
||||
|
||||
const enum AVSampleFormat *lf = s->lavc_acodec->sample_fmts;
|
||||
for (int i = 0; lf && lf[i] != AV_SAMPLE_FMT_NONE; i++) {
|
||||
int mpfmt = af_from_avformat(lf[i]);
|
||||
if (mpfmt)
|
||||
mp_autoconvert_add_afmt(conv, mpfmt);
|
||||
}
|
||||
|
||||
const uint64_t *lch = s->lavc_acodec->channel_layouts;
|
||||
for (int n = 0; lch && lch[n]; n++) {
|
||||
struct mp_chmap chmap = {0};
|
||||
mp_chmap_from_lavc(&chmap, lch[n]);
|
||||
if (mp_chmap_is_valid(&chmap))
|
||||
mp_autoconvert_add_chmap(conv, &chmap);
|
||||
}
|
||||
|
||||
// At least currently, the AC3 encoder doesn't export sample rates.
|
||||
mp_autoconvert_add_srate(conv, 48000);
|
||||
|
||||
mp_pin_connect(conv->f->pins[0], f->ppins[0]);
|
||||
|
||||
struct mp_filter *fs = mp_fixed_aframe_size_create(f, AC3_FRAME_SIZE, true);
|
||||
if (!fs)
|
||||
abort();
|
||||
|
||||
mp_pin_connect(fs->pins[0], conv->f->pins[1]);
|
||||
s->in_pin = fs->pins[1];
|
||||
|
||||
return f;
|
||||
|
||||
error:
|
||||
talloc_free(f);
|
||||
return NULL;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct f_opts
|
||||
#define OPT_BASE_STRUCT struct af_ac3enc_s
|
||||
|
||||
const struct mp_user_filter_entry af_lavcac3enc = {
|
||||
.desc = {
|
||||
.description = "runtime encode to ac3 using libavcodec",
|
||||
.name = "lavcac3enc",
|
||||
.priv_size = sizeof(OPT_BASE_STRUCT),
|
||||
.priv_defaults = &(const OPT_BASE_STRUCT) {
|
||||
.add_iec61937_header = 1,
|
||||
.bit_rate = 640,
|
||||
.min_channel_num = 3,
|
||||
.encoder = "ac3",
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
{"tospdif", OPT_FLAG(add_iec61937_header)},
|
||||
{"bitrate", OPT_CHOICE(bit_rate,
|
||||
{"auto", 0}, {"default", 0}), M_RANGE(32, 640)},
|
||||
{"minch", OPT_INT(min_channel_num), M_RANGE(2, 6)},
|
||||
{"encoder", OPT_STRING(encoder)},
|
||||
{"o", OPT_KEYVALUELIST(avopts)},
|
||||
{0}
|
||||
},
|
||||
const struct af_info af_info_lavcac3enc = {
|
||||
.info = "runtime encode to ac3 using libavcodec",
|
||||
.name = "lavcac3enc",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(struct af_ac3enc_s),
|
||||
.priv_defaults = &(const struct af_ac3enc_s){
|
||||
.cfg_add_iec61937_header = 1,
|
||||
.cfg_bit_rate = 640,
|
||||
.cfg_min_channel_num = 3,
|
||||
.cfg_encoder = "ac3",
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_FLAG("tospdif", cfg_add_iec61937_header, 0),
|
||||
OPT_CHOICE_OR_INT("bitrate", cfg_bit_rate, 0, 32, 640,
|
||||
({"auto", 0}, {"default", 0})),
|
||||
OPT_INTRANGE("minch", cfg_min_channel_num, 0, 2, 6),
|
||||
OPT_STRING("encoder", cfg_encoder, 0),
|
||||
OPT_KEYVALUELIST("o", cfg_avopts, 0),
|
||||
{0}
|
||||
},
|
||||
.create = af_lavcac3enc_create,
|
||||
};
|
||||
@@ -0,0 +1,413 @@
|
||||
/*
|
||||
* 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;
|
||||
bool ok = false;
|
||||
|
||||
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);
|
||||
|
||||
ok = true;
|
||||
error:
|
||||
|
||||
if (!ok) {
|
||||
MP_FATAL(af, "Can't configure libavfilter graph.\n");
|
||||
avfilter_graph_free(&graph);
|
||||
}
|
||||
avfilter_inout_free(&inputs);
|
||||
avfilter_inout_free(&outputs);
|
||||
talloc_free(tmp);
|
||||
return ok;
|
||||
}
|
||||
|
||||
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, frame->metadata);
|
||||
#endif
|
||||
}
|
||||
|
||||
static int filter_frame(struct af_instance *af, struct mp_audio *data)
|
||||
{
|
||||
struct priv *p = af->priv;
|
||||
AVFrame *frame = NULL;
|
||||
|
||||
if (p->eof && data)
|
||||
reset(af);
|
||||
|
||||
if (!p->graph)
|
||||
goto error;
|
||||
|
||||
if (!data) {
|
||||
if (p->eof)
|
||||
return 0;
|
||||
p->eof = true;
|
||||
}
|
||||
|
||||
if (data) {
|
||||
frame = mp_audio_to_avframe_and_unref(data);
|
||||
data = NULL;
|
||||
if (!frame)
|
||||
goto error;
|
||||
|
||||
// Timebase is 1/sample_rate
|
||||
frame->pts = p->samples_in;
|
||||
p->samples_in += frame->nb_samples;
|
||||
}
|
||||
|
||||
if (av_buffersrc_add_frame(p->in, frame) < 0)
|
||||
goto error;
|
||||
|
||||
av_frame_free(&frame);
|
||||
talloc_free(data);
|
||||
return 0;
|
||||
error:
|
||||
av_frame_free(&frame);
|
||||
talloc_free(data);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int filter_out(struct af_instance *af)
|
||||
{
|
||||
struct priv *p = af->priv;
|
||||
|
||||
if (!p->graph)
|
||||
goto error;
|
||||
|
||||
AVFrame *frame = av_frame_alloc();
|
||||
if (!frame)
|
||||
goto error;
|
||||
|
||||
int err = av_buffersink_get_frame(p->out, frame);
|
||||
if (err == AVERROR(EAGAIN) || err == AVERROR_EOF) {
|
||||
// Not an error situation - no more output buffers in queue.
|
||||
// AVERROR_EOF means we shouldn't even give the filter more
|
||||
// input, but we don't handle that completely correctly.
|
||||
av_frame_free(&frame);
|
||||
p->eof |= err == AVERROR_EOF;
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct mp_audio *out = mp_audio_from_avframe(frame);
|
||||
if (!out)
|
||||
goto error;
|
||||
|
||||
mp_audio_copy_config(out, af->data);
|
||||
|
||||
if (frame->pts != AV_NOPTS_VALUE) {
|
||||
double in_time = p->samples_in / (double)af->fmt_in.rate;
|
||||
double out_time = frame->pts * av_q2d(p->timebase_out);
|
||||
// Need pts past the last output sample.
|
||||
out_time += out->samples / (double)out->rate;
|
||||
|
||||
af->delay = in_time - out_time;
|
||||
}
|
||||
|
||||
get_metadata_from_av_frame(af, frame);
|
||||
af_add_output_frame(af, out);
|
||||
av_frame_free(&frame);
|
||||
return 0;
|
||||
error:
|
||||
av_frame_free(&frame);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void uninit(struct af_instance *af)
|
||||
{
|
||||
destroy_graph(af);
|
||||
}
|
||||
|
||||
static int af_open(struct af_instance *af)
|
||||
{
|
||||
struct priv *p = af->priv;
|
||||
|
||||
af->control = control;
|
||||
af->uninit = uninit;
|
||||
af->filter_frame = filter_frame;
|
||||
af->filter_out = filter_out;
|
||||
|
||||
if (p->is_bridge) {
|
||||
if (!p->cfg_filter_name) {
|
||||
MP_ERR(af, "Filter name not set!\n");
|
||||
return 0;
|
||||
}
|
||||
if (!avfilter_get_by_name(p->cfg_filter_name)) {
|
||||
MP_ERR(af, "libavfilter filter '%s' not found!\n", p->cfg_filter_name);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
const struct af_info af_info_lavfi = {
|
||||
.info = "libavfilter bridge",
|
||||
.name = "lavfi",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_STRING("graph", cfg_graph, 0),
|
||||
OPT_KEYVALUELIST("o", cfg_avopts, 0),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
|
||||
const struct af_info af_info_lavfi_bridge = {
|
||||
.info = "libavfilter bridge (explicit options)",
|
||||
.name = "lavfi-bridge",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.priv_defaults = &(const struct priv){
|
||||
.is_bridge = true,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_STRING("name", cfg_filter_name, M_OPT_MIN, .min = 1),
|
||||
OPT_KEYVALUELIST("opts", cfg_filter_opts, 0),
|
||||
OPT_STRING("graph", cfg_graph, 0),
|
||||
OPT_KEYVALUELIST("o", cfg_avopts, 0),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* 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 "common/common.h"
|
||||
#include "config.h"
|
||||
|
||||
#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"
|
||||
#include "audio/aconverter.h"
|
||||
|
||||
struct af_resample {
|
||||
int allow_detach;
|
||||
double playback_speed;
|
||||
struct mp_resample_opts opts;
|
||||
struct mp_aconverter *converter;
|
||||
};
|
||||
|
||||
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) {
|
||||
if (!mp_aconverter_reconfig(s->converter,
|
||||
in->rate, in->format, in->channels,
|
||||
out->rate, out->format, out->channels))
|
||||
r = AF_ERROR;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
case AF_CONTROL_SET_PLAYBACK_SPEED_RESAMPLE: {
|
||||
s->playback_speed = *(double *)arg;
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_RESET:
|
||||
mp_aconverter_flush(s->converter);
|
||||
return AF_OK;
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static void uninit(struct af_instance *af)
|
||||
{
|
||||
struct af_resample *s = af->priv;
|
||||
|
||||
talloc_free(s->converter);
|
||||
}
|
||||
|
||||
static int filter(struct af_instance *af, struct mp_audio *in)
|
||||
{
|
||||
struct af_resample *s = af->priv;
|
||||
|
||||
mp_aconverter_set_speed(s->converter, s->playback_speed);
|
||||
|
||||
af->filter_out(af);
|
||||
|
||||
struct mp_aframe *aframe = mp_audio_to_aframe(in);
|
||||
if (!aframe && in)
|
||||
return -1;
|
||||
talloc_free(in);
|
||||
bool ok = mp_aconverter_write_input(s->converter, aframe);
|
||||
if (!ok)
|
||||
talloc_free(aframe);
|
||||
|
||||
return ok ? 0 : -1;
|
||||
}
|
||||
|
||||
static int filter_out(struct af_instance *af)
|
||||
{
|
||||
struct af_resample *s = af->priv;
|
||||
bool eof;
|
||||
struct mp_aframe *out = mp_aconverter_read_output(s->converter, &eof);
|
||||
if (out)
|
||||
af_add_output_frame(af, mp_audio_from_aframe(out));
|
||||
talloc_free(out);
|
||||
af->delay = mp_aconverter_get_latency(s->converter);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int af_open(struct af_instance *af)
|
||||
{
|
||||
struct af_resample *s = af->priv;
|
||||
|
||||
af->control = control;
|
||||
af->uninit = uninit;
|
||||
af->filter_frame = filter;
|
||||
af->filter_out = filter_out;
|
||||
|
||||
s->converter = mp_aconverter_create(af->global, af->log, &s->opts);
|
||||
|
||||
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 = MP_RESAMPLE_OPTS_DEF,
|
||||
.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", opts.avopts, 0),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
+169
-287
@@ -20,350 +20,232 @@
|
||||
|
||||
#include <rubberband/rubberband-c.h>
|
||||
|
||||
#include "audio/aframe.h"
|
||||
#include "audio/format.h"
|
||||
#include "common/common.h"
|
||||
#include "filters/f_autoconvert.h"
|
||||
#include "filters/filter_internal.h"
|
||||
#include "filters/user_filters.h"
|
||||
#include "options/m_option.h"
|
||||
|
||||
// command line options
|
||||
struct f_opts {
|
||||
int transients, detector, phase, window,
|
||||
smoothing, formant, pitch, channels;
|
||||
double scale;
|
||||
};
|
||||
#include "af.h"
|
||||
|
||||
struct priv {
|
||||
struct f_opts *opts;
|
||||
|
||||
struct mp_pin *in_pin;
|
||||
struct mp_aframe *cur_format;
|
||||
struct mp_aframe_pool *out_pool;
|
||||
bool sent_final;
|
||||
RubberBandState rubber;
|
||||
double speed;
|
||||
double pitch;
|
||||
struct mp_aframe *pending;
|
||||
struct mp_audio *pending;
|
||||
bool needs_reset;
|
||||
// Estimate how much librubberband has buffered internally.
|
||||
// I could not find a way to do this with the librubberband API.
|
||||
double rubber_delay;
|
||||
// command line options
|
||||
int opt_transients, opt_detector, opt_phase, opt_window,
|
||||
opt_smoothing, opt_formant, opt_pitch, opt_channels;
|
||||
};
|
||||
|
||||
static void update_speed(struct priv *p, double new_speed)
|
||||
static void update_speed(struct af_instance *af, double new_speed)
|
||||
{
|
||||
struct priv *p = af->priv;
|
||||
|
||||
p->speed = new_speed;
|
||||
if (p->rubber)
|
||||
rubberband_set_time_ratio(p->rubber, 1.0 / p->speed);
|
||||
rubberband_set_time_ratio(p->rubber, 1.0 / p->speed);
|
||||
}
|
||||
|
||||
static bool update_pitch(struct priv *p, double new_pitch)
|
||||
static void update_pitch(struct af_instance *af, double new_pitch)
|
||||
{
|
||||
if (new_pitch < 0.01 || new_pitch > 100.0)
|
||||
return false;
|
||||
struct priv *p = af->priv;
|
||||
|
||||
p->pitch = new_pitch;
|
||||
if (p->rubber)
|
||||
rubberband_set_pitch_scale(p->rubber, p->pitch);
|
||||
return true;
|
||||
rubberband_set_pitch_scale(p->rubber, p->pitch);
|
||||
}
|
||||
|
||||
static bool init_rubberband(struct mp_filter *f)
|
||||
static int control(struct af_instance *af, int cmd, void *arg)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
struct priv *p = af->priv;
|
||||
|
||||
assert(!p->rubber);
|
||||
assert(p->pending);
|
||||
switch (cmd) {
|
||||
case AF_CONTROL_REINIT: {
|
||||
struct mp_audio *in = arg;
|
||||
struct mp_audio orig_in = *in;
|
||||
struct mp_audio *out = af->data;
|
||||
|
||||
int opts = p->opts->transients | p->opts->detector | p->opts->phase |
|
||||
p->opts->window | p->opts->smoothing | p->opts->formant |
|
||||
p->opts->pitch | p-> opts->channels |
|
||||
RubberBandOptionProcessRealTime;
|
||||
in->format = AF_FORMAT_FLOATP;
|
||||
mp_audio_copy_config(out, in);
|
||||
|
||||
int rate = mp_aframe_get_rate(p->pending);
|
||||
int channels = mp_aframe_get_channels(p->pending);
|
||||
if (mp_aframe_get_format(p->pending) != AF_FORMAT_FLOATP)
|
||||
return false;
|
||||
if (p->rubber)
|
||||
rubberband_delete(p->rubber);
|
||||
|
||||
p->rubber = rubberband_new(rate, channels, opts, 1.0, 1.0);
|
||||
if (!p->rubber) {
|
||||
MP_FATAL(f, "librubberband initialization failed.\n");
|
||||
return false;
|
||||
int opts = p->opt_transients | p->opt_detector | p->opt_phase |
|
||||
p->opt_window | p->opt_smoothing | p->opt_formant |
|
||||
p->opt_pitch | p-> opt_channels |
|
||||
RubberBandOptionProcessRealTime;
|
||||
|
||||
p->rubber = rubberband_new(in->rate, in->channels.num, opts, 1.0, 1.0);
|
||||
if (!p->rubber) {
|
||||
MP_FATAL(af, "librubberband initialization failed.\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
update_speed(af, p->speed);
|
||||
update_pitch(af, p->pitch);
|
||||
control(af, AF_CONTROL_RESET, NULL);
|
||||
|
||||
return mp_audio_config_equals(in, &orig_in) ? AF_OK : AF_FALSE;
|
||||
}
|
||||
|
||||
mp_aframe_config_copy(p->cur_format, p->pending);
|
||||
|
||||
update_speed(p, p->speed);
|
||||
update_pitch(p, p->pitch);
|
||||
|
||||
return true;
|
||||
case AF_CONTROL_SET_PLAYBACK_SPEED: {
|
||||
update_speed(af, *(double *)arg);
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_RESET:
|
||||
if (p->rubber)
|
||||
rubberband_reset(p->rubber);
|
||||
talloc_free(p->pending);
|
||||
p->pending = NULL;
|
||||
p->rubber_delay = 0;
|
||||
return AF_OK;
|
||||
case AF_CONTROL_COMMAND: {
|
||||
char **args = arg;
|
||||
if (!strcmp(args[0], "set-pitch")) {
|
||||
char *endptr;
|
||||
double pitch = strtod(args[1], &endptr);
|
||||
if (*endptr || pitch < 0.01 || pitch > 100.0)
|
||||
return CONTROL_ERROR;
|
||||
update_pitch(af, pitch);
|
||||
return CONTROL_OK;
|
||||
} else {
|
||||
return CONTROL_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static void process(struct mp_filter *f)
|
||||
static int filter_frame(struct af_instance *af, struct mp_audio *data)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
struct priv *p = af->priv;
|
||||
|
||||
if (!mp_pin_in_needs_data(f->ppins[1]))
|
||||
return;
|
||||
talloc_free(p->pending);
|
||||
p->pending = data;
|
||||
|
||||
while (!p->rubber || !p->pending || rubberband_available(p->rubber) <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int filter_out(struct af_instance *af)
|
||||
{
|
||||
struct priv *p = af->priv;
|
||||
|
||||
while (rubberband_available(p->rubber) <= 0) {
|
||||
const float *dummy[MP_NUM_CHANNELS] = {0};
|
||||
const float **in_data = dummy;
|
||||
size_t in_samples = 0;
|
||||
if (p->pending) {
|
||||
if (!p->pending->samples)
|
||||
break;
|
||||
|
||||
bool eof = false;
|
||||
if (!p->pending || !mp_aframe_get_size(p->pending)) {
|
||||
struct mp_frame frame = mp_pin_out_read(p->in_pin);
|
||||
if (frame.type == MP_FRAME_AUDIO) {
|
||||
TA_FREEP(&p->pending);
|
||||
p->pending = frame.data;
|
||||
} else if (frame.type == MP_FRAME_EOF) {
|
||||
eof = true;
|
||||
} else if (frame.type) {
|
||||
MP_ERR(f, "unexpected frame type\n");
|
||||
goto error;
|
||||
} else {
|
||||
return; // no new data yet
|
||||
}
|
||||
}
|
||||
assert(p->pending || eof);
|
||||
|
||||
if (!p->rubber) {
|
||||
if (!p->pending) {
|
||||
mp_pin_in_write(f->ppins[1], MP_EOF_FRAME);
|
||||
return;
|
||||
}
|
||||
if (!init_rubberband(f))
|
||||
goto error;
|
||||
}
|
||||
|
||||
bool format_change =
|
||||
p->pending && !mp_aframe_config_equals(p->pending, p->cur_format);
|
||||
|
||||
if (p->pending && !format_change) {
|
||||
size_t needs = rubberband_get_samples_required(p->rubber);
|
||||
uint8_t **planes = mp_aframe_get_data_ro(p->pending);
|
||||
int num_planes = mp_aframe_get_planes(p->pending);
|
||||
for (int n = 0; n < num_planes; n++)
|
||||
in_data[n] = (void *)planes[n];
|
||||
in_samples = MPMIN(mp_aframe_get_size(p->pending), needs);
|
||||
}
|
||||
|
||||
bool final = format_change || eof;
|
||||
if (!p->sent_final)
|
||||
rubberband_process(p->rubber, in_data, in_samples, final);
|
||||
p->sent_final |= final;
|
||||
|
||||
p->rubber_delay += in_samples;
|
||||
|
||||
if (p->pending && !format_change)
|
||||
mp_aframe_skip_samples(p->pending, in_samples);
|
||||
|
||||
if (rubberband_available(p->rubber) > 0) {
|
||||
if (eof)
|
||||
mp_pin_out_repeat_eof(p->in_pin); // drain more next time
|
||||
} else {
|
||||
if (eof) {
|
||||
mp_pin_in_write(f->ppins[1], MP_EOF_FRAME);
|
||||
// recover from previous EOF
|
||||
if (p->needs_reset) {
|
||||
rubberband_reset(p->rubber);
|
||||
p->rubber_delay = 0;
|
||||
TA_FREEP(&p->pending);
|
||||
p->sent_final = false;
|
||||
return;
|
||||
} else if (format_change) {
|
||||
// go on with proper reinit on the next iteration
|
||||
rubberband_delete(p->rubber);
|
||||
p->sent_final = false;
|
||||
p->rubber = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
p->needs_reset = false;
|
||||
|
||||
assert(p->pending);
|
||||
size_t needs = rubberband_get_samples_required(p->rubber);
|
||||
in_data = (void *)&p->pending->planes;
|
||||
in_samples = MPMIN(p->pending->samples, needs);
|
||||
}
|
||||
|
||||
if (p->needs_reset)
|
||||
break; // previous EOF
|
||||
p->needs_reset = !p->pending; // EOF
|
||||
|
||||
rubberband_process(p->rubber, in_data, in_samples, p->needs_reset);
|
||||
p->rubber_delay += in_samples;
|
||||
|
||||
if (!p->pending)
|
||||
break;
|
||||
mp_audio_skip_samples(p->pending, in_samples);
|
||||
}
|
||||
|
||||
int out_samples = rubberband_available(p->rubber);
|
||||
if (out_samples > 0) {
|
||||
struct mp_aframe *out = mp_aframe_new_ref(p->cur_format);
|
||||
if (mp_aframe_pool_allocate(p->out_pool, out, out_samples) < 0) {
|
||||
talloc_free(out);
|
||||
goto error;
|
||||
}
|
||||
struct mp_audio *out =
|
||||
mp_audio_pool_get(af->out_pool, af->data, out_samples);
|
||||
if (!out)
|
||||
return -1;
|
||||
if (p->pending)
|
||||
mp_audio_copy_config(out, p->pending);
|
||||
|
||||
mp_aframe_copy_attributes(out, p->pending);
|
||||
float **out_data = (void *)&out->planes;
|
||||
out->samples = rubberband_retrieve(p->rubber, out_data, out->samples);
|
||||
p->rubber_delay -= out->samples * p->speed;
|
||||
|
||||
float *out_data[MP_NUM_CHANNELS] = {0};
|
||||
uint8_t **planes = mp_aframe_get_data_rw(out);
|
||||
assert(planes);
|
||||
int num_planes = mp_aframe_get_planes(out);
|
||||
for (int n = 0; n < num_planes; n++)
|
||||
out_data[n] = (void *)planes[n];
|
||||
|
||||
out_samples = rubberband_retrieve(p->rubber, out_data, out_samples);
|
||||
|
||||
if (!out_samples) {
|
||||
mp_filter_internal_mark_progress(f); // unexpected, just try again
|
||||
talloc_free(out);
|
||||
return;
|
||||
}
|
||||
|
||||
mp_aframe_set_size(out, out_samples);
|
||||
|
||||
p->rubber_delay -= out_samples * p->speed;
|
||||
|
||||
double pts = mp_aframe_get_pts(p->pending);
|
||||
if (pts != MP_NOPTS_VALUE) {
|
||||
// Note: rubberband_get_latency() does not do what you'd expect.
|
||||
double delay = p->rubber_delay / mp_aframe_get_effective_rate(out);
|
||||
mp_aframe_set_pts(out, pts - delay);
|
||||
}
|
||||
|
||||
mp_aframe_mul_speed(out, p->speed);
|
||||
|
||||
mp_pin_in_write(f->ppins[1], MAKE_FRAME(MP_FRAME_AUDIO, out));
|
||||
af_add_output_frame(af, out);
|
||||
}
|
||||
|
||||
return;
|
||||
error:
|
||||
mp_filter_internal_mark_failed(f);
|
||||
int delay_samples = p->rubber_delay;
|
||||
if (p->pending)
|
||||
delay_samples += p->pending->samples;
|
||||
af->delay = delay_samples / (af->data->rate * p->speed);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool command(struct mp_filter *f, struct mp_filter_command *cmd)
|
||||
static void uninit(struct af_instance *af)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
|
||||
switch (cmd->type) {
|
||||
case MP_FILTER_COMMAND_TEXT: {
|
||||
char *endptr = NULL;
|
||||
double pitch = p->pitch;
|
||||
if (!strcmp(cmd->cmd, "set-pitch")) {
|
||||
pitch = strtod(cmd->arg, &endptr);
|
||||
if (*endptr)
|
||||
return false;
|
||||
return update_pitch(p, pitch);
|
||||
} else if (!strcmp(cmd->cmd, "multiply-pitch")) {
|
||||
double mult = strtod(cmd->arg, &endptr);
|
||||
if (*endptr || mult <= 0)
|
||||
return false;
|
||||
pitch *= mult;
|
||||
return update_pitch(p, pitch);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case MP_FILTER_COMMAND_SET_SPEED:
|
||||
update_speed(p, cmd->speed);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void reset(struct mp_filter *f)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
|
||||
if (p->rubber)
|
||||
rubberband_reset(p->rubber);
|
||||
p->rubber_delay = 0;
|
||||
p->sent_final = false;
|
||||
TA_FREEP(&p->pending);
|
||||
}
|
||||
|
||||
static void destroy(struct mp_filter *f)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
struct priv *p = af->priv;
|
||||
|
||||
if (p->rubber)
|
||||
rubberband_delete(p->rubber);
|
||||
talloc_free(p->pending);
|
||||
}
|
||||
|
||||
static const struct mp_filter_info af_rubberband_filter = {
|
||||
.name = "rubberband",
|
||||
.priv_size = sizeof(struct priv),
|
||||
.process = process,
|
||||
.command = command,
|
||||
.reset = reset,
|
||||
.destroy = destroy,
|
||||
};
|
||||
|
||||
static struct mp_filter *af_rubberband_create(struct mp_filter *parent,
|
||||
void *options)
|
||||
static int af_open(struct af_instance *af)
|
||||
{
|
||||
struct mp_filter *f = mp_filter_create(parent, &af_rubberband_filter);
|
||||
if (!f) {
|
||||
talloc_free(options);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
mp_filter_add_pin(f, MP_PIN_IN, "in");
|
||||
mp_filter_add_pin(f, MP_PIN_OUT, "out");
|
||||
|
||||
struct priv *p = f->priv;
|
||||
p->opts = talloc_steal(p, options);
|
||||
p->speed = 1.0;
|
||||
p->pitch = p->opts->scale;
|
||||
p->cur_format = talloc_steal(p, mp_aframe_create());
|
||||
p->out_pool = mp_aframe_pool_create(p);
|
||||
|
||||
struct mp_autoconvert *conv = mp_autoconvert_create(f);
|
||||
if (!conv)
|
||||
abort();
|
||||
|
||||
mp_autoconvert_add_afmt(conv, AF_FORMAT_FLOATP);
|
||||
|
||||
mp_pin_connect(conv->f->pins[0], f->ppins[0]);
|
||||
p->in_pin = conv->f->pins[1];
|
||||
|
||||
return f;
|
||||
af->control = control;
|
||||
af->filter_frame = filter_frame;
|
||||
af->filter_out = filter_out;
|
||||
af->uninit = uninit;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct f_opts
|
||||
|
||||
const struct mp_user_filter_entry af_rubberband = {
|
||||
.desc = {
|
||||
.description = "Pitch conversion with librubberband",
|
||||
.name = "rubberband",
|
||||
.priv_size = sizeof(OPT_BASE_STRUCT),
|
||||
.priv_defaults = &(const OPT_BASE_STRUCT) {
|
||||
.scale = 1.0,
|
||||
.pitch = RubberBandOptionPitchHighConsistency,
|
||||
.transients = RubberBandOptionTransientsMixed,
|
||||
.formant = RubberBandOptionFormantPreserved,
|
||||
.channels = RubberBandOptionChannelsTogether,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
{"transients", OPT_CHOICE(transients,
|
||||
{"crisp", RubberBandOptionTransientsCrisp},
|
||||
{"mixed", RubberBandOptionTransientsMixed},
|
||||
{"smooth", RubberBandOptionTransientsSmooth})},
|
||||
{"detector", OPT_CHOICE(detector,
|
||||
{"compound", RubberBandOptionDetectorCompound},
|
||||
{"percussive", RubberBandOptionDetectorPercussive},
|
||||
{"soft", RubberBandOptionDetectorSoft})},
|
||||
{"phase", OPT_CHOICE(phase,
|
||||
{"laminar", RubberBandOptionPhaseLaminar},
|
||||
{"independent", RubberBandOptionPhaseIndependent})},
|
||||
{"window", OPT_CHOICE(window,
|
||||
{"standard", RubberBandOptionWindowStandard},
|
||||
{"short", RubberBandOptionWindowShort},
|
||||
{"long", RubberBandOptionWindowLong})},
|
||||
{"smoothing", OPT_CHOICE(smoothing,
|
||||
{"off", RubberBandOptionSmoothingOff},
|
||||
{"on", RubberBandOptionSmoothingOn})},
|
||||
{"formant", OPT_CHOICE(formant,
|
||||
{"shifted", RubberBandOptionFormantShifted},
|
||||
{"preserved", RubberBandOptionFormantPreserved})},
|
||||
{"pitch", OPT_CHOICE(pitch,
|
||||
{"quality", RubberBandOptionPitchHighQuality},
|
||||
{"speed", RubberBandOptionPitchHighSpeed},
|
||||
{"consistency", RubberBandOptionPitchHighConsistency})},
|
||||
{"channels", OPT_CHOICE(channels,
|
||||
{"apart", RubberBandOptionChannelsApart},
|
||||
{"together", RubberBandOptionChannelsTogether})},
|
||||
{"pitch-scale", OPT_DOUBLE(scale), M_RANGE(0.01, 100)},
|
||||
{0}
|
||||
},
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
const struct af_info af_info_rubberband = {
|
||||
.info = "Pitch conversion with librubberband",
|
||||
.name = "rubberband",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.priv_defaults = &(const struct priv) {
|
||||
.speed = 1.0,
|
||||
.pitch = 1.0,
|
||||
.opt_pitch = RubberBandOptionPitchHighConsistency,
|
||||
.opt_transients = RubberBandOptionTransientsMixed,
|
||||
.opt_formant = RubberBandOptionFormantPreserved,
|
||||
.opt_channels = RubberBandOptionChannelsTogether,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_CHOICE("transients", opt_transients, 0,
|
||||
({"crisp", RubberBandOptionTransientsCrisp},
|
||||
{"mixed", RubberBandOptionTransientsMixed},
|
||||
{"smooth", RubberBandOptionTransientsSmooth})),
|
||||
OPT_CHOICE("detector", opt_detector, 0,
|
||||
({"compound", RubberBandOptionDetectorCompound},
|
||||
{"percussive", RubberBandOptionDetectorPercussive},
|
||||
{"soft", RubberBandOptionDetectorSoft})),
|
||||
OPT_CHOICE("phase", opt_phase, 0,
|
||||
({"laminar", RubberBandOptionPhaseLaminar},
|
||||
{"independent", RubberBandOptionPhaseIndependent})),
|
||||
OPT_CHOICE("window", opt_window, 0,
|
||||
({"standard", RubberBandOptionWindowStandard},
|
||||
{"short", RubberBandOptionWindowShort},
|
||||
{"long", RubberBandOptionWindowLong})),
|
||||
OPT_CHOICE("smoothing", opt_smoothing, 0,
|
||||
({"off", RubberBandOptionSmoothingOff},
|
||||
{"on", RubberBandOptionSmoothingOn})),
|
||||
OPT_CHOICE("formant", opt_formant, 0,
|
||||
({"shifted", RubberBandOptionFormantShifted},
|
||||
{"preserved", RubberBandOptionFormantPreserved})),
|
||||
OPT_CHOICE("pitch", opt_pitch, 0,
|
||||
({"quality", RubberBandOptionPitchHighQuality},
|
||||
{"speed", RubberBandOptionPitchHighSpeed},
|
||||
{"consistency", RubberBandOptionPitchHighConsistency})),
|
||||
OPT_CHOICE("channels", opt_channels, 0,
|
||||
({"apart", RubberBandOptionChannelsApart},
|
||||
{"together", RubberBandOptionChannelsTogether})),
|
||||
OPT_DOUBLE("pitch-scale", pitch, M_OPT_RANGE, .min = 0.01, .max = 100),
|
||||
{0}
|
||||
},
|
||||
.create = af_rubberband_create,
|
||||
};
|
||||
+244
-376
@@ -30,39 +30,19 @@
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <float.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <limits.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "audio/aframe.h"
|
||||
#include "audio/format.h"
|
||||
#include "common/common.h"
|
||||
#include "filters/f_autoconvert.h"
|
||||
#include "filters/filter_internal.h"
|
||||
#include "filters/user_filters.h"
|
||||
|
||||
#include "af.h"
|
||||
#include "options/m_option.h"
|
||||
|
||||
struct f_opts {
|
||||
float scale_nominal;
|
||||
float ms_stride;
|
||||
float ms_search;
|
||||
float percent_overlap;
|
||||
#define SCALE_TEMPO 1
|
||||
#define SCALE_PITCH 2
|
||||
int speed_opt;
|
||||
};
|
||||
|
||||
struct priv {
|
||||
struct f_opts *opts;
|
||||
|
||||
struct mp_pin *in_pin;
|
||||
struct mp_aframe *cur_format;
|
||||
struct mp_aframe_pool *out_pool;
|
||||
double current_pts;
|
||||
struct mp_aframe *in;
|
||||
|
||||
// Data for specific instances of this filter
|
||||
typedef struct af_scaletempo_s
|
||||
{
|
||||
// stride
|
||||
float scale;
|
||||
float speed;
|
||||
@@ -82,23 +62,29 @@ struct priv {
|
||||
int bytes_standing;
|
||||
void *buf_overlap;
|
||||
void *table_blend;
|
||||
void (*output_overlap)(struct priv *s, void *out_buf,
|
||||
void (*output_overlap)(struct af_scaletempo_s *s, void *out_buf,
|
||||
int bytes_off);
|
||||
// best overlap
|
||||
int frames_search;
|
||||
int num_channels;
|
||||
void *buf_pre_corr;
|
||||
void *table_window;
|
||||
int (*best_overlap_offset)(struct priv *s);
|
||||
};
|
||||
int (*best_overlap_offset)(struct af_scaletempo_s *s);
|
||||
// command line
|
||||
float scale_nominal;
|
||||
float ms_stride;
|
||||
float percent_overlap;
|
||||
float ms_search;
|
||||
#define SCALE_TEMPO 1
|
||||
#define SCALE_PITCH 2
|
||||
int speed_opt;
|
||||
} af_scaletempo_t;
|
||||
|
||||
static bool reinit(struct mp_filter *f);
|
||||
|
||||
// Return whether it got enough data for filtering.
|
||||
static bool fill_queue(struct priv *s)
|
||||
static int fill_queue(struct af_instance *af, struct mp_audio *data, int offset)
|
||||
{
|
||||
int bytes_in = s->in ? mp_aframe_get_size(s->in) * s->bytes_per_frame : 0;
|
||||
int offset = 0;
|
||||
af_scaletempo_t *s = af->priv;
|
||||
int bytes_in = (data ? mp_audio_psize(data) : 0) - offset;
|
||||
int offset_unchanged = offset;
|
||||
|
||||
if (s->bytes_to_slide > 0) {
|
||||
if (s->bytes_to_slide < s->bytes_queued) {
|
||||
@@ -117,27 +103,21 @@ static bool fill_queue(struct priv *s)
|
||||
}
|
||||
}
|
||||
|
||||
int bytes_needed = s->bytes_queue - s->bytes_queued;
|
||||
assert(bytes_needed >= 0);
|
||||
|
||||
int bytes_copy = MPMIN(bytes_needed, bytes_in);
|
||||
if (bytes_copy > 0) {
|
||||
uint8_t **planes = mp_aframe_get_data_ro(s->in);
|
||||
memcpy(s->buf_queue + s->bytes_queued, planes[0] + offset, bytes_copy);
|
||||
if (bytes_in > 0) {
|
||||
int bytes_copy = MPMIN(s->bytes_queue - s->bytes_queued, bytes_in);
|
||||
assert(bytes_copy >= 0);
|
||||
memcpy(s->buf_queue + s->bytes_queued,
|
||||
(int8_t *)data->planes[0] + offset, bytes_copy);
|
||||
s->bytes_queued += bytes_copy;
|
||||
offset += bytes_copy;
|
||||
bytes_needed -= bytes_copy;
|
||||
}
|
||||
|
||||
if (s->in)
|
||||
mp_aframe_skip_samples(s->in, offset / s->bytes_per_frame);
|
||||
|
||||
return bytes_needed == 0;
|
||||
return offset - offset_unchanged;
|
||||
}
|
||||
|
||||
#define UNROLL_PADDING (4 * 4)
|
||||
|
||||
static int best_overlap_offset_float(struct priv *s)
|
||||
static int best_overlap_offset_float(af_scaletempo_t *s)
|
||||
{
|
||||
float best_corr = INT_MIN;
|
||||
int best_off = 0;
|
||||
@@ -166,7 +146,7 @@ static int best_overlap_offset_float(struct priv *s)
|
||||
return best_off * 4 * s->num_channels;
|
||||
}
|
||||
|
||||
static int best_overlap_offset_s16(struct priv *s)
|
||||
static int best_overlap_offset_s16(af_scaletempo_t *s)
|
||||
{
|
||||
int64_t best_corr = INT64_MIN;
|
||||
int best_off = 0;
|
||||
@@ -187,10 +167,10 @@ static int best_overlap_offset_s16(struct priv *s)
|
||||
ps += s->samples_overlap - s->num_channels;
|
||||
long i = -(s->samples_overlap - s->num_channels);
|
||||
do {
|
||||
corr += ppc[i + 0] * (int64_t)ps[i + 0];
|
||||
corr += ppc[i + 1] * (int64_t)ps[i + 1];
|
||||
corr += ppc[i + 2] * (int64_t)ps[i + 2];
|
||||
corr += ppc[i + 3] * (int64_t)ps[i + 3];
|
||||
corr += ppc[i + 0] * ps[i + 0];
|
||||
corr += ppc[i + 1] * ps[i + 1];
|
||||
corr += ppc[i + 2] * ps[i + 2];
|
||||
corr += ppc[i + 3] * ps[i + 3];
|
||||
i += 4;
|
||||
} while (i < 0);
|
||||
if (corr > best_corr) {
|
||||
@@ -203,7 +183,7 @@ static int best_overlap_offset_s16(struct priv *s)
|
||||
return best_off * 2 * s->num_channels;
|
||||
}
|
||||
|
||||
static void output_overlap_float(struct priv *s, void *buf_out,
|
||||
static void output_overlap_float(af_scaletempo_t *s, void *buf_out,
|
||||
int bytes_off)
|
||||
{
|
||||
float *pout = buf_out;
|
||||
@@ -216,7 +196,7 @@ static void output_overlap_float(struct priv *s, void *buf_out,
|
||||
}
|
||||
}
|
||||
|
||||
static void output_overlap_s16(struct priv *s, void *buf_out,
|
||||
static void output_overlap_s16(af_scaletempo_t *s, void *buf_out,
|
||||
int bytes_off)
|
||||
{
|
||||
int16_t *pout = buf_out;
|
||||
@@ -229,77 +209,29 @@ static void output_overlap_s16(struct priv *s, void *buf_out,
|
||||
}
|
||||
}
|
||||
|
||||
static void process(struct mp_filter *f)
|
||||
static int filter(struct af_instance *af, struct mp_audio *data)
|
||||
{
|
||||
struct priv *s = f->priv;
|
||||
af_scaletempo_t *s = af->priv;
|
||||
|
||||
if (!mp_pin_in_needs_data(f->ppins[1]))
|
||||
return;
|
||||
|
||||
struct mp_aframe *out = NULL;
|
||||
|
||||
bool drain = false;
|
||||
bool is_eof = false;
|
||||
if (!s->in) {
|
||||
struct mp_frame frame = mp_pin_out_read(s->in_pin);
|
||||
if (!frame.type)
|
||||
return; // no input yet
|
||||
if (frame.type != MP_FRAME_AUDIO && frame.type != MP_FRAME_EOF) {
|
||||
MP_ERR(f, "unexpected frame type\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
s->in = frame.type == MP_FRAME_AUDIO ? frame.data : NULL;
|
||||
is_eof = drain = !s->in;
|
||||
|
||||
// EOF before it was even initialized once.
|
||||
if (is_eof && !mp_aframe_config_is_valid(s->cur_format)) {
|
||||
mp_pin_in_write(f->ppins[1], MP_EOF_FRAME);
|
||||
return;
|
||||
}
|
||||
|
||||
if (s->in && !mp_aframe_config_equals(s->in, s->cur_format)) {
|
||||
if (s->bytes_queued) {
|
||||
// Drain remaining data before executing the format change.
|
||||
MP_VERBOSE(f, "draining\n");
|
||||
mp_pin_out_unread(s->in_pin, frame);
|
||||
s->in = NULL;
|
||||
drain = true;
|
||||
} else {
|
||||
if (!reinit(f)) {
|
||||
MP_ERR(f, "initialization failed\n");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (s->in)
|
||||
s->current_pts = mp_aframe_end_pts(s->in);
|
||||
if (s->scale == 1.0) {
|
||||
af->delay = 0;
|
||||
af_add_output_frame(af, data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!fill_queue(s) && !drain) {
|
||||
TA_FREEP(&s->in);
|
||||
mp_pin_out_request_data_next(s->in_pin);
|
||||
return;
|
||||
int in_samples = data ? data->samples : 0;
|
||||
struct mp_audio *out = mp_audio_pool_get(af->out_pool, af->data,
|
||||
((int)(in_samples / s->frames_stride_scaled) + 1) * s->frames_stride);
|
||||
if (!out) {
|
||||
talloc_free(data);
|
||||
return -1;
|
||||
}
|
||||
if (data)
|
||||
mp_audio_copy_attributes(out, data);
|
||||
|
||||
int max_out_samples = s->bytes_stride / s->bytes_per_frame;
|
||||
if (drain)
|
||||
max_out_samples += s->bytes_queued;
|
||||
|
||||
out = mp_aframe_new_ref(s->cur_format);
|
||||
if (mp_aframe_pool_allocate(s->out_pool, out, max_out_samples) < 0)
|
||||
goto error;
|
||||
|
||||
if (s->in)
|
||||
mp_aframe_copy_attributes(out, s->in);
|
||||
|
||||
uint8_t **out_planes = mp_aframe_get_data_rw(out);
|
||||
if (!out_planes)
|
||||
goto error;
|
||||
int8_t *pout = out_planes[0];
|
||||
int out_offset = 0;
|
||||
if (s->bytes_queued >= s->bytes_queue) {
|
||||
int offset_in = fill_queue(af, data, 0);
|
||||
int8_t *pout = out->planes[0];
|
||||
while (s->bytes_queued >= s->bytes_queue) {
|
||||
int ti;
|
||||
float tf;
|
||||
int bytes_off = 0;
|
||||
@@ -308,12 +240,12 @@ static void process(struct mp_filter *f)
|
||||
if (s->output_overlap) {
|
||||
if (s->best_overlap_offset)
|
||||
bytes_off = s->best_overlap_offset(s);
|
||||
s->output_overlap(s, pout + out_offset, bytes_off);
|
||||
s->output_overlap(s, pout, bytes_off);
|
||||
}
|
||||
memcpy(pout + out_offset + s->bytes_overlap,
|
||||
memcpy(pout + s->bytes_overlap,
|
||||
s->buf_queue + bytes_off + s->bytes_overlap,
|
||||
s->bytes_standing);
|
||||
out_offset += s->bytes_stride;
|
||||
pout += s->bytes_stride;
|
||||
|
||||
// input stride
|
||||
memcpy(s->buf_overlap,
|
||||
@@ -323,304 +255,240 @@ static void process(struct mp_filter *f)
|
||||
ti = (int)tf;
|
||||
s->frames_stride_error = tf - ti;
|
||||
s->bytes_to_slide = ti * s->bytes_per_frame;
|
||||
|
||||
offset_in += fill_queue(af, data, offset_in);
|
||||
}
|
||||
// Drain remaining buffered data.
|
||||
if (drain && s->bytes_queued) {
|
||||
memcpy(pout + out_offset, s->buf_queue, s->bytes_queued);
|
||||
out_offset += s->bytes_queued;
|
||||
s->bytes_queued = 0;
|
||||
}
|
||||
mp_aframe_set_size(out, out_offset / s->bytes_per_frame);
|
||||
|
||||
// This filter can have a negative delay when scale > 1:
|
||||
// output corresponding to some length of input can be decided and written
|
||||
// after receiving only a part of that input.
|
||||
float delay = (out_offset * s->speed + s->bytes_queued - s->bytes_to_slide) /
|
||||
s->bytes_per_frame / mp_aframe_get_effective_rate(out)
|
||||
+ (s->in ? mp_aframe_duration(s->in) : 0);
|
||||
af->delay = (s->bytes_queued - s->bytes_to_slide) / s->scale
|
||||
/ out->sstride / out->rate;
|
||||
|
||||
if (s->current_pts != MP_NOPTS_VALUE)
|
||||
mp_aframe_set_pts(out, s->current_pts - delay);
|
||||
|
||||
mp_aframe_mul_speed(out, s->speed);
|
||||
|
||||
if (!mp_aframe_get_size(out))
|
||||
TA_FREEP(&out);
|
||||
|
||||
if (is_eof && out) {
|
||||
mp_pin_out_repeat_eof(s->in_pin);
|
||||
} else if (is_eof && !out) {
|
||||
mp_pin_in_write(f->ppins[1], MP_EOF_FRAME);
|
||||
} else if (!is_eof && !out) {
|
||||
mp_pin_out_request_data_next(s->in_pin);
|
||||
out->samples = (pout - (int8_t *)out->planes[0]) / out->sstride;
|
||||
talloc_free(data);
|
||||
if (out->samples) {
|
||||
af_add_output_frame(af, out);
|
||||
} else {
|
||||
talloc_free(out);
|
||||
}
|
||||
|
||||
if (out)
|
||||
mp_pin_in_write(f->ppins[1], MAKE_FRAME(MP_FRAME_AUDIO, out));
|
||||
|
||||
return;
|
||||
|
||||
error:
|
||||
TA_FREEP(&s->in);
|
||||
talloc_free(out);
|
||||
mp_filter_internal_mark_failed(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void update_speed(struct priv *s, float speed)
|
||||
static void update_speed(struct af_instance *af, float speed)
|
||||
{
|
||||
af_scaletempo_t *s = af->priv;
|
||||
|
||||
s->speed = speed;
|
||||
|
||||
double factor = (s->opts->speed_opt & SCALE_PITCH) ? 1.0 / s->speed : s->speed;
|
||||
s->scale = factor * s->opts->scale_nominal;
|
||||
double factor = (s->speed_opt & SCALE_PITCH) ? 1.0 / s->speed : s->speed;
|
||||
s->scale = factor * s->scale_nominal;
|
||||
|
||||
s->frames_stride_scaled = s->scale * s->frames_stride;
|
||||
s->frames_stride_error = MPMIN(s->frames_stride_error, s->frames_stride_scaled);
|
||||
}
|
||||
|
||||
static bool reinit(struct mp_filter *f)
|
||||
// Initialization and runtime control
|
||||
static int control(struct af_instance *af, int cmd, void *arg)
|
||||
{
|
||||
struct priv *s = f->priv;
|
||||
af_scaletempo_t *s = af->priv;
|
||||
switch (cmd) {
|
||||
case AF_CONTROL_REINIT: {
|
||||
struct mp_audio *data = (struct mp_audio *)arg;
|
||||
float srate = data->rate / 1000.0;
|
||||
int nch = data->nch;
|
||||
int use_int = 0;
|
||||
|
||||
mp_aframe_reset(s->cur_format);
|
||||
mp_audio_force_interleaved_format(data);
|
||||
mp_audio_copy_config(af->data, data);
|
||||
|
||||
float srate = mp_aframe_get_rate(s->in) / 1000.0;
|
||||
int nch = mp_aframe_get_channels(s->in);
|
||||
int format = mp_aframe_get_format(s->in);
|
||||
|
||||
int use_int = 0;
|
||||
if (format == AF_FORMAT_S16) {
|
||||
use_int = 1;
|
||||
} else if (format != AF_FORMAT_FLOAT) {
|
||||
return false;
|
||||
}
|
||||
int bps = use_int ? 2 : 4;
|
||||
|
||||
s->frames_stride = srate * s->opts->ms_stride;
|
||||
s->bytes_stride = s->frames_stride * bps * nch;
|
||||
|
||||
update_speed(s, s->speed);
|
||||
|
||||
int frames_overlap = s->frames_stride * s->opts->percent_overlap;
|
||||
if (frames_overlap <= 0) {
|
||||
s->bytes_standing = s->bytes_stride;
|
||||
s->samples_standing = s->bytes_standing / bps;
|
||||
s->output_overlap = NULL;
|
||||
s->bytes_overlap = 0;
|
||||
} else {
|
||||
s->samples_overlap = frames_overlap * nch;
|
||||
s->bytes_overlap = frames_overlap * nch * bps;
|
||||
s->bytes_standing = s->bytes_stride - s->bytes_overlap;
|
||||
s->samples_standing = s->bytes_standing / bps;
|
||||
s->buf_overlap = realloc(s->buf_overlap, s->bytes_overlap);
|
||||
s->table_blend = realloc(s->table_blend, s->bytes_overlap * 4);
|
||||
if (!s->buf_overlap || !s->table_blend) {
|
||||
MP_FATAL(f, "Out of memory\n");
|
||||
return false;
|
||||
}
|
||||
memset(s->buf_overlap, 0, s->bytes_overlap);
|
||||
if (use_int) {
|
||||
int32_t *pb = s->table_blend;
|
||||
int64_t blend = 0;
|
||||
for (int i = 0; i < frames_overlap; i++) {
|
||||
int32_t v = blend / frames_overlap;
|
||||
for (int j = 0; j < nch; j++)
|
||||
*pb++ = v;
|
||||
blend += 65536; // 2^16
|
||||
}
|
||||
s->output_overlap = output_overlap_s16;
|
||||
if (data->format == AF_FORMAT_S16) {
|
||||
use_int = 1;
|
||||
} else {
|
||||
float *pb = s->table_blend;
|
||||
for (int i = 0; i < frames_overlap; i++) {
|
||||
float v = i / (float)frames_overlap;
|
||||
for (int j = 0; j < nch; j++)
|
||||
*pb++ = v;
|
||||
}
|
||||
s->output_overlap = output_overlap_float;
|
||||
mp_audio_set_format(af->data, AF_FORMAT_FLOAT);
|
||||
}
|
||||
}
|
||||
int bps = af->data->bps;
|
||||
|
||||
s->frames_search = (frames_overlap > 1) ? srate * s->opts->ms_search : 0;
|
||||
if (s->frames_search <= 0)
|
||||
s->best_overlap_offset = NULL;
|
||||
else {
|
||||
if (use_int) {
|
||||
int64_t t = frames_overlap;
|
||||
int32_t n = 8589934588LL / (t * t); // 4 * (2^31 - 1) / t^2
|
||||
s->buf_pre_corr = realloc(s->buf_pre_corr,
|
||||
s->bytes_overlap * 2 + UNROLL_PADDING);
|
||||
s->table_window = realloc(s->table_window,
|
||||
s->bytes_overlap * 2 - nch * bps * 2);
|
||||
if (!s->buf_pre_corr || !s->table_window) {
|
||||
MP_FATAL(f, "Out of memory\n");
|
||||
return false;
|
||||
}
|
||||
memset((char *)s->buf_pre_corr + s->bytes_overlap * 2, 0,
|
||||
UNROLL_PADDING);
|
||||
int32_t *pw = s->table_window;
|
||||
for (int i = 1; i < frames_overlap; i++) {
|
||||
int32_t v = (i * (t - i) * n) >> 15;
|
||||
for (int j = 0; j < nch; j++)
|
||||
*pw++ = v;
|
||||
}
|
||||
s->best_overlap_offset = best_overlap_offset_s16;
|
||||
s->frames_stride = srate * s->ms_stride;
|
||||
s->bytes_stride = s->frames_stride * bps * nch;
|
||||
af->delay = 0;
|
||||
|
||||
update_speed(af, s->speed);
|
||||
|
||||
int frames_overlap = s->frames_stride * s->percent_overlap;
|
||||
if (frames_overlap <= 0) {
|
||||
s->bytes_standing = s->bytes_stride;
|
||||
s->samples_standing = s->bytes_standing / bps;
|
||||
s->output_overlap = NULL;
|
||||
s->bytes_overlap = 0;
|
||||
} else {
|
||||
s->buf_pre_corr = realloc(s->buf_pre_corr, s->bytes_overlap);
|
||||
s->table_window = realloc(s->table_window,
|
||||
s->bytes_overlap - nch * bps);
|
||||
if (!s->buf_pre_corr || !s->table_window) {
|
||||
MP_FATAL(f, "Out of memory\n");
|
||||
return false;
|
||||
s->samples_overlap = frames_overlap * nch;
|
||||
s->bytes_overlap = frames_overlap * nch * bps;
|
||||
s->bytes_standing = s->bytes_stride - s->bytes_overlap;
|
||||
s->samples_standing = s->bytes_standing / bps;
|
||||
s->buf_overlap = realloc(s->buf_overlap, s->bytes_overlap);
|
||||
s->table_blend = realloc(s->table_blend, s->bytes_overlap * 4);
|
||||
if (!s->buf_overlap || !s->table_blend) {
|
||||
MP_FATAL(af, "Out of memory\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
float *pw = s->table_window;
|
||||
for (int i = 1; i < frames_overlap; i++) {
|
||||
float v = i * (frames_overlap - i);
|
||||
for (int j = 0; j < nch; j++)
|
||||
*pw++ = v;
|
||||
memset(s->buf_overlap, 0, s->bytes_overlap);
|
||||
if (use_int) {
|
||||
int32_t *pb = s->table_blend;
|
||||
int64_t blend = 0;
|
||||
for (int i = 0; i < frames_overlap; i++) {
|
||||
int32_t v = blend / frames_overlap;
|
||||
for (int j = 0; j < nch; j++)
|
||||
*pb++ = v;
|
||||
blend += 65536; // 2^16
|
||||
}
|
||||
s->output_overlap = output_overlap_s16;
|
||||
} else {
|
||||
float *pb = s->table_blend;
|
||||
for (int i = 0; i < frames_overlap; i++) {
|
||||
float v = i / (float)frames_overlap;
|
||||
for (int j = 0; j < nch; j++)
|
||||
*pb++ = v;
|
||||
}
|
||||
s->output_overlap = output_overlap_float;
|
||||
}
|
||||
s->best_overlap_offset = best_overlap_offset_float;
|
||||
}
|
||||
}
|
||||
|
||||
s->bytes_per_frame = bps * nch;
|
||||
s->num_channels = nch;
|
||||
|
||||
s->bytes_queue = (s->frames_search + s->frames_stride + frames_overlap)
|
||||
* bps * nch;
|
||||
s->buf_queue = realloc(s->buf_queue, s->bytes_queue + UNROLL_PADDING);
|
||||
if (!s->buf_queue) {
|
||||
MP_FATAL(f, "Out of memory\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
s->bytes_queued = 0;
|
||||
s->bytes_to_slide = 0;
|
||||
|
||||
MP_DBG(f, ""
|
||||
"%.2f stride_in, %i stride_out, %i standing, "
|
||||
"%i overlap, %i search, %i queue, %s mode\n",
|
||||
s->frames_stride_scaled,
|
||||
(int)(s->bytes_stride / nch / bps),
|
||||
(int)(s->bytes_standing / nch / bps),
|
||||
(int)(s->bytes_overlap / nch / bps),
|
||||
s->frames_search,
|
||||
(int)(s->bytes_queue / nch / bps),
|
||||
(use_int ? "s16" : "float"));
|
||||
|
||||
mp_aframe_config_copy(s->cur_format, s->in);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool command(struct mp_filter *f, struct mp_filter_command *cmd)
|
||||
{
|
||||
struct priv *s = f->priv;
|
||||
|
||||
if (cmd->type == MP_FILTER_COMMAND_SET_SPEED) {
|
||||
if (s->opts->speed_opt & SCALE_TEMPO) {
|
||||
if (s->opts->speed_opt & SCALE_PITCH)
|
||||
return false;
|
||||
update_speed(s, cmd->speed);
|
||||
return true;
|
||||
} else if (s->opts->speed_opt & SCALE_PITCH) {
|
||||
update_speed(s, cmd->speed);
|
||||
return false; // do not signal OK
|
||||
s->frames_search = (frames_overlap > 1) ? srate * s->ms_search : 0;
|
||||
if (s->frames_search <= 0)
|
||||
s->best_overlap_offset = NULL;
|
||||
else {
|
||||
if (use_int) {
|
||||
int64_t t = frames_overlap;
|
||||
int32_t n = 8589934588LL / (t * t); // 4 * (2^31 - 1) / t^2
|
||||
s->buf_pre_corr = realloc(s->buf_pre_corr,
|
||||
s->bytes_overlap * 2 + UNROLL_PADDING);
|
||||
s->table_window = realloc(s->table_window,
|
||||
s->bytes_overlap * 2 - nch * bps * 2);
|
||||
if (!s->buf_pre_corr || !s->table_window) {
|
||||
MP_FATAL(af, "Out of memory\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
memset((char *)s->buf_pre_corr + s->bytes_overlap * 2, 0,
|
||||
UNROLL_PADDING);
|
||||
int32_t *pw = s->table_window;
|
||||
for (int i = 1; i < frames_overlap; i++) {
|
||||
int32_t v = (i * (t - i) * n) >> 15;
|
||||
for (int j = 0; j < nch; j++)
|
||||
*pw++ = v;
|
||||
}
|
||||
s->best_overlap_offset = best_overlap_offset_s16;
|
||||
} else {
|
||||
s->buf_pre_corr = realloc(s->buf_pre_corr, s->bytes_overlap);
|
||||
s->table_window = realloc(s->table_window,
|
||||
s->bytes_overlap - nch * bps);
|
||||
if (!s->buf_pre_corr || !s->table_window) {
|
||||
MP_FATAL(af, "Out of memory\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
float *pw = s->table_window;
|
||||
for (int i = 1; i < frames_overlap; i++) {
|
||||
float v = i * (frames_overlap - i);
|
||||
for (int j = 0; j < nch; j++)
|
||||
*pw++ = v;
|
||||
}
|
||||
s->best_overlap_offset = best_overlap_offset_float;
|
||||
}
|
||||
}
|
||||
|
||||
s->bytes_per_frame = bps * nch;
|
||||
s->num_channels = nch;
|
||||
|
||||
s->bytes_queue = (s->frames_search + s->frames_stride + frames_overlap)
|
||||
* bps * nch;
|
||||
s->buf_queue = realloc(s->buf_queue, s->bytes_queue + UNROLL_PADDING);
|
||||
if (!s->buf_queue) {
|
||||
MP_FATAL(af, "Out of memory\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
s->bytes_queued = 0;
|
||||
s->bytes_to_slide = 0;
|
||||
|
||||
MP_DBG(af, ""
|
||||
"%.2f stride_in, %i stride_out, %i standing, "
|
||||
"%i overlap, %i search, %i queue, %s mode\n",
|
||||
s->frames_stride_scaled,
|
||||
(int)(s->bytes_stride / nch / bps),
|
||||
(int)(s->bytes_standing / nch / bps),
|
||||
(int)(s->bytes_overlap / nch / bps),
|
||||
s->frames_search,
|
||||
(int)(s->bytes_queue / nch / bps),
|
||||
(use_int ? "s16" : "float"));
|
||||
|
||||
return af_test_output(af, (struct mp_audio *)arg);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void reset(struct mp_filter *f)
|
||||
{
|
||||
struct priv *s = f->priv;
|
||||
|
||||
s->current_pts = MP_NOPTS_VALUE;
|
||||
s->bytes_queued = 0;
|
||||
s->bytes_to_slide = 0;
|
||||
s->frames_stride_error = 0;
|
||||
if (s->buf_overlap && s->bytes_overlap)
|
||||
case AF_CONTROL_SET_PLAYBACK_SPEED: {
|
||||
double speed = *(double *)arg;
|
||||
if (s->speed_opt & SCALE_TEMPO) {
|
||||
if (s->speed_opt & SCALE_PITCH)
|
||||
break;
|
||||
update_speed(af, speed);
|
||||
} else if (s->speed_opt & SCALE_PITCH) {
|
||||
update_speed(af, speed);
|
||||
break; // do not signal OK
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_RESET:
|
||||
s->bytes_queued = 0;
|
||||
s->bytes_to_slide = 0;
|
||||
s->frames_stride_error = 0;
|
||||
memset(s->buf_overlap, 0, s->bytes_overlap);
|
||||
TA_FREEP(&s->in);
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static void destroy(struct mp_filter *f)
|
||||
// Deallocate memory
|
||||
static void uninit(struct af_instance *af)
|
||||
{
|
||||
struct priv *s = f->priv;
|
||||
af_scaletempo_t *s = af->priv;
|
||||
free(s->buf_queue);
|
||||
free(s->buf_overlap);
|
||||
free(s->buf_pre_corr);
|
||||
free(s->table_blend);
|
||||
free(s->table_window);
|
||||
TA_FREEP(&s->in);
|
||||
mp_filter_free_children(f);
|
||||
}
|
||||
|
||||
static const struct mp_filter_info af_scaletempo_filter = {
|
||||
.name = "scaletempo",
|
||||
.priv_size = sizeof(struct priv),
|
||||
.process = process,
|
||||
.command = command,
|
||||
.reset = reset,
|
||||
.destroy = destroy,
|
||||
};
|
||||
|
||||
static struct mp_filter *af_scaletempo_create(struct mp_filter *parent,
|
||||
void *options)
|
||||
// Allocate memory and set function pointers
|
||||
static int af_open(struct af_instance *af)
|
||||
{
|
||||
struct mp_filter *f = mp_filter_create(parent, &af_scaletempo_filter);
|
||||
if (!f) {
|
||||
talloc_free(options);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
mp_filter_add_pin(f, MP_PIN_IN, "in");
|
||||
mp_filter_add_pin(f, MP_PIN_OUT, "out");
|
||||
|
||||
struct priv *s = f->priv;
|
||||
s->opts = talloc_steal(s, options);
|
||||
s->speed = 1.0;
|
||||
s->cur_format = talloc_steal(s, mp_aframe_create());
|
||||
s->out_pool = mp_aframe_pool_create(s);
|
||||
|
||||
struct mp_autoconvert *conv = mp_autoconvert_create(f);
|
||||
if (!conv)
|
||||
abort();
|
||||
|
||||
mp_autoconvert_add_afmt(conv, AF_FORMAT_S16);
|
||||
mp_autoconvert_add_afmt(conv, AF_FORMAT_FLOAT);
|
||||
|
||||
mp_pin_connect(conv->f->pins[0], f->ppins[0]);
|
||||
s->in_pin = conv->f->pins[1];
|
||||
|
||||
return f;
|
||||
af->control = control;
|
||||
af->uninit = uninit;
|
||||
af->filter_frame = filter;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct f_opts
|
||||
#define OPT_BASE_STRUCT af_scaletempo_t
|
||||
|
||||
const struct mp_user_filter_entry af_scaletempo = {
|
||||
.desc = {
|
||||
.description = "Scale audio tempo while maintaining pitch",
|
||||
.name = "scaletempo",
|
||||
.priv_size = sizeof(OPT_BASE_STRUCT),
|
||||
.priv_defaults = &(const OPT_BASE_STRUCT) {
|
||||
.ms_stride = 60,
|
||||
.percent_overlap = .20,
|
||||
.ms_search = 14,
|
||||
.speed_opt = SCALE_TEMPO,
|
||||
.scale_nominal = 1.0,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
{"scale", OPT_FLOAT(scale_nominal), M_RANGE(0.01, DBL_MAX)},
|
||||
{"stride", OPT_FLOAT(ms_stride), M_RANGE(0.01, DBL_MAX)},
|
||||
{"overlap", OPT_FLOAT(percent_overlap), M_RANGE(0, 1)},
|
||||
{"search", OPT_FLOAT(ms_search), M_RANGE(0, DBL_MAX)},
|
||||
{"speed", OPT_CHOICE(speed_opt,
|
||||
{"pitch", SCALE_PITCH},
|
||||
{"tempo", SCALE_TEMPO},
|
||||
{"none", 0},
|
||||
{"both", SCALE_TEMPO | SCALE_PITCH})},
|
||||
{0}
|
||||
},
|
||||
const struct af_info af_info_scaletempo = {
|
||||
.info = "Scale audio tempo while maintaining pitch",
|
||||
.name = "scaletempo",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(af_scaletempo_t),
|
||||
.priv_defaults = &(const af_scaletempo_t) {
|
||||
.ms_stride = 60,
|
||||
.percent_overlap = .20,
|
||||
.ms_search = 14,
|
||||
.speed_opt = SCALE_TEMPO,
|
||||
.speed = 1.0,
|
||||
.scale_nominal = 1.0,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_FLOAT("scale", scale_nominal, M_OPT_MIN, .min = 0.01),
|
||||
OPT_FLOAT("stride", ms_stride, M_OPT_MIN, .min = 0.01),
|
||||
OPT_FLOAT("overlap", percent_overlap, M_OPT_RANGE, .min = 0, .max = 1),
|
||||
OPT_FLOAT("search", ms_search, M_OPT_MIN, .min = 0),
|
||||
OPT_CHOICE("speed", speed_opt, 0,
|
||||
({"pitch", SCALE_PITCH},
|
||||
{"tempo", SCALE_TEMPO},
|
||||
{"none", 0},
|
||||
{"both", SCALE_TEMPO | SCALE_PITCH})),
|
||||
{0}
|
||||
},
|
||||
.create = af_scaletempo_create,
|
||||
};
|
||||
@@ -1,245 +0,0 @@
|
||||
#include "audio/aframe.h"
|
||||
#include "audio/filter/af_scaletempo2_internals.h"
|
||||
#include "audio/format.h"
|
||||
#include "common/common.h"
|
||||
#include "filters/f_autoconvert.h"
|
||||
#include "filters/filter_internal.h"
|
||||
#include "filters/user_filters.h"
|
||||
#include "options/m_option.h"
|
||||
|
||||
struct priv {
|
||||
struct mp_scaletempo2 data;
|
||||
struct mp_pin *in_pin;
|
||||
struct mp_aframe *cur_format;
|
||||
struct mp_aframe_pool *out_pool;
|
||||
bool sent_final;
|
||||
struct mp_aframe *pending;
|
||||
bool initialized;
|
||||
double frame_delay;
|
||||
float speed;
|
||||
};
|
||||
|
||||
static bool init_scaletempo2(struct mp_filter *f);
|
||||
static void reset(struct mp_filter *f);
|
||||
|
||||
static void process(struct mp_filter *f)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
|
||||
if (!mp_pin_in_needs_data(f->ppins[1]))
|
||||
return;
|
||||
|
||||
while (!p->initialized || !p->pending ||
|
||||
!mp_scaletempo2_frames_available(&p->data))
|
||||
{
|
||||
bool eof = false;
|
||||
if (!p->pending || !mp_aframe_get_size(p->pending)) {
|
||||
struct mp_frame frame = mp_pin_out_read(p->in_pin);
|
||||
if (frame.type == MP_FRAME_AUDIO) {
|
||||
TA_FREEP(&p->pending);
|
||||
p->pending = frame.data;
|
||||
} else if (frame.type == MP_FRAME_EOF) {
|
||||
eof = true;
|
||||
} else if (frame.type) {
|
||||
MP_ERR(f, "unexpected frame type\n");
|
||||
goto error;
|
||||
} else {
|
||||
return; // no new data yet
|
||||
}
|
||||
}
|
||||
assert(p->pending || eof);
|
||||
|
||||
if (!p->initialized) {
|
||||
if (!p->pending) {
|
||||
mp_pin_in_write(f->ppins[1], MP_EOF_FRAME);
|
||||
return;
|
||||
}
|
||||
if (!init_scaletempo2(f))
|
||||
goto error;
|
||||
}
|
||||
|
||||
bool format_change =
|
||||
p->pending && !mp_aframe_config_equals(p->pending, p->cur_format);
|
||||
|
||||
bool final = format_change || eof;
|
||||
if (p->pending && !format_change && !p->sent_final) {
|
||||
int frame_size = mp_aframe_get_size(p->pending);
|
||||
uint8_t **planes = mp_aframe_get_data_ro(p->pending);
|
||||
int read = mp_scaletempo2_fill_input_buffer(&p->data,
|
||||
planes, frame_size, final);
|
||||
p->frame_delay += read;
|
||||
mp_aframe_skip_samples(p->pending, read);
|
||||
}
|
||||
p->sent_final |= final;
|
||||
|
||||
if (mp_scaletempo2_frames_available(&p->data)) {
|
||||
if (eof) {
|
||||
mp_pin_out_repeat_eof(p->in_pin); // drain more next time
|
||||
}
|
||||
} else if (final) {
|
||||
p->initialized = false;
|
||||
p->sent_final = false;
|
||||
if (eof) {
|
||||
mp_pin_in_write(f->ppins[1], MP_EOF_FRAME);
|
||||
return;
|
||||
} else if (format_change) {
|
||||
// go on with proper reinit on the next iteration
|
||||
p->initialized = false;
|
||||
p->sent_final = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert(p->pending);
|
||||
if (mp_scaletempo2_frames_available(&p->data)) {
|
||||
struct mp_aframe *out = mp_aframe_new_ref(p->cur_format);
|
||||
int out_samples = p->data.ola_hop_size;
|
||||
if (mp_aframe_pool_allocate(p->out_pool, out, out_samples) < 0) {
|
||||
talloc_free(out);
|
||||
goto error;
|
||||
}
|
||||
|
||||
mp_aframe_copy_attributes(out, p->pending);
|
||||
|
||||
uint8_t **planes = mp_aframe_get_data_rw(out);
|
||||
assert(planes);
|
||||
assert(mp_aframe_get_planes(out) == p->data.channels);
|
||||
|
||||
out_samples = mp_scaletempo2_fill_buffer(&p->data,
|
||||
(float**)planes, out_samples, p->speed);
|
||||
|
||||
double pts = mp_aframe_get_pts(p->pending);
|
||||
p->frame_delay -= out_samples * p->speed;
|
||||
|
||||
if (pts != MP_NOPTS_VALUE) {
|
||||
double delay = p->frame_delay / mp_aframe_get_effective_rate(out);
|
||||
mp_aframe_set_pts(out, pts - delay);
|
||||
}
|
||||
|
||||
mp_aframe_set_size(out, out_samples);
|
||||
mp_aframe_mul_speed(out, p->speed);
|
||||
mp_pin_in_write(f->ppins[1], MAKE_FRAME(MP_FRAME_AUDIO, out));
|
||||
}
|
||||
|
||||
return;
|
||||
error:
|
||||
mp_filter_internal_mark_failed(f);
|
||||
}
|
||||
|
||||
static bool init_scaletempo2(struct mp_filter *f)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
assert(p->pending);
|
||||
|
||||
if (mp_aframe_get_format(p->pending) != AF_FORMAT_FLOATP)
|
||||
return false;
|
||||
|
||||
mp_aframe_reset(p->cur_format);
|
||||
p->initialized = true;
|
||||
p->sent_final = false;
|
||||
p->frame_delay = 0;
|
||||
mp_aframe_config_copy(p->cur_format, p->pending);
|
||||
|
||||
mp_scaletempo2_init(&p->data, mp_aframe_get_channels(p->pending),
|
||||
mp_aframe_get_rate(p->pending));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool command(struct mp_filter *f, struct mp_filter_command *cmd)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
|
||||
switch (cmd->type) {
|
||||
case MP_FILTER_COMMAND_SET_SPEED:
|
||||
p->speed = cmd->speed;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void reset(struct mp_filter *f)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
mp_scaletempo2_reset(&p->data);
|
||||
p->frame_delay = 0;
|
||||
p->initialized = false;
|
||||
TA_FREEP(&p->pending);
|
||||
}
|
||||
|
||||
static void destroy(struct mp_filter *f)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
mp_scaletempo2_destroy(&p->data);
|
||||
talloc_free(p->pending);
|
||||
}
|
||||
|
||||
static const struct mp_filter_info af_scaletempo2_filter = {
|
||||
.name = "scaletempo2",
|
||||
.priv_size = sizeof(struct priv),
|
||||
.process = process,
|
||||
.command = command,
|
||||
.reset = reset,
|
||||
.destroy = destroy,
|
||||
};
|
||||
|
||||
static struct mp_filter *af_scaletempo2_create(
|
||||
struct mp_filter *parent, void *options)
|
||||
{
|
||||
struct mp_filter *f = mp_filter_create(parent, &af_scaletempo2_filter);
|
||||
if (!f) {
|
||||
talloc_free(options);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
mp_filter_add_pin(f, MP_PIN_IN, "in");
|
||||
mp_filter_add_pin(f, MP_PIN_OUT, "out");
|
||||
|
||||
struct priv *p = f->priv;
|
||||
p->data.opts = talloc_steal(p, options);
|
||||
p->speed = 1.0;
|
||||
p->cur_format = talloc_steal(p, mp_aframe_create());
|
||||
p->out_pool = mp_aframe_pool_create(p);
|
||||
p->pending = NULL;
|
||||
p->initialized = false;
|
||||
|
||||
struct mp_autoconvert *conv = mp_autoconvert_create(f);
|
||||
if (!conv)
|
||||
abort();
|
||||
|
||||
mp_autoconvert_add_afmt(conv, AF_FORMAT_FLOATP);
|
||||
|
||||
mp_pin_connect(conv->f->pins[0], f->ppins[0]);
|
||||
p->in_pin = conv->f->pins[1];
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct mp_scaletempo2_opts
|
||||
const struct mp_user_filter_entry af_scaletempo2 = {
|
||||
.desc = {
|
||||
.description = "Scale audio tempo while maintaining pitch"
|
||||
" (filter ported from chromium)",
|
||||
.name = "scaletempo2",
|
||||
.priv_size = sizeof(OPT_BASE_STRUCT),
|
||||
.priv_defaults = &(const OPT_BASE_STRUCT) {
|
||||
.min_playback_rate = 0.25,
|
||||
.max_playback_rate = 4.0,
|
||||
.ola_window_size_ms = 20,
|
||||
.wsola_search_interval_ms = 30,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
{"search-interval",
|
||||
OPT_FLOAT(wsola_search_interval_ms), M_RANGE(1, 1000)},
|
||||
{"window-size",
|
||||
OPT_FLOAT(ola_window_size_ms), M_RANGE(1, 1000)},
|
||||
{"min-speed",
|
||||
OPT_FLOAT(min_playback_rate), M_RANGE(0, FLT_MAX)},
|
||||
{"max-speed",
|
||||
OPT_FLOAT(max_playback_rate), M_RANGE(0, FLT_MAX)},
|
||||
{0}
|
||||
}
|
||||
},
|
||||
.create = af_scaletempo2_create,
|
||||
};
|
||||
@@ -1,724 +0,0 @@
|
||||
#include <float.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "audio/chmap.h"
|
||||
#include "audio/filter/af_scaletempo2_internals.h"
|
||||
|
||||
// Algorithm overview (from chromium):
|
||||
// Waveform Similarity Overlap-and-add (WSOLA).
|
||||
//
|
||||
// One WSOLA iteration
|
||||
//
|
||||
// 1) Extract |target_block| as input frames at indices
|
||||
// [|target_block_index|, |target_block_index| + |ola_window_size|).
|
||||
// Note that |target_block| is the "natural" continuation of the output.
|
||||
//
|
||||
// 2) Extract |search_block| as input frames at indices
|
||||
// [|search_block_index|,
|
||||
// |search_block_index| + |num_candidate_blocks| + |ola_window_size|).
|
||||
//
|
||||
// 3) Find a block within the |search_block| that is most similar
|
||||
// to |target_block|. Let |optimal_index| be the index of such block and
|
||||
// write it to |optimal_block|.
|
||||
//
|
||||
// 4) Update:
|
||||
// |optimal_block| = |transition_window| * |target_block| +
|
||||
// (1 - |transition_window|) * |optimal_block|.
|
||||
//
|
||||
// 5) Overlap-and-add |optimal_block| to the |wsola_output|.
|
||||
//
|
||||
// 6) Update:write
|
||||
|
||||
struct interval {
|
||||
int lo;
|
||||
int hi;
|
||||
};
|
||||
|
||||
static bool in_interval(int n, struct interval q)
|
||||
{
|
||||
return n >= q.lo && n <= q.hi;
|
||||
}
|
||||
|
||||
static float **realloc_2d(float **p, int x, int y)
|
||||
{
|
||||
float **array = realloc(p, sizeof(float*) * x + sizeof(float) * x * y);
|
||||
float* data = (float*) (array + x);
|
||||
for (int i = 0; i < x; ++i) {
|
||||
array[i] = data + i * y;
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
static void zero_2d(float **a, int x, int y)
|
||||
{
|
||||
memset(a + x, 0, sizeof(float) * x * y);
|
||||
}
|
||||
|
||||
static void zero_2d_partial(float **a, int x, int y)
|
||||
{
|
||||
for (int i = 0; i < x; ++i) {
|
||||
memset(a[i], 0, sizeof(float) * y);
|
||||
}
|
||||
}
|
||||
|
||||
// Energies of sliding windows of channels are interleaved.
|
||||
// The number windows is |input_frames| - (|frames_per_window| - 1), hence,
|
||||
// the method assumes |energy| must be, at least, of size
|
||||
// (|input_frames| - (|frames_per_window| - 1)) * |channels|.
|
||||
static void multi_channel_moving_block_energies(
|
||||
float **input, int input_frames, int channels,
|
||||
int frames_per_block, float *energy)
|
||||
{
|
||||
int num_blocks = input_frames - (frames_per_block - 1);
|
||||
|
||||
for (int k = 0; k < channels; ++k) {
|
||||
const float* input_channel = input[k];
|
||||
|
||||
energy[k] = 0;
|
||||
|
||||
// First block of channel |k|.
|
||||
for (int m = 0; m < frames_per_block; ++m) {
|
||||
energy[k] += input_channel[m] * input_channel[m];
|
||||
}
|
||||
|
||||
const float* slide_out = input_channel;
|
||||
const float* slide_in = input_channel + frames_per_block;
|
||||
for (int n = 1; n < num_blocks; ++n, ++slide_in, ++slide_out) {
|
||||
energy[k + n * channels] = energy[k + (n - 1) * channels]
|
||||
- *slide_out * *slide_out + *slide_in * *slide_in;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static float multi_channel_similarity_measure(
|
||||
const float* dot_prod_a_b,
|
||||
const float* energy_a, const float* energy_b,
|
||||
int channels)
|
||||
{
|
||||
const float epsilon = 1e-12f;
|
||||
float similarity_measure = 0.0f;
|
||||
for (int n = 0; n < channels; ++n) {
|
||||
similarity_measure += dot_prod_a_b[n]
|
||||
/ sqrtf(energy_a[n] * energy_b[n] + epsilon);
|
||||
}
|
||||
return similarity_measure;
|
||||
}
|
||||
|
||||
// Dot-product of channels of two AudioBus. For each AudioBus an offset is
|
||||
// given. |dot_product[k]| is the dot-product of channel |k|. The caller should
|
||||
// allocate sufficient space for |dot_product|.
|
||||
static void multi_channel_dot_product(
|
||||
float **a, int frame_offset_a,
|
||||
float **b, int frame_offset_b,
|
||||
int channels,
|
||||
int num_frames, float *dot_product)
|
||||
{
|
||||
assert(frame_offset_a >= 0);
|
||||
assert(frame_offset_b >= 0);
|
||||
|
||||
memset(dot_product, 0, sizeof(*dot_product) * channels);
|
||||
for (int k = 0; k < channels; ++k) {
|
||||
const float* ch_a = a[k] + frame_offset_a;
|
||||
const float* ch_b = b[k] + frame_offset_b;
|
||||
for (int n = 0; n < num_frames; ++n) {
|
||||
dot_product[k] += *ch_a++ * *ch_b++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fit the curve f(x) = a * x^2 + b * x + c such that
|
||||
// f(-1) = y[0]
|
||||
// f(0) = y[1]
|
||||
// f(1) = y[2]
|
||||
// and return the maximum, assuming that y[0] <= y[1] >= y[2].
|
||||
static void quadratic_interpolation(
|
||||
const float* y_values, float* extremum, float* extremum_value)
|
||||
{
|
||||
float a = 0.5f * (y_values[2] + y_values[0]) - y_values[1];
|
||||
float b = 0.5f * (y_values[2] - y_values[0]);
|
||||
float c = y_values[1];
|
||||
|
||||
if (a == 0.f) {
|
||||
// The coordinates are colinear (within floating-point error).
|
||||
*extremum = 0;
|
||||
*extremum_value = y_values[1];
|
||||
} else {
|
||||
*extremum = -b / (2.f * a);
|
||||
*extremum_value = a * (*extremum) * (*extremum) + b * (*extremum) + c;
|
||||
}
|
||||
}
|
||||
|
||||
// Search a subset of all candid blocks. The search is performed every
|
||||
// |decimation| frames. This reduces complexity by a factor of about
|
||||
// 1 / |decimation|. A cubic interpolation is used to have a better estimate of
|
||||
// the best match.
|
||||
static int decimated_search(
|
||||
int decimation, struct interval exclude_interval,
|
||||
float **target_block, int target_block_frames,
|
||||
float **search_segment, int search_segment_frames,
|
||||
int channels,
|
||||
const float *energy_target_block, const float *energy_candidate_blocks)
|
||||
{
|
||||
int num_candidate_blocks = search_segment_frames - (target_block_frames - 1);
|
||||
float dot_prod [MP_NUM_CHANNELS];
|
||||
float similarity[3]; // Three elements for cubic interpolation.
|
||||
|
||||
int n = 0;
|
||||
multi_channel_dot_product(
|
||||
target_block, 0,
|
||||
search_segment, n,
|
||||
channels,
|
||||
target_block_frames, dot_prod);
|
||||
similarity[0] = multi_channel_similarity_measure(
|
||||
dot_prod, energy_target_block,
|
||||
&energy_candidate_blocks[n * channels], channels);
|
||||
|
||||
// Set the starting point as optimal point.
|
||||
float best_similarity = similarity[0];
|
||||
int optimal_index = 0;
|
||||
|
||||
n += decimation;
|
||||
if (n >= num_candidate_blocks) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
multi_channel_dot_product(
|
||||
target_block, 0,
|
||||
search_segment, n,
|
||||
channels,
|
||||
target_block_frames, dot_prod);
|
||||
similarity[1] = multi_channel_similarity_measure(
|
||||
dot_prod, energy_target_block,
|
||||
&energy_candidate_blocks[n * channels], channels);
|
||||
|
||||
n += decimation;
|
||||
if (n >= num_candidate_blocks) {
|
||||
// We cannot do any more sampling. Compare these two values and return the
|
||||
// optimal index.
|
||||
return similarity[1] > similarity[0] ? decimation : 0;
|
||||
}
|
||||
|
||||
for (; n < num_candidate_blocks; n += decimation) {
|
||||
multi_channel_dot_product(
|
||||
target_block, 0,
|
||||
search_segment, n,
|
||||
channels,
|
||||
target_block_frames, dot_prod);
|
||||
|
||||
similarity[2] = multi_channel_similarity_measure(
|
||||
dot_prod, energy_target_block,
|
||||
&energy_candidate_blocks[n * channels], channels);
|
||||
|
||||
if ((similarity[1] > similarity[0] && similarity[1] >= similarity[2]) ||
|
||||
(similarity[1] >= similarity[0] && similarity[1] > similarity[2]))
|
||||
{
|
||||
// A local maximum is found. Do a cubic interpolation for a better
|
||||
// estimate of candidate maximum.
|
||||
float normalized_candidate_index;
|
||||
float candidate_similarity;
|
||||
quadratic_interpolation(similarity, &normalized_candidate_index,
|
||||
&candidate_similarity);
|
||||
|
||||
int candidate_index = n - decimation
|
||||
+ (int)(normalized_candidate_index * decimation + 0.5f);
|
||||
if (candidate_similarity > best_similarity
|
||||
&& !in_interval(candidate_index, exclude_interval)) {
|
||||
optimal_index = candidate_index;
|
||||
best_similarity = candidate_similarity;
|
||||
}
|
||||
} else if (n + decimation >= num_candidate_blocks &&
|
||||
similarity[2] > best_similarity &&
|
||||
!in_interval(n, exclude_interval))
|
||||
{
|
||||
// If this is the end-point and has a better similarity-measure than
|
||||
// optimal, then we accept it as optimal point.
|
||||
optimal_index = n;
|
||||
best_similarity = similarity[2];
|
||||
}
|
||||
memmove(similarity, &similarity[1], 2 * sizeof(*similarity));
|
||||
}
|
||||
return optimal_index;
|
||||
}
|
||||
|
||||
// Search [|low_limit|, |high_limit|] of |search_segment| to find a block that
|
||||
// is most similar to |target_block|. |energy_target_block| is the energy of the
|
||||
// |target_block|. |energy_candidate_blocks| is the energy of all blocks within
|
||||
// |search_block|.
|
||||
static int full_search(
|
||||
int low_limit, int high_limit,
|
||||
struct interval exclude_interval,
|
||||
float **target_block, int target_block_frames,
|
||||
float **search_block, int search_block_frames,
|
||||
int channels,
|
||||
const float* energy_target_block,
|
||||
const float* energy_candidate_blocks)
|
||||
{
|
||||
// int block_size = target_block->frames;
|
||||
float dot_prod [sizeof(float) * MP_NUM_CHANNELS];
|
||||
|
||||
float best_similarity = -FLT_MAX;//FLT_MIN;
|
||||
int optimal_index = 0;
|
||||
|
||||
for (int n = low_limit; n <= high_limit; ++n) {
|
||||
if (in_interval(n, exclude_interval)) {
|
||||
continue;
|
||||
}
|
||||
multi_channel_dot_product(target_block, 0, search_block, n, channels,
|
||||
target_block_frames, dot_prod);
|
||||
|
||||
float similarity = multi_channel_similarity_measure(
|
||||
dot_prod, energy_target_block,
|
||||
&energy_candidate_blocks[n * channels], channels);
|
||||
|
||||
if (similarity > best_similarity) {
|
||||
best_similarity = similarity;
|
||||
optimal_index = n;
|
||||
}
|
||||
}
|
||||
|
||||
return optimal_index;
|
||||
}
|
||||
|
||||
// Find the index of the block, within |search_block|, that is most similar
|
||||
// to |target_block|. Obviously, the returned index is w.r.t. |search_block|.
|
||||
// |exclude_interval| is an interval that is excluded from the search.
|
||||
static int compute_optimal_index(
|
||||
float **search_block, int search_block_frames,
|
||||
float **target_block, int target_block_frames,
|
||||
float *energy_candidate_blocks,
|
||||
int channels,
|
||||
struct interval exclude_interval)
|
||||
{
|
||||
int num_candidate_blocks = search_block_frames - (target_block_frames - 1);
|
||||
|
||||
// This is a compromise between complexity reduction and search accuracy. I
|
||||
// don't have a proof that down sample of order 5 is optimal.
|
||||
// One can compute a decimation factor that minimizes complexity given
|
||||
// the size of |search_block| and |target_block|. However, my experiments
|
||||
// show the rate of missing the optimal index is significant.
|
||||
// This value is chosen heuristically based on experiments.
|
||||
const int search_decimation = 5;
|
||||
|
||||
float energy_target_block [MP_NUM_CHANNELS];
|
||||
// energy_candidate_blocks must have at least size
|
||||
// sizeof(float) * channels * num_candidate_blocks
|
||||
|
||||
// Energy of all candid frames.
|
||||
multi_channel_moving_block_energies(
|
||||
search_block,
|
||||
search_block_frames,
|
||||
channels,
|
||||
target_block_frames,
|
||||
energy_candidate_blocks);
|
||||
|
||||
// Energy of target frame.
|
||||
multi_channel_dot_product(
|
||||
target_block, 0,
|
||||
target_block, 0,
|
||||
channels,
|
||||
target_block_frames, energy_target_block);
|
||||
|
||||
int optimal_index = decimated_search(
|
||||
search_decimation, exclude_interval,
|
||||
target_block, target_block_frames,
|
||||
search_block, search_block_frames,
|
||||
channels,
|
||||
energy_target_block,
|
||||
energy_candidate_blocks);
|
||||
|
||||
int lim_low = MPMAX(0, optimal_index - search_decimation);
|
||||
int lim_high = MPMIN(num_candidate_blocks - 1,
|
||||
optimal_index + search_decimation);
|
||||
return full_search(
|
||||
lim_low, lim_high, exclude_interval,
|
||||
target_block, target_block_frames,
|
||||
search_block, search_block_frames,
|
||||
channels,
|
||||
energy_target_block, energy_candidate_blocks);
|
||||
}
|
||||
|
||||
static void peek_buffer(struct mp_scaletempo2 *p,
|
||||
int frames, int read_offset, int write_offset, float **dest)
|
||||
{
|
||||
assert(p->input_buffer_frames >= frames);
|
||||
for (int i = 0; i < p->channels; ++i) {
|
||||
memcpy(dest[i] + write_offset,
|
||||
p->input_buffer[i] + read_offset,
|
||||
frames * sizeof(float));
|
||||
}
|
||||
}
|
||||
|
||||
static void seek_buffer(struct mp_scaletempo2 *p, int frames)
|
||||
{
|
||||
assert(p->input_buffer_frames >= frames);
|
||||
p->input_buffer_frames -= frames;
|
||||
for (int i = 0; i < p->channels; ++i) {
|
||||
memmove(p->input_buffer[i], p->input_buffer[i] + frames,
|
||||
p->input_buffer_frames * sizeof(float));
|
||||
}
|
||||
}
|
||||
|
||||
static void read_buffer(struct mp_scaletempo2 *p, int frames, float **dest)
|
||||
{
|
||||
peek_buffer(p, frames, 0, 0, dest);
|
||||
seek_buffer(p, frames);
|
||||
}
|
||||
|
||||
static int write_completed_frames_to(struct mp_scaletempo2 *p,
|
||||
int requested_frames, int dest_offset, float **dest)
|
||||
{
|
||||
int rendered_frames = MPMIN(p->num_complete_frames, requested_frames);
|
||||
|
||||
if (rendered_frames == 0)
|
||||
return 0; // There is nothing to read from |wsola_output|, return.
|
||||
|
||||
for (int i = 0; i < p->channels; ++i) {
|
||||
memcpy(dest[i] + dest_offset, p->wsola_output[i],
|
||||
rendered_frames * sizeof(float));
|
||||
}
|
||||
|
||||
// Remove the frames which are read.
|
||||
int frames_to_move = p->wsola_output_size - rendered_frames;
|
||||
for (int k = 0; k < p->channels; ++k) {
|
||||
float *ch = p->wsola_output[k];
|
||||
memmove(ch, &ch[rendered_frames], sizeof(*ch) * frames_to_move);
|
||||
}
|
||||
p->num_complete_frames -= rendered_frames;
|
||||
return rendered_frames;
|
||||
}
|
||||
|
||||
static bool can_perform_wsola(struct mp_scaletempo2 *p)
|
||||
{
|
||||
const int search_block_size = p->num_candidate_blocks
|
||||
+ (p->ola_window_size - 1);
|
||||
return p->target_block_index + p->ola_window_size <= p->input_buffer_frames
|
||||
&& p->search_block_index + search_block_size <= p->input_buffer_frames;
|
||||
}
|
||||
|
||||
// number of frames needed until a wsola iteration can be performed
|
||||
static int frames_needed(struct mp_scaletempo2 *p)
|
||||
{
|
||||
return MPMAX(0, MPMAX(
|
||||
p->target_block_index + p->ola_window_size - p->input_buffer_frames,
|
||||
p->search_block_index + p->search_block_size - p->input_buffer_frames));
|
||||
}
|
||||
|
||||
int mp_scaletempo2_fill_input_buffer(struct mp_scaletempo2 *p,
|
||||
uint8_t **planes, int frame_size, bool final)
|
||||
{
|
||||
int needed = frames_needed(p);
|
||||
int read = MPMIN(needed, frame_size);
|
||||
int total_fill = final ? needed : read;
|
||||
if (total_fill == 0) return 0;
|
||||
|
||||
assert(total_fill + p->input_buffer_frames <= p->input_buffer_size);
|
||||
|
||||
for (int i = 0; i < p->channels; ++i) {
|
||||
memcpy(p->input_buffer[i] + p->input_buffer_frames,
|
||||
planes[i], read * sizeof(float));
|
||||
for (int j = read; j < total_fill; ++j) {
|
||||
p->input_buffer[p->input_buffer_frames + j] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
p->input_buffer_frames += total_fill;
|
||||
return read;
|
||||
}
|
||||
|
||||
static bool target_is_within_search_region(struct mp_scaletempo2 *p)
|
||||
{
|
||||
const int search_block_size = p->num_candidate_blocks + (p->ola_window_size - 1);
|
||||
|
||||
return p->target_block_index >= p->search_block_index
|
||||
&& p->target_block_index + p->ola_window_size
|
||||
<= p->search_block_index + search_block_size;
|
||||
}
|
||||
|
||||
|
||||
static void peek_audio_with_zero_prepend(struct mp_scaletempo2 *p,
|
||||
int read_offset_frames, float **dest, int dest_frames)
|
||||
{
|
||||
assert(read_offset_frames + dest_frames <= p->input_buffer_frames);
|
||||
|
||||
int write_offset = 0;
|
||||
int num_frames_to_read = dest_frames;
|
||||
if (read_offset_frames < 0) {
|
||||
int num_zero_frames_appended = MPMIN(
|
||||
-read_offset_frames, num_frames_to_read);
|
||||
read_offset_frames = 0;
|
||||
num_frames_to_read -= num_zero_frames_appended;
|
||||
write_offset = num_zero_frames_appended;
|
||||
zero_2d_partial(dest, p->channels, num_zero_frames_appended);
|
||||
}
|
||||
peek_buffer(p, num_frames_to_read, read_offset_frames, write_offset, dest);
|
||||
}
|
||||
|
||||
static void get_optimal_block(struct mp_scaletempo2 *p)
|
||||
{
|
||||
int optimal_index = 0;
|
||||
|
||||
// An interval around last optimal block which is excluded from the search.
|
||||
// This is to reduce the buzzy sound. The number 160 is rather arbitrary and
|
||||
// derived heuristically.
|
||||
const int exclude_interval_length_frames = 160;
|
||||
if (target_is_within_search_region(p)) {
|
||||
optimal_index = p->target_block_index;
|
||||
peek_audio_with_zero_prepend(p,
|
||||
optimal_index, p->optimal_block, p->ola_window_size);
|
||||
} else {
|
||||
peek_audio_with_zero_prepend(p,
|
||||
p->target_block_index, p->target_block, p->ola_window_size);
|
||||
peek_audio_with_zero_prepend(p,
|
||||
p->search_block_index, p->search_block, p->search_block_size);
|
||||
int last_optimal = p->target_block_index
|
||||
- p->ola_hop_size - p->search_block_index;
|
||||
struct interval exclude_iterval = {
|
||||
.lo = last_optimal - exclude_interval_length_frames / 2,
|
||||
.hi = last_optimal + exclude_interval_length_frames / 2
|
||||
};
|
||||
|
||||
// |optimal_index| is in frames and it is relative to the beginning of the
|
||||
// |search_block|.
|
||||
optimal_index = compute_optimal_index(
|
||||
p->search_block, p->search_block_size,
|
||||
p->target_block, p->ola_window_size,
|
||||
p->energy_candidate_blocks,
|
||||
p->channels,
|
||||
exclude_iterval);
|
||||
|
||||
// Translate |index| w.r.t. the beginning of |audio_buffer| and extract the
|
||||
// optimal block.
|
||||
optimal_index += p->search_block_index;
|
||||
peek_audio_with_zero_prepend(p,
|
||||
optimal_index, p->optimal_block, p->ola_window_size);
|
||||
|
||||
// Make a transition from target block to the optimal block if different.
|
||||
// Target block has the best continuation to the current output.
|
||||
// Optimal block is the most similar block to the target, however, it might
|
||||
// introduce some discontinuity when over-lap-added. Therefore, we combine
|
||||
// them for a smoother transition. The length of transition window is twice
|
||||
// as that of the optimal-block which makes it like a weighting function
|
||||
// where target-block has higher weight close to zero (weight of 1 at index
|
||||
// 0) and lower weight close the end.
|
||||
for (int k = 0; k < p->channels; ++k) {
|
||||
float* ch_opt = p->optimal_block[k];
|
||||
float* ch_target = p->target_block[k];
|
||||
for (int n = 0; n < p->ola_window_size; ++n) {
|
||||
ch_opt[n] = ch_opt[n] * p->transition_window[n]
|
||||
+ ch_target[n] * p->transition_window[p->ola_window_size + n];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Next target is one hop ahead of the current optimal.
|
||||
p->target_block_index = optimal_index + p->ola_hop_size;
|
||||
}
|
||||
|
||||
static void update_output_time(struct mp_scaletempo2 *p,
|
||||
float playback_rate, double time_change)
|
||||
{
|
||||
p->output_time += time_change;
|
||||
// Center of the search region, in frames.
|
||||
int search_block_center_index = (int)(p->output_time * playback_rate + 0.5);
|
||||
p->search_block_index = search_block_center_index
|
||||
- p->search_block_center_offset;
|
||||
}
|
||||
|
||||
static void remove_old_input_frames(struct mp_scaletempo2 *p, float playback_rate)
|
||||
{
|
||||
const int earliest_used_index = MPMIN(
|
||||
p->target_block_index, p->search_block_index);
|
||||
if (earliest_used_index <= 0)
|
||||
return; // Nothing to remove.
|
||||
|
||||
// Remove frames from input and adjust indices accordingly.
|
||||
seek_buffer(p, earliest_used_index);
|
||||
p->target_block_index -= earliest_used_index;
|
||||
|
||||
// Adjust output index.
|
||||
double output_time_change = ((double) earliest_used_index) / playback_rate;
|
||||
assert(p->output_time >= output_time_change);
|
||||
update_output_time(p, playback_rate, -output_time_change);
|
||||
}
|
||||
|
||||
static bool run_one_wsola_iteration(struct mp_scaletempo2 *p, float playback_rate)
|
||||
{
|
||||
if (!can_perform_wsola(p)){
|
||||
return false;
|
||||
}
|
||||
|
||||
get_optimal_block(p);
|
||||
|
||||
// Overlap-and-add.
|
||||
for (int k = 0; k < p->channels; ++k) {
|
||||
float* ch_opt_frame = p->optimal_block[k];
|
||||
float* ch_output = p->wsola_output[k] + p->num_complete_frames;
|
||||
for (int n = 0; n < p->ola_hop_size; ++n) {
|
||||
ch_output[n] = ch_output[n] * p->ola_window[p->ola_hop_size + n] +
|
||||
ch_opt_frame[n] * p->ola_window[n];
|
||||
}
|
||||
|
||||
// Copy the second half to the output.
|
||||
memcpy(&ch_output[p->ola_hop_size], &ch_opt_frame[p->ola_hop_size],
|
||||
sizeof(*ch_opt_frame) * p->ola_hop_size);
|
||||
}
|
||||
|
||||
p->num_complete_frames += p->ola_hop_size;
|
||||
update_output_time(p, playback_rate, p->ola_hop_size);
|
||||
remove_old_input_frames(p, playback_rate);
|
||||
return true;
|
||||
}
|
||||
|
||||
int mp_scaletempo2_fill_buffer(struct mp_scaletempo2 *p,
|
||||
float **dest, int dest_size, float playback_rate)
|
||||
{
|
||||
if (playback_rate == 0) return 0;
|
||||
|
||||
// Optimize the muted case to issue a single clear instead of performing
|
||||
// the full crossfade and clearing each crossfaded frame.
|
||||
if (playback_rate < p->opts->min_playback_rate
|
||||
|| (playback_rate > p->opts->max_playback_rate
|
||||
&& p->opts->max_playback_rate > 0))
|
||||
{
|
||||
int frames_to_render = MPMIN(dest_size,
|
||||
(int) (p->input_buffer_frames / playback_rate));
|
||||
|
||||
// Compute accurate number of frames to actually skip in the source data.
|
||||
// Includes the leftover partial frame from last request. However, we can
|
||||
// only skip over complete frames, so a partial frame may remain for next
|
||||
// time.
|
||||
p->muted_partial_frame += frames_to_render * playback_rate;
|
||||
int seek_frames = (int) (p->muted_partial_frame);
|
||||
zero_2d_partial(dest, p->channels, frames_to_render);
|
||||
seek_buffer(p, seek_frames);
|
||||
|
||||
// Determine the partial frame that remains to be skipped for next call. If
|
||||
// the user switches back to playing, it may be off time by this partial
|
||||
// frame, which would be undetectable. If they subsequently switch to
|
||||
// another playback rate that mutes, the code will attempt to line up the
|
||||
// frames again.
|
||||
p->muted_partial_frame -= seek_frames;
|
||||
return frames_to_render;
|
||||
}
|
||||
|
||||
int slower_step = (int) ceilf(p->ola_window_size * playback_rate);
|
||||
int faster_step = (int) ceilf(p->ola_window_size / playback_rate);
|
||||
|
||||
// Optimize the most common |playback_rate| ~= 1 case to use a single copy
|
||||
// instead of copying frame by frame.
|
||||
if (p->ola_window_size <= faster_step && slower_step >= p->ola_window_size) {
|
||||
int frames_to_copy = MPMIN(dest_size, p->input_buffer_frames);
|
||||
read_buffer(p, frames_to_copy, dest);
|
||||
return frames_to_copy;
|
||||
}
|
||||
|
||||
int rendered_frames = 0;
|
||||
do {
|
||||
rendered_frames += write_completed_frames_to(p,
|
||||
dest_size - rendered_frames, rendered_frames, dest);
|
||||
} while (rendered_frames < dest_size
|
||||
&& run_one_wsola_iteration(p, playback_rate));
|
||||
return rendered_frames;
|
||||
}
|
||||
|
||||
bool mp_scaletempo2_frames_available(struct mp_scaletempo2 *p)
|
||||
{
|
||||
return can_perform_wsola(p) || p->num_complete_frames > 0;
|
||||
}
|
||||
|
||||
void mp_scaletempo2_destroy(struct mp_scaletempo2 *p)
|
||||
{
|
||||
free(p->ola_window);
|
||||
free(p->transition_window);
|
||||
free(p->wsola_output);
|
||||
free(p->optimal_block);
|
||||
free(p->search_block);
|
||||
free(p->target_block);
|
||||
free(p->input_buffer);
|
||||
free(p->energy_candidate_blocks);
|
||||
}
|
||||
|
||||
void mp_scaletempo2_reset(struct mp_scaletempo2 *p)
|
||||
{
|
||||
p->input_buffer_frames = 0;
|
||||
p->output_time = 0.0;
|
||||
p->search_block_index = 0;
|
||||
p->target_block_index = 0;
|
||||
// Clear the queue of decoded packets.
|
||||
zero_2d(p->wsola_output, p->channels, p->wsola_output_size);
|
||||
p->num_complete_frames = 0;
|
||||
}
|
||||
|
||||
// Return a "periodic" Hann window. This is the first L samples of an L+1
|
||||
// Hann window. It is perfect reconstruction for overlap-and-add.
|
||||
static void get_symmetric_hanning_window(int window_length, float* window)
|
||||
{
|
||||
const float scale = 2.0f * M_PI / window_length;
|
||||
for (int n = 0; n < window_length; ++n)
|
||||
window[n] = 0.5f * (1.0f - cosf(n * scale));
|
||||
}
|
||||
|
||||
|
||||
void mp_scaletempo2_init(struct mp_scaletempo2 *p, int channels, int rate)
|
||||
{
|
||||
p->muted_partial_frame = 0;
|
||||
p->output_time = 0;
|
||||
p->search_block_center_offset = 0;
|
||||
p->search_block_index = 0;
|
||||
p->num_complete_frames = 0;
|
||||
p->channels = channels;
|
||||
|
||||
p->samples_per_second = rate;
|
||||
p->num_candidate_blocks = (int)(p->opts->wsola_search_interval_ms
|
||||
* p->samples_per_second / 1000);
|
||||
p->ola_window_size = (int)(p->opts->ola_window_size_ms
|
||||
* p->samples_per_second / 1000);
|
||||
// Make sure window size in an even number.
|
||||
p->ola_window_size += p->ola_window_size & 1;
|
||||
p->ola_hop_size = p->ola_window_size / 2;
|
||||
// |num_candidate_blocks| / 2 is the offset of the center of the search
|
||||
// block to the center of the first (left most) candidate block. The offset
|
||||
// of the center of a candidate block to its left most point is
|
||||
// |ola_window_size| / 2 - 1. Note that |ola_window_size| is even and in
|
||||
// our convention the center belongs to the left half, so we need to subtract
|
||||
// one frame to get the correct offset.
|
||||
//
|
||||
// Search Block
|
||||
// <------------------------------------------->
|
||||
//
|
||||
// |ola_window_size| / 2 - 1
|
||||
// <----
|
||||
//
|
||||
// |num_candidate_blocks| / 2
|
||||
// <----------------
|
||||
// center
|
||||
// X----X----------------X---------------X-----X
|
||||
// <----------> <---------->
|
||||
// Candidate ... Candidate
|
||||
// 1, ... |num_candidate_blocks|
|
||||
p->search_block_center_offset = p->num_candidate_blocks / 2
|
||||
+ (p->ola_window_size / 2 - 1);
|
||||
p->ola_window = realloc(p->ola_window, sizeof(float) * p->ola_window_size);
|
||||
get_symmetric_hanning_window(p->ola_window_size, p->ola_window);
|
||||
p->transition_window = realloc(p->transition_window,
|
||||
sizeof(float) * p->ola_window_size * 2);
|
||||
get_symmetric_hanning_window(2 * p->ola_window_size, p->transition_window);
|
||||
|
||||
p->wsola_output_size = p->ola_window_size + p->ola_hop_size;
|
||||
p->wsola_output = realloc_2d(p->wsola_output, p->channels, p->wsola_output_size);
|
||||
// Initialize for overlap-and-add of the first block.
|
||||
zero_2d(p->wsola_output, p->channels, p->wsola_output_size);
|
||||
|
||||
// Auxiliary containers.
|
||||
p->optimal_block = realloc_2d(p->optimal_block, p->channels, p->ola_window_size);
|
||||
p->search_block_size = p->num_candidate_blocks + (p->ola_window_size - 1);
|
||||
p->search_block = realloc_2d(p->search_block, p->channels, p->search_block_size);
|
||||
p->target_block = realloc_2d(p->target_block, p->channels, p->ola_window_size);
|
||||
|
||||
p->input_buffer_size = 4 * MPMAX(p->ola_window_size, p->search_block_size);
|
||||
p->input_buffer = realloc_2d(p->input_buffer, p->channels, p->input_buffer_size);
|
||||
p->input_buffer_frames = 0;
|
||||
|
||||
p->energy_candidate_blocks = realloc(p->energy_candidate_blocks,
|
||||
sizeof(float) * p->channels * p->num_candidate_blocks);
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
// This filter was ported from Chromium
|
||||
// (https://chromium.googlesource.com/chromium/chromium/+/51ed77e3f37a9a9b80d6d0a8259e84a8ca635259/media/filters/audio_renderer_algorithm.cc)
|
||||
//
|
||||
// Copyright 2015 The Chromium Authors. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include "common/common.h"
|
||||
|
||||
struct mp_scaletempo2_opts {
|
||||
// Max/min supported playback rates for fast/slow audio. Audio outside of these
|
||||
// ranges are muted.
|
||||
// Audio at these speeds would sound better under a frequency domain algorithm.
|
||||
float min_playback_rate;
|
||||
float max_playback_rate;
|
||||
// Overlap-and-add window size in milliseconds.
|
||||
float ola_window_size_ms;
|
||||
// Size of search interval in milliseconds. The search interval is
|
||||
// [-delta delta] around |output_index| * |playback_rate|. So the search
|
||||
// interval is 2 * delta.
|
||||
float wsola_search_interval_ms;
|
||||
};
|
||||
|
||||
struct mp_scaletempo2 {
|
||||
struct mp_scaletempo2_opts *opts;
|
||||
// Number of channels in audio stream.
|
||||
int channels;
|
||||
// Sample rate of audio stream.
|
||||
int samples_per_second;
|
||||
// If muted, keep track of partial frames that should have been skipped over.
|
||||
double muted_partial_frame;
|
||||
// Book keeping of the current time of generated audio, in frames. This
|
||||
// should be appropriately updated when out samples are generated, regardless
|
||||
// of whether we push samples out when fill_buffer() is called or we store
|
||||
// audio in |wsola_output| for the subsequent calls to fill_buffer().
|
||||
// Furthermore, if samples from |audio_buffer| are evicted then this
|
||||
// member variable should be updated based on |playback_rate|.
|
||||
// Note that this member should be updated ONLY by calling update_output_time(),
|
||||
// so that |search_block_index| is update accordingly.
|
||||
double output_time;
|
||||
// The offset of the center frame of |search_block| w.r.t. its first frame.
|
||||
int search_block_center_offset;
|
||||
// Index of the beginning of the |search_block|, in frames.
|
||||
int search_block_index;
|
||||
// Number of Blocks to search to find the most similar one to the target
|
||||
// frame.
|
||||
int num_candidate_blocks;
|
||||
// Index of the beginning of the target block, counted in frames.
|
||||
int target_block_index;
|
||||
// Overlap-and-add window size in frames.
|
||||
int ola_window_size;
|
||||
// The hop size of overlap-and-add in frames. This implementation assumes 50%
|
||||
// overlap-and-add.
|
||||
int ola_hop_size;
|
||||
// Number of frames in |wsola_output| that overlap-and-add is completed for
|
||||
// them and can be copied to output if fill_buffer() is called. It also
|
||||
// specifies the index where the next WSOLA window has to overlap-and-add.
|
||||
int num_complete_frames;
|
||||
// Overlap-and-add window.
|
||||
float *ola_window;
|
||||
// Transition window, used to update |optimal_block| by a weighted sum of
|
||||
// |optimal_block| and |target_block|.
|
||||
float *transition_window;
|
||||
// This stores a part of the output that is created but couldn't be rendered.
|
||||
// Output is generated frame-by-frame which at some point might exceed the
|
||||
// number of requested samples. Furthermore, due to overlap-and-add,
|
||||
// the last half-window of the output is incomplete, which is stored in this
|
||||
// buffer.
|
||||
float **wsola_output;
|
||||
int wsola_output_size;
|
||||
// Auxiliary variables to avoid allocation in every iteration.
|
||||
// Stores the optimal block in every iteration. This is the most
|
||||
// similar block to |target_block| within |search_block| and it is
|
||||
// overlap-and-added to |wsola_output|.
|
||||
float **optimal_block;
|
||||
// A block of data that search is performed over to find the |optimal_block|.
|
||||
float **search_block;
|
||||
int search_block_size;
|
||||
// Stores the target block, denoted as |target| above. |search_block| is
|
||||
// searched for a block (|optimal_block|) that is most similar to
|
||||
// |target_block|.
|
||||
float **target_block;
|
||||
// Buffered audio data.
|
||||
float **input_buffer;
|
||||
int input_buffer_size;
|
||||
int input_buffer_frames;
|
||||
float *energy_candidate_blocks;
|
||||
};
|
||||
|
||||
void mp_scaletempo2_destroy(struct mp_scaletempo2 *p);
|
||||
void mp_scaletempo2_reset(struct mp_scaletempo2 *p);
|
||||
void mp_scaletempo2_init(struct mp_scaletempo2 *p, int channels, int rate);
|
||||
int mp_scaletempo2_fill_input_buffer(struct mp_scaletempo2 *p,
|
||||
uint8_t **planes, int frame_size, bool final);
|
||||
int mp_scaletempo2_fill_buffer(struct mp_scaletempo2 *p,
|
||||
float **dest, int dest_size, float playback_rate);
|
||||
bool mp_scaletempo2_frames_available(struct mp_scaletempo2 *p);
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2002 Anders Johansson ajh@atri.curtin.edu.au
|
||||
*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MPLAYER_EQUALIZER_H
|
||||
#define MPLAYER_EQUALIZER_H
|
||||
|
||||
/* Equalizer plugin header file defines struct used for setting or
|
||||
getting the gain of a specific channel and frequency */
|
||||
|
||||
typedef struct equalizer_s
|
||||
{
|
||||
float gain; // Gain in dB -15 - 15
|
||||
int channel; // Channel number 0 - 5
|
||||
int band; // Frequency band 0 - 9
|
||||
}equalizer_t;
|
||||
|
||||
/* The different frequency bands are:
|
||||
nr. center frequency
|
||||
0 31.25 Hz
|
||||
1 62.50 Hz
|
||||
2 125.0 Hz
|
||||
3 250.0 Hz
|
||||
4 500.0 Hz
|
||||
5 1.000 kHz
|
||||
6 2.000 kHz
|
||||
7 4.000 kHz
|
||||
8 8.000 kHz
|
||||
9 16.00 kHz
|
||||
*/
|
||||
|
||||
#endif /* MPLAYER_EQUALIZER_H */
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "af.h"
|
||||
|
||||
/* Convert from ms to sample time */
|
||||
int af_from_ms(int n, float* in, int* out, int rate, float mi, float ma)
|
||||
{
|
||||
int i = 0;
|
||||
// Sanity check
|
||||
if(!in || !out)
|
||||
return AF_ERROR;
|
||||
|
||||
for(i=0;i<n;i++)
|
||||
out[i]=(int)((float)rate * MPCLAMP(in[i],mi,ma)/1000.0);
|
||||
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* test if output format matches
|
||||
* af: audio filter
|
||||
* out: needed format, will be overwritten by available
|
||||
* format if they do not match
|
||||
* returns: AF_FALSE if formats do not match, AF_OK if they match
|
||||
*
|
||||
* compares the format, rate and nch values of af->data with out
|
||||
* Note: logically, *out=*af->data always happens, because out contains the
|
||||
* format only, no actual audio data or memory allocations. *out always
|
||||
* contains the parameters from af->data after the function returns.
|
||||
*/
|
||||
int af_test_output(struct af_instance* af, struct mp_audio* out)
|
||||
{
|
||||
if((af->data->format != out->format) ||
|
||||
(af->data->bps != out->bps) ||
|
||||
(af->data->rate != out->rate) ||
|
||||
!mp_chmap_equals(&af->data->channels, &out->channels)){
|
||||
*out = *af->data;
|
||||
return AF_FALSE;
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
/* Soft clipping, the sound of a dream, thanks to Jon Wattes
|
||||
post to Musicdsp.org */
|
||||
float af_softclip(float a)
|
||||
{
|
||||
if (a >= M_PI/2)
|
||||
return 1.0;
|
||||
else if (a <= -M_PI/2)
|
||||
return -1.0;
|
||||
else
|
||||
return sin(a);
|
||||
}
|
||||
@@ -27,14 +27,12 @@ static const struct {
|
||||
{AV_SAMPLE_FMT_U8, AF_FORMAT_U8},
|
||||
{AV_SAMPLE_FMT_S16, AF_FORMAT_S16},
|
||||
{AV_SAMPLE_FMT_S32, AF_FORMAT_S32},
|
||||
{AV_SAMPLE_FMT_S64, AF_FORMAT_S64},
|
||||
{AV_SAMPLE_FMT_FLT, AF_FORMAT_FLOAT},
|
||||
{AV_SAMPLE_FMT_DBL, AF_FORMAT_DOUBLE},
|
||||
|
||||
{AV_SAMPLE_FMT_U8P, AF_FORMAT_U8P},
|
||||
{AV_SAMPLE_FMT_S16P, AF_FORMAT_S16P},
|
||||
{AV_SAMPLE_FMT_S32P, AF_FORMAT_S32P},
|
||||
{AV_SAMPLE_FMT_S64P, AF_FORMAT_S64P},
|
||||
{AV_SAMPLE_FMT_FLTP, AF_FORMAT_FLOATP},
|
||||
{AV_SAMPLE_FMT_DBLP, AF_FORMAT_DOUBLEP},
|
||||
|
||||
|
||||
+27
-22
@@ -29,7 +29,6 @@ int af_fmt_to_bytes(int format)
|
||||
case AF_FORMAT_U8: return 1;
|
||||
case AF_FORMAT_S16: return 2;
|
||||
case AF_FORMAT_S32: return 4;
|
||||
case AF_FORMAT_S64: return 8;
|
||||
case AF_FORMAT_FLOAT: return 4;
|
||||
case AF_FORMAT_DOUBLE: return 8;
|
||||
}
|
||||
@@ -56,6 +55,12 @@ bool af_fmt_is_int(int format)
|
||||
return format && !af_fmt_is_spdif(format) && !af_fmt_is_float(format);
|
||||
}
|
||||
|
||||
// false for interleaved and AF_FORMAT_UNKNOWN
|
||||
bool af_fmt_is_planar(int format)
|
||||
{
|
||||
return format && af_fmt_to_planar(format) == format;
|
||||
}
|
||||
|
||||
bool af_fmt_is_spdif(int format)
|
||||
{
|
||||
return af_format_sample_alignment(format) > 1;
|
||||
@@ -70,35 +75,27 @@ static const int planar_formats[][2] = {
|
||||
{AF_FORMAT_U8P, AF_FORMAT_U8},
|
||||
{AF_FORMAT_S16P, AF_FORMAT_S16},
|
||||
{AF_FORMAT_S32P, AF_FORMAT_S32},
|
||||
{AF_FORMAT_S64P, AF_FORMAT_S64},
|
||||
{AF_FORMAT_FLOATP, AF_FORMAT_FLOAT},
|
||||
{AF_FORMAT_DOUBLEP, AF_FORMAT_DOUBLE},
|
||||
};
|
||||
|
||||
bool af_fmt_is_planar(int format)
|
||||
{
|
||||
for (int n = 0; n < MP_ARRAY_SIZE(planar_formats); n++) {
|
||||
if (planar_formats[n][0] == format)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Return the planar format corresponding to the given format.
|
||||
// If the format is already planar or if there's no equivalent,
|
||||
// return it.
|
||||
// If the format is already planar, return it.
|
||||
// Return 0 if there's no equivalent.
|
||||
int af_fmt_to_planar(int format)
|
||||
{
|
||||
for (int n = 0; n < MP_ARRAY_SIZE(planar_formats); n++) {
|
||||
if (planar_formats[n][1] == format)
|
||||
return planar_formats[n][0];
|
||||
if (planar_formats[n][0] == format)
|
||||
return format;
|
||||
}
|
||||
return format;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Return the interleaved format corresponding to the given format.
|
||||
// If the format is already interleaved or if there's no equivalent,
|
||||
// return it.
|
||||
// If the format is already interleaved, return it.
|
||||
// Always succeeds if format is actually planar; otherwise return 0.
|
||||
int af_fmt_from_planar(int format)
|
||||
{
|
||||
for (int n = 0; n < MP_ARRAY_SIZE(planar_formats); n++) {
|
||||
@@ -119,13 +116,11 @@ const char *af_fmt_to_str(int format)
|
||||
case AF_FORMAT_U8: return "u8";
|
||||
case AF_FORMAT_S16: return "s16";
|
||||
case AF_FORMAT_S32: return "s32";
|
||||
case AF_FORMAT_S64: return "s64";
|
||||
case AF_FORMAT_FLOAT: return "float";
|
||||
case AF_FORMAT_DOUBLE: return "double";
|
||||
case AF_FORMAT_U8P: return "u8p";
|
||||
case AF_FORMAT_S16P: return "s16p";
|
||||
case AF_FORMAT_S32P: return "s32p";
|
||||
case AF_FORMAT_S64P: return "s64p";
|
||||
case AF_FORMAT_FLOATP: return "floatp";
|
||||
case AF_FORMAT_DOUBLEP: return "doublep";
|
||||
case AF_FORMAT_S_AAC: return "spdif-aac";
|
||||
@@ -139,6 +134,17 @@ const char *af_fmt_to_str(int format)
|
||||
return "??";
|
||||
}
|
||||
|
||||
int af_fmt_seconds_to_bytes(int format, float seconds, int channels, int samplerate)
|
||||
{
|
||||
assert(!af_fmt_is_planar(format));
|
||||
int bps = af_fmt_to_bytes(format);
|
||||
int framelen = channels * bps;
|
||||
int bytes = seconds * bps * samplerate;
|
||||
if (bytes % framelen)
|
||||
bytes += framelen - (bytes % framelen);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
void af_fill_silence(void *dst, size_t bytes, int format)
|
||||
{
|
||||
memset(dst, af_fmt_is_unsigned(format) ? 0x80 : 0, bytes);
|
||||
@@ -148,7 +154,7 @@ void af_fill_silence(void *dst, size_t bytes, int format)
|
||||
// If the formats are equal, 1024 is returned. If they are gravely incompatible
|
||||
// (like s16<->ac3), INT_MIN is returned. If there is implied loss of precision
|
||||
// (like s16->s8), a value <0 is returned.
|
||||
int af_format_conversion_score(int dst_format, int src_format)
|
||||
static int af_format_conversion_score(int dst_format, int src_format)
|
||||
{
|
||||
if (dst_format == AF_FORMAT_UNKNOWN || src_format == AF_FORMAT_UNKNOWN)
|
||||
return INT_MIN;
|
||||
@@ -203,11 +209,10 @@ static int cmp_entry(const void *a, const void *b)
|
||||
// and the list is terminated with 0 (AF_FORMAT_UNKNOWN).
|
||||
// Keep in mind that this also returns formats with flipped interleaving
|
||||
// (e.g. for s16, it returns [s16, s16p, ...]).
|
||||
// out_formats must be an int[AF_FORMAT_COUNT + 1] array.
|
||||
void af_get_best_sample_formats(int src_format, int *out_formats)
|
||||
void af_get_best_sample_formats(int src_format, int out_formats[AF_FORMAT_COUNT])
|
||||
{
|
||||
int num = 0;
|
||||
struct entry e[AF_FORMAT_COUNT + 1];
|
||||
struct entry e[AF_FORMAT_COUNT];
|
||||
for (int fmt = 1; fmt < AF_FORMAT_COUNT; fmt++) {
|
||||
int score = af_format_conversion_score(fmt, src_format);
|
||||
if (score > INT_MIN)
|
||||
|
||||
+4
-4
@@ -27,7 +27,6 @@ enum af_format {
|
||||
AF_FORMAT_U8,
|
||||
AF_FORMAT_S16,
|
||||
AF_FORMAT_S32,
|
||||
AF_FORMAT_S64,
|
||||
AF_FORMAT_FLOAT,
|
||||
AF_FORMAT_DOUBLE,
|
||||
|
||||
@@ -35,7 +34,6 @@ enum af_format {
|
||||
AF_FORMAT_U8P,
|
||||
AF_FORMAT_S16P,
|
||||
AF_FORMAT_S32P,
|
||||
AF_FORMAT_S64P,
|
||||
AF_FORMAT_FLOATP,
|
||||
AF_FORMAT_DOUBLEP,
|
||||
|
||||
@@ -66,10 +64,12 @@ bool af_fmt_is_pcm(int format);
|
||||
int af_fmt_to_planar(int format);
|
||||
int af_fmt_from_planar(int format);
|
||||
|
||||
// Amount of bytes that contain audio of the given duration, aligned to frames.
|
||||
int af_fmt_seconds_to_bytes(int format, float seconds, int channels, int samplerate);
|
||||
|
||||
void af_fill_silence(void *dst, size_t bytes, int format);
|
||||
|
||||
void af_get_best_sample_formats(int src_format, int *out_formats);
|
||||
int af_format_conversion_score(int dst_format, int src_format);
|
||||
void af_get_best_sample_formats(int src_format, int out_formats[AF_FORMAT_COUNT]);
|
||||
int af_select_best_samplerate(int src_sampelrate, const int *available);
|
||||
|
||||
int af_format_sample_alignment(int format);
|
||||
|
||||
+116
-46
@@ -29,17 +29,18 @@
|
||||
#include "audio/format.h"
|
||||
|
||||
#include "options/options.h"
|
||||
#include "options/m_config_frontend.h"
|
||||
#include "options/m_config.h"
|
||||
#include "osdep/endian.h"
|
||||
#include "common/msg.h"
|
||||
#include "common/common.h"
|
||||
#include "common/global.h"
|
||||
|
||||
extern const struct ao_driver audio_out_audiotrack;
|
||||
extern const struct ao_driver audio_out_oss;
|
||||
extern const struct ao_driver audio_out_audiounit;
|
||||
extern const struct ao_driver audio_out_coreaudio;
|
||||
extern const struct ao_driver audio_out_coreaudio_exclusive;
|
||||
extern const struct ao_driver audio_out_rsound;
|
||||
extern const struct ao_driver audio_out_sndio;
|
||||
extern const struct ao_driver audio_out_pulse;
|
||||
extern const struct ao_driver audio_out_jack;
|
||||
extern const struct ao_driver audio_out_openal;
|
||||
@@ -53,9 +54,6 @@ extern const struct ao_driver audio_out_sdl;
|
||||
|
||||
static const struct ao_driver * const audio_out_drivers[] = {
|
||||
// native:
|
||||
#if HAVE_ANDROID
|
||||
&audio_out_audiotrack,
|
||||
#endif
|
||||
#if HAVE_AUDIOUNIT
|
||||
&audio_out_audiounit,
|
||||
#endif
|
||||
@@ -70,6 +68,9 @@ static const struct ao_driver * const audio_out_drivers[] = {
|
||||
#endif
|
||||
#if HAVE_WASAPI
|
||||
&audio_out_wasapi,
|
||||
#endif
|
||||
#if HAVE_OSS_AUDIO
|
||||
&audio_out_oss,
|
||||
#endif
|
||||
// wrappers:
|
||||
#if HAVE_JACK
|
||||
@@ -81,15 +82,23 @@ static const struct ao_driver * const audio_out_drivers[] = {
|
||||
#if HAVE_OPENSLES
|
||||
&audio_out_opensles,
|
||||
#endif
|
||||
#if HAVE_SDL2_AUDIO
|
||||
#if HAVE_SDL1 || HAVE_SDL2
|
||||
&audio_out_sdl,
|
||||
#endif
|
||||
#if HAVE_SNDIO
|
||||
&audio_out_sndio,
|
||||
#endif
|
||||
&audio_out_null,
|
||||
#if HAVE_COREAUDIO
|
||||
&audio_out_coreaudio_exclusive,
|
||||
#endif
|
||||
&audio_out_pcm,
|
||||
#if HAVE_ENCODING
|
||||
&audio_out_lavc,
|
||||
#endif
|
||||
#if HAVE_RSOUND
|
||||
&audio_out_rsound,
|
||||
#endif
|
||||
NULL
|
||||
};
|
||||
|
||||
@@ -113,7 +122,7 @@ static bool get_desc(struct m_obj_desc *dst, int index)
|
||||
}
|
||||
|
||||
// For the ao option
|
||||
static const struct m_obj_list ao_obj_list = {
|
||||
const struct m_obj_list ao_obj_list = {
|
||||
.get_desc = get_desc,
|
||||
.description = "audio outputs",
|
||||
.allow_unknown_entries = true,
|
||||
@@ -122,31 +131,13 @@ static const struct m_obj_list ao_obj_list = {
|
||||
.use_global_options = true,
|
||||
};
|
||||
|
||||
#define OPT_BASE_STRUCT struct ao_opts
|
||||
const struct m_sub_options ao_conf = {
|
||||
.opts = (const struct m_option[]) {
|
||||
{"ao", OPT_SETTINGSLIST(audio_driver_list, &ao_obj_list),
|
||||
.flags = UPDATE_AUDIO},
|
||||
{"audio-device", OPT_STRING(audio_device), .flags = UPDATE_AUDIO},
|
||||
{"audio-client-name", OPT_STRING(audio_client_name), .flags = UPDATE_AUDIO},
|
||||
{"audio-buffer", OPT_DOUBLE(audio_buffer),
|
||||
.flags = UPDATE_AUDIO, M_RANGE(0, 10)},
|
||||
{0}
|
||||
},
|
||||
.size = sizeof(OPT_BASE_STRUCT),
|
||||
.defaults = &(const OPT_BASE_STRUCT){
|
||||
.audio_buffer = 0.2,
|
||||
.audio_device = "auto",
|
||||
.audio_client_name = "mpv",
|
||||
},
|
||||
};
|
||||
|
||||
static struct ao *ao_alloc(bool probing, struct mpv_global *global,
|
||||
void (*wakeup_cb)(void *ctx), void *wakeup_ctx,
|
||||
char *name)
|
||||
{
|
||||
assert(wakeup_cb);
|
||||
|
||||
struct MPOpts *opts = global->opts;
|
||||
struct mp_log *log = mp_log_new(NULL, global->log, "ao");
|
||||
struct m_obj_desc desc;
|
||||
if (!m_obj_list_find(&desc, &ao_obj_list, bstr0(name))) {
|
||||
@@ -154,7 +145,6 @@ static struct ao *ao_alloc(bool probing, struct mpv_global *global,
|
||||
talloc_free(log);
|
||||
return NULL;
|
||||
};
|
||||
struct ao_opts *opts = mp_get_config_group(NULL, global, &ao_conf);
|
||||
struct ao *ao = talloc_ptrtype(NULL, ao);
|
||||
talloc_steal(ao, log);
|
||||
*ao = (struct ao) {
|
||||
@@ -167,7 +157,6 @@ static struct ao *ao_alloc(bool probing, struct mpv_global *global,
|
||||
.def_buffer = opts->audio_buffer,
|
||||
.client_name = talloc_strdup(ao, opts->audio_client_name),
|
||||
};
|
||||
talloc_free(opts);
|
||||
ao->priv = m_config_group_from_desc(ao, ao->log, global, &desc, name);
|
||||
if (!ao->priv)
|
||||
goto error;
|
||||
@@ -200,9 +189,14 @@ static struct ao *ao_init(bool probing, struct mpv_global *global,
|
||||
af_fmt_to_str(ao->format));
|
||||
|
||||
ao->device = talloc_strdup(ao, dev);
|
||||
|
||||
ao->api = ao->driver->play ? &ao_api_push : &ao_api_pull;
|
||||
ao->api_priv = talloc_zero_size(ao, ao->api->priv_size);
|
||||
assert(!ao->api->priv_defaults && !ao->api->options);
|
||||
|
||||
ao->stream_silence = flags & AO_INIT_STREAM_SILENCE;
|
||||
|
||||
init_buffer_pre(ao);
|
||||
ao->period_size = 1;
|
||||
|
||||
int r = ao->driver->init(ao);
|
||||
if (r < 0) {
|
||||
@@ -211,14 +205,18 @@ static struct ao *ao_init(bool probing, struct mpv_global *global,
|
||||
char redirect[80], rdevice[80];
|
||||
snprintf(redirect, sizeof(redirect), "%s", ao->redirect);
|
||||
snprintf(rdevice, sizeof(rdevice), "%s", ao->device ? ao->device : "");
|
||||
ao_uninit(ao);
|
||||
talloc_free(ao);
|
||||
return ao_init(probing, global, wakeup_cb, wakeup_ctx,
|
||||
encode_lavc_ctx, flags, samplerate, format, channels,
|
||||
rdevice, redirect);
|
||||
}
|
||||
goto fail;
|
||||
}
|
||||
ao->driver_initialized = true;
|
||||
|
||||
if (ao->period_size < 1) {
|
||||
MP_ERR(ao, "Invalid period size set.\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ao->sstride = af_fmt_to_bytes(ao->format);
|
||||
ao->num_planes = 1;
|
||||
@@ -229,25 +227,22 @@ static struct ao *ao_init(bool probing, struct mpv_global *global,
|
||||
}
|
||||
ao->bps = ao->samplerate * ao->sstride;
|
||||
|
||||
if (ao->device_buffer <= 0 && ao->driver->write) {
|
||||
MP_ERR(ao, "Device buffer size not set.\n");
|
||||
goto fail;
|
||||
}
|
||||
if (!ao->device_buffer && ao->driver->get_space)
|
||||
ao->device_buffer = ao->driver->get_space(ao);
|
||||
if (ao->device_buffer)
|
||||
MP_VERBOSE(ao, "device buffer: %d samples.\n", ao->device_buffer);
|
||||
ao->buffer = MPMAX(ao->device_buffer, ao->def_buffer * ao->samplerate);
|
||||
ao->buffer = MPMAX(ao->buffer, 1);
|
||||
|
||||
int align = af_format_sample_alignment(ao->format);
|
||||
ao->buffer = (ao->buffer + align - 1) / align * align;
|
||||
MP_VERBOSE(ao, "using soft-buffer of %d samples.\n", ao->buffer);
|
||||
|
||||
if (!init_buffer_post(ao))
|
||||
if (ao->api->init(ao) < 0)
|
||||
goto fail;
|
||||
return ao;
|
||||
|
||||
fail:
|
||||
ao_uninit(ao);
|
||||
talloc_free(ao);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -273,8 +268,8 @@ struct ao *ao_init_best(struct mpv_global *global,
|
||||
struct encode_lavc_context *encode_lavc_ctx,
|
||||
int samplerate, int format, struct mp_chmap channels)
|
||||
{
|
||||
struct MPOpts *opts = global->opts;
|
||||
void *tmp = talloc_new(NULL);
|
||||
struct ao_opts *opts = mp_get_config_group(tmp, global, &ao_conf);
|
||||
struct mp_log *log = mp_log_new(tmp, global->log, "ao");
|
||||
struct ao *ao = NULL;
|
||||
struct m_obj_settings *ao_list = NULL;
|
||||
@@ -341,20 +336,95 @@ struct ao *ao_init_best(struct mpv_global *global,
|
||||
return ao;
|
||||
}
|
||||
|
||||
// Uninitialize and destroy the AO. Remaining audio must be dropped.
|
||||
void ao_uninit(struct ao *ao)
|
||||
{
|
||||
if (ao)
|
||||
ao->api->uninit(ao);
|
||||
talloc_free(ao);
|
||||
}
|
||||
|
||||
// Queue the given audio data. Start playback if it hasn't started yet. Return
|
||||
// the number of samples that was accepted (the core will try to queue the rest
|
||||
// again later). Should never block.
|
||||
// data: start pointer for each plane. If the audio data is packed, only
|
||||
// data[0] is valid, otherwise there is a plane for each channel.
|
||||
// samples: size of the audio data (see ao->sstride)
|
||||
// flags: currently AOPLAY_FINAL_CHUNK can be set
|
||||
int ao_play(struct ao *ao, void **data, int samples, int flags)
|
||||
{
|
||||
return ao->api->play(ao, data, samples, flags);
|
||||
}
|
||||
|
||||
int ao_control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
{
|
||||
return ao->api->control ? ao->api->control(ao, cmd, arg) : CONTROL_UNKNOWN;
|
||||
}
|
||||
|
||||
// Return size of the buffered data in seconds. Can include the device latency.
|
||||
// Basically, this returns how much data there is still to play, and how long
|
||||
// it takes until the last sample in the buffer reaches the speakers. This is
|
||||
// used for audio/video synchronization, so it's very important to implement
|
||||
// this correctly.
|
||||
double ao_get_delay(struct ao *ao)
|
||||
{
|
||||
return ao->api->get_delay(ao);
|
||||
}
|
||||
|
||||
// Return free size of the internal audio buffer. This controls how much audio
|
||||
// the core should decode and try to queue with ao_play().
|
||||
int ao_get_space(struct ao *ao)
|
||||
{
|
||||
return ao->api->get_space(ao);
|
||||
}
|
||||
|
||||
// Stop playback and empty buffers. Essentially go back to the state after
|
||||
// ao->init().
|
||||
void ao_reset(struct ao *ao)
|
||||
{
|
||||
if (ao->api->reset)
|
||||
ao->api->reset(ao);
|
||||
}
|
||||
|
||||
// Pause playback. Keep the current buffer. ao_get_delay() must return the
|
||||
// same value as before pausing.
|
||||
void ao_pause(struct ao *ao)
|
||||
{
|
||||
if (ao->api->pause)
|
||||
ao->api->pause(ao);
|
||||
}
|
||||
|
||||
// Resume playback. Play the remaining buffer. If the driver doesn't support
|
||||
// pausing, it has to work around this and e.g. use ao_play_silence() to fill
|
||||
// the lost audio.
|
||||
void ao_resume(struct ao *ao)
|
||||
{
|
||||
if (ao->api->resume)
|
||||
ao->api->resume(ao);
|
||||
}
|
||||
|
||||
// Block until the current audio buffer has played completely.
|
||||
void ao_drain(struct ao *ao)
|
||||
{
|
||||
if (ao->api->drain)
|
||||
ao->api->drain(ao);
|
||||
}
|
||||
|
||||
bool ao_eof_reached(struct ao *ao)
|
||||
{
|
||||
return ao->api->get_eof ? ao->api->get_eof(ao) : true;
|
||||
}
|
||||
|
||||
// Query the AO_EVENT_*s as requested by the events parameter, and return them.
|
||||
int ao_query_and_reset_events(struct ao *ao, int events)
|
||||
{
|
||||
return atomic_fetch_and(&ao->events_, ~(unsigned)events) & events;
|
||||
}
|
||||
|
||||
// Returns events that were set by this calls.
|
||||
int ao_add_events(struct ao *ao, int events)
|
||||
static void ao_add_events(struct ao *ao, int events)
|
||||
{
|
||||
unsigned prev_events = atomic_fetch_or(&ao->events_, events);
|
||||
unsigned new = events & ~prev_events;
|
||||
if (new)
|
||||
ao->wakeup_cb(ao->wakeup_ctx);
|
||||
return new;
|
||||
atomic_fetch_or(&ao->events_, events);
|
||||
ao->wakeup_cb(ao->wakeup_ctx);
|
||||
}
|
||||
|
||||
// Request that the player core destroys and recreates the AO. Fully thread-safe.
|
||||
|
||||
+10
-16
@@ -35,16 +35,19 @@ enum aocontrol {
|
||||
AOCONTROL_SET_MUTE,
|
||||
// Has char* as argument, which contains the desired stream title.
|
||||
AOCONTROL_UPDATE_STREAM_TITLE,
|
||||
// the AO does the equivalent of af_volume (return CONTROL_TRUE if yes)
|
||||
AOCONTROL_HAS_SOFT_VOLUME,
|
||||
// like above, but volume persists (per app), mpv won't restore volume
|
||||
AOCONTROL_HAS_PER_APP_VOLUME,
|
||||
};
|
||||
|
||||
// If set, then the queued audio data is the last. Note that after a while, new
|
||||
// data might be written again, instead of closing the AO.
|
||||
#define PLAYER_FINAL_CHUNK 1
|
||||
#define AOPLAY_FINAL_CHUNK 1
|
||||
|
||||
enum {
|
||||
AO_EVENT_RELOAD = 1,
|
||||
AO_EVENT_HOTPLUG = 2,
|
||||
AO_EVENT_INITIAL_UNBLOCK = 4,
|
||||
};
|
||||
|
||||
enum {
|
||||
@@ -79,13 +82,6 @@ struct mpv_global;
|
||||
struct input_ctx;
|
||||
struct encode_lavc_context;
|
||||
|
||||
struct ao_opts {
|
||||
struct m_obj_settings *audio_driver_list;
|
||||
char *audio_device;
|
||||
char *audio_client_name;
|
||||
double audio_buffer;
|
||||
};
|
||||
|
||||
struct ao *ao_init_best(struct mpv_global *global,
|
||||
int init_flags,
|
||||
void (*wakeup_cb)(void *ctx), void *wakeup_ctx,
|
||||
@@ -97,19 +93,17 @@ void ao_get_format(struct ao *ao,
|
||||
const char *ao_get_name(struct ao *ao);
|
||||
const char *ao_get_description(struct ao *ao);
|
||||
bool ao_untimed(struct ao *ao);
|
||||
int ao_play(struct ao *ao, void **data, int samples, int flags);
|
||||
int ao_control(struct ao *ao, enum aocontrol cmd, void *arg);
|
||||
void ao_set_gain(struct ao *ao, float gain);
|
||||
double ao_get_delay(struct ao *ao);
|
||||
int ao_get_space(struct ao *ao);
|
||||
void ao_reset(struct ao *ao);
|
||||
void ao_start(struct ao *ao);
|
||||
void ao_set_paused(struct ao *ao, bool paused);
|
||||
void ao_pause(struct ao *ao);
|
||||
void ao_resume(struct ao *ao);
|
||||
void ao_drain(struct ao *ao);
|
||||
bool ao_is_playing(struct ao *ao);
|
||||
struct mp_async_queue;
|
||||
struct mp_async_queue *ao_get_queue(struct ao *ao);
|
||||
bool ao_eof_reached(struct ao *ao);
|
||||
int ao_query_and_reset_events(struct ao *ao, int events);
|
||||
int ao_add_events(struct ao *ao, int events);
|
||||
void ao_unblock(struct ao *ao);
|
||||
void ao_request_reload(struct ao *ao);
|
||||
void ao_hotplug_event(struct ao *ao);
|
||||
|
||||
|
||||
+260
-195
@@ -30,7 +30,6 @@
|
||||
#include <sys/time.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <limits.h>
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -43,11 +42,8 @@
|
||||
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
#if defined(SND_CHMAP_API_VERSION) && SND_CHMAP_API_VERSION >= (1 << 16)
|
||||
#define HAVE_CHMAP_API 1
|
||||
#else
|
||||
#define HAVE_CHMAP_API 0
|
||||
#endif
|
||||
#define HAVE_CHMAP_API \
|
||||
(defined(SND_CHMAP_API_VERSION) && SND_CHMAP_API_VERSION >= (1 << 16))
|
||||
|
||||
#include "ao.h"
|
||||
#include "internal.h"
|
||||
@@ -60,21 +56,17 @@ struct ao_alsa_opts {
|
||||
int resample;
|
||||
int ni;
|
||||
int ignore_chmap;
|
||||
int buffer_time;
|
||||
int frags;
|
||||
};
|
||||
|
||||
#define OPT_BASE_STRUCT struct ao_alsa_opts
|
||||
static const struct m_sub_options ao_alsa_conf = {
|
||||
.opts = (const struct m_option[]) {
|
||||
{"alsa-resample", OPT_FLAG(resample)},
|
||||
{"alsa-mixer-device", OPT_STRING(mixer_device)},
|
||||
{"alsa-mixer-name", OPT_STRING(mixer_name)},
|
||||
{"alsa-mixer-index", OPT_INT(mixer_index), M_RANGE(0, 99)},
|
||||
{"alsa-non-interleaved", OPT_FLAG(ni)},
|
||||
{"alsa-ignore-chmap", OPT_FLAG(ignore_chmap)},
|
||||
{"alsa-buffer-time", OPT_INT(buffer_time), M_RANGE(0, INT_MAX)},
|
||||
{"alsa-periods", OPT_INT(frags), M_RANGE(0, INT_MAX)},
|
||||
OPT_FLAG("alsa-resample", resample, 0),
|
||||
OPT_STRING("alsa-mixer-device", mixer_device, 0),
|
||||
OPT_STRING("alsa-mixer-name", mixer_name, 0),
|
||||
OPT_INTRANGE("alsa-mixer-index", mixer_index, 0, 0, 99),
|
||||
OPT_FLAG("alsa-non-interleaved", ni, 0),
|
||||
OPT_FLAG("alsa-ignore-chmap", ignore_chmap, 0),
|
||||
{0}
|
||||
},
|
||||
.defaults = &(const struct ao_alsa_opts) {
|
||||
@@ -82,8 +74,6 @@ static const struct m_sub_options ao_alsa_conf = {
|
||||
.mixer_name = "Master",
|
||||
.mixer_index = 0,
|
||||
.ni = 0,
|
||||
.buffer_time = 100000,
|
||||
.frags = 4,
|
||||
},
|
||||
.size = sizeof(struct ao_alsa_opts),
|
||||
};
|
||||
@@ -93,6 +83,9 @@ struct priv {
|
||||
bool device_lost;
|
||||
snd_pcm_format_t alsa_fmt;
|
||||
bool can_pause;
|
||||
bool paused;
|
||||
snd_pcm_sframes_t prepause_frames;
|
||||
double delay_before_pause;
|
||||
snd_pcm_uframes_t buffersize;
|
||||
snd_pcm_uframes_t outburst;
|
||||
|
||||
@@ -103,6 +96,9 @@ struct priv {
|
||||
struct ao_alsa_opts *opts;
|
||||
};
|
||||
|
||||
#define BUFFER_TIME 250000 // 250ms
|
||||
#define FRAGCOUNT 16
|
||||
|
||||
#define CHECK_ALSA_ERROR(message) \
|
||||
do { \
|
||||
if (err < 0) { \
|
||||
@@ -117,6 +113,21 @@ struct priv {
|
||||
MP_WARN(ao, "%s: %s\n", (message), snd_strerror(err)); \
|
||||
} while (0)
|
||||
|
||||
// Common code for handling ENODEV, which happens if a device gets "lost", and
|
||||
// can't be used anymore. Returns true if alsa_err is not ENODEV.
|
||||
static bool check_device_present(struct ao *ao, int alsa_err)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
if (alsa_err != -ENODEV)
|
||||
return true;
|
||||
if (!p->device_lost) {
|
||||
MP_WARN(ao, "Device lost, trying to recover...\n");
|
||||
ao_request_reload(ao);
|
||||
p->device_lost = true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
@@ -507,7 +518,7 @@ static int set_chmap(struct ao *ao, struct mp_chmap *dev_chmap, int num_channels
|
||||
|
||||
#endif /* else HAVE_CHMAP_API */
|
||||
|
||||
static void dump_hw_params(struct ao *ao, const char *msg,
|
||||
static void dump_hw_params(struct ao *ao, int msglevel, const char *msg,
|
||||
snd_pcm_hw_params_t *hw_params)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
@@ -519,7 +530,7 @@ static void dump_hw_params(struct ao *ao, const char *msg,
|
||||
char *tmp = NULL;
|
||||
size_t tmp_s = snd_output_buffer_string(p->output, &tmp);
|
||||
if (tmp)
|
||||
mp_msg(ao->log, MSGL_DEBUG, "%s---\n%.*s---\n", msg, (int)tmp_s, tmp);
|
||||
mp_msg(ao->log, msglevel, "%s---\n%.*s---\n", msg, (int)tmp_s, tmp);
|
||||
snd_output_flush(p->output);
|
||||
}
|
||||
|
||||
@@ -634,7 +645,6 @@ alsa_error: ;
|
||||
static int init_device(struct ao *ao, int mode)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
struct ao_alsa_opts *opts = p->opts;
|
||||
int ret = INIT_DEVICE_ERR_GENERIC;
|
||||
char *tmp;
|
||||
size_t tmp_s;
|
||||
@@ -668,7 +678,7 @@ static int init_device(struct ao *ao, int mode)
|
||||
err = snd_pcm_hw_params_any(p->alsa, alsa_hwparams);
|
||||
CHECK_ALSA_ERROR("Unable to get initial parameters");
|
||||
|
||||
dump_hw_params(ao, "Start HW params:\n", alsa_hwparams);
|
||||
dump_hw_params(ao, MSGL_DEBUG, "Start HW params:\n", alsa_hwparams);
|
||||
|
||||
// Some ALSA drivers have broken delay reporting, so disable the ALSA
|
||||
// resampling plugin by default.
|
||||
@@ -676,7 +686,7 @@ static int init_device(struct ao *ao, int mode)
|
||||
err = snd_pcm_hw_params_set_rate_resample(p->alsa, alsa_hwparams, 0);
|
||||
CHECK_ALSA_ERROR("Unable to disable resampling");
|
||||
}
|
||||
dump_hw_params(ao, "HW params after rate:\n", alsa_hwparams);
|
||||
dump_hw_params(ao, MSGL_DEBUG, "HW params after rate:\n", alsa_hwparams);
|
||||
|
||||
snd_pcm_access_t access = af_fmt_is_planar(ao->format)
|
||||
? SND_PCM_ACCESS_RW_NONINTERLEAVED
|
||||
@@ -688,10 +698,10 @@ static int init_device(struct ao *ao, int mode)
|
||||
err = snd_pcm_hw_params_set_access(p->alsa, alsa_hwparams, access);
|
||||
}
|
||||
CHECK_ALSA_ERROR("Unable to set access type");
|
||||
dump_hw_params(ao, "HW params after access:\n", alsa_hwparams);
|
||||
dump_hw_params(ao, MSGL_DEBUG, "HW params after access:\n", alsa_hwparams);
|
||||
|
||||
bool found_format = false;
|
||||
int try_formats[AF_FORMAT_COUNT + 1];
|
||||
int try_formats[AF_FORMAT_COUNT];
|
||||
af_get_best_sample_formats(ao->format, try_formats);
|
||||
for (int n = 0; try_formats[n] && !found_format; n++) {
|
||||
int mp_format = try_formats[n];
|
||||
@@ -731,7 +741,7 @@ static int init_device(struct ao *ao, int mode)
|
||||
|
||||
err = snd_pcm_hw_params_set_format(p->alsa, alsa_hwparams, p->alsa_fmt);
|
||||
CHECK_ALSA_ERROR("Unable to set format");
|
||||
dump_hw_params(ao, "HW params after format:\n", alsa_hwparams);
|
||||
dump_hw_params(ao, MSGL_DEBUG, "HW params after format:\n", alsa_hwparams);
|
||||
|
||||
// Stereo, or mono if input is 1 channel.
|
||||
struct mp_chmap reduced;
|
||||
@@ -760,7 +770,7 @@ static int init_device(struct ao *ao, int mode)
|
||||
err = snd_pcm_hw_params_set_channels_near
|
||||
(p->alsa, alsa_hwparams, &num_channels);
|
||||
CHECK_ALSA_ERROR("Unable to set channels");
|
||||
dump_hw_params(ao, "HW params after channels:\n", alsa_hwparams);
|
||||
dump_hw_params(ao, MSGL_DEBUG, "HW params after channels:\n", alsa_hwparams);
|
||||
|
||||
if (num_channels > MP_NUM_CHANNELS) {
|
||||
MP_FATAL(ao, "Too many audio channels (%d).\n", num_channels);
|
||||
@@ -770,35 +780,32 @@ static int init_device(struct ao *ao, int mode)
|
||||
err = snd_pcm_hw_params_set_rate_near
|
||||
(p->alsa, alsa_hwparams, &ao->samplerate, NULL);
|
||||
CHECK_ALSA_ERROR("Unable to set samplerate-2");
|
||||
dump_hw_params(ao, "HW params after rate-2:\n", alsa_hwparams);
|
||||
dump_hw_params(ao, MSGL_DEBUG, "HW params after rate-2:\n", alsa_hwparams);
|
||||
|
||||
snd_pcm_hw_params_t *hwparams_backup;
|
||||
snd_pcm_hw_params_alloca(&hwparams_backup);
|
||||
snd_pcm_hw_params_copy(hwparams_backup, alsa_hwparams);
|
||||
|
||||
// Cargo-culted buffer settings; might still be useful for PulseAudio.
|
||||
err = 0;
|
||||
if (opts->buffer_time) {
|
||||
err = snd_pcm_hw_params_set_buffer_time_near
|
||||
(p->alsa, alsa_hwparams, &(unsigned int){opts->buffer_time}, NULL);
|
||||
CHECK_ALSA_WARN("Unable to set buffer time near");
|
||||
}
|
||||
if (err >= 0 && opts->frags) {
|
||||
err = snd_pcm_hw_params_set_buffer_time_near
|
||||
(p->alsa, alsa_hwparams, &(unsigned int){BUFFER_TIME}, NULL);
|
||||
CHECK_ALSA_WARN("Unable to set buffer time near");
|
||||
if (err >= 0) {
|
||||
err = snd_pcm_hw_params_set_periods_near
|
||||
(p->alsa, alsa_hwparams, &(unsigned int){opts->frags}, NULL);
|
||||
(p->alsa, alsa_hwparams, &(unsigned int){FRAGCOUNT}, NULL);
|
||||
CHECK_ALSA_WARN("Unable to set periods");
|
||||
}
|
||||
if (err < 0)
|
||||
snd_pcm_hw_params_copy(alsa_hwparams, hwparams_backup);
|
||||
|
||||
dump_hw_params(ao, "Going to set final HW params:\n", alsa_hwparams);
|
||||
dump_hw_params(ao, MSGL_V, "Going to set final HW params:\n", alsa_hwparams);
|
||||
|
||||
/* finally install hardware parameters */
|
||||
err = snd_pcm_hw_params(p->alsa, alsa_hwparams);
|
||||
ret = INIT_DEVICE_ERR_HWPARAMS;
|
||||
CHECK_ALSA_ERROR("Unable to set hw-parameters");
|
||||
ret = INIT_DEVICE_ERR_GENERIC;
|
||||
dump_hw_params(ao, "Final HW params:\n", alsa_hwparams);
|
||||
dump_hw_params(ao, MSGL_DEBUG, "Final HW params:\n", alsa_hwparams);
|
||||
|
||||
if (set_chmap(ao, &dev_chmap, num_channels) < 0)
|
||||
goto alsa_error;
|
||||
@@ -833,10 +840,16 @@ static int init_device(struct ao *ao, int mode)
|
||||
err = snd_pcm_sw_params_get_boundary(alsa_swparams, &boundary);
|
||||
CHECK_ALSA_ERROR("Unable to get boundary");
|
||||
|
||||
// Manual trigger; INT_MAX as suggested by ALSA doxygen (they call it MAXINT).
|
||||
err = snd_pcm_sw_params_set_start_threshold(p->alsa, alsa_swparams, INT_MAX);
|
||||
/* start playing when one period has been written */
|
||||
err = snd_pcm_sw_params_set_start_threshold
|
||||
(p->alsa, alsa_swparams, p->outburst);
|
||||
CHECK_ALSA_ERROR("Unable to set start threshold");
|
||||
|
||||
/* disable underrun reporting */
|
||||
err = snd_pcm_sw_params_set_stop_threshold
|
||||
(p->alsa, alsa_swparams, boundary);
|
||||
CHECK_ALSA_ERROR("Unable to set stop threshold");
|
||||
|
||||
/* play silence when there is an underrun */
|
||||
err = snd_pcm_sw_params_set_silence_size
|
||||
(p->alsa, alsa_swparams, boundary);
|
||||
@@ -850,12 +863,10 @@ static int init_device(struct ao *ao, int mode)
|
||||
MP_VERBOSE(ao, "period size: %d samples\n", (int)p->outburst);
|
||||
|
||||
ao->device_buffer = p->buffersize;
|
||||
ao->period_size = p->outburst;
|
||||
|
||||
p->convert.channels = ao->channels.num;
|
||||
|
||||
err = snd_pcm_prepare(p->alsa);
|
||||
CHECK_ALSA_ERROR("pcm prepare error");
|
||||
|
||||
return 0;
|
||||
|
||||
alsa_error:
|
||||
@@ -913,194 +924,244 @@ static int init(struct ao *ao)
|
||||
return r;
|
||||
}
|
||||
|
||||
// Function for dealing with playback state. This attempts to recover the ALSA
|
||||
// state (bring it into SND_PCM_STATE_{PREPARED,RUNNING,PAUSED,UNDERRUN}). If
|
||||
// state!=NULL, fill it after recovery is attempted.
|
||||
// Returns true if PCM is in one the expected states.
|
||||
static bool recover_and_get_state(struct ao *ao, struct mp_pcm_state *state)
|
||||
static void drain(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
snd_pcm_drain(p->alsa);
|
||||
}
|
||||
|
||||
static int get_space(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
snd_pcm_status_t *status;
|
||||
int err;
|
||||
|
||||
snd_pcm_status_t *st;
|
||||
snd_pcm_status_alloca(&st);
|
||||
snd_pcm_status_alloca(&status);
|
||||
|
||||
bool state_ok = false;
|
||||
snd_pcm_state_t pcmst = SND_PCM_STATE_DISCONNECTED;
|
||||
err = snd_pcm_status(p->alsa, status);
|
||||
if (!check_device_present(ao, err))
|
||||
goto alsa_error;
|
||||
CHECK_ALSA_ERROR("cannot get pcm status");
|
||||
|
||||
// Give it a number of chances to recover. This tries to deal with the fact
|
||||
// that the API is asynchronous, and to account for some past cargo-cult
|
||||
// (where things were retried in a loop).
|
||||
for (int n = 0; n < 10; n++) {
|
||||
err = snd_pcm_status(p->alsa, st);
|
||||
if (err == -EPIPE) {
|
||||
// ALSA APIs can return -EPIPE when an XRUN happens,
|
||||
// we skip right to handling it by setting pcmst
|
||||
// manually.
|
||||
pcmst = SND_PCM_STATE_XRUN;
|
||||
} else {
|
||||
// Otherwise do error checking and query the PCM state properly.
|
||||
CHECK_ALSA_ERROR("snd_pcm_status");
|
||||
|
||||
pcmst = snd_pcm_status_get_state(st);
|
||||
}
|
||||
|
||||
if (pcmst == SND_PCM_STATE_PREPARED ||
|
||||
pcmst == SND_PCM_STATE_RUNNING ||
|
||||
pcmst == SND_PCM_STATE_PAUSED)
|
||||
{
|
||||
state_ok = true;
|
||||
break;
|
||||
}
|
||||
|
||||
MP_VERBOSE(ao, "attempt %d to recover from state '%s'...\n",
|
||||
n + 1, snd_pcm_state_name(pcmst));
|
||||
|
||||
switch (pcmst) {
|
||||
// Underrun; recover. (We never use draining.)
|
||||
case SND_PCM_STATE_XRUN:
|
||||
case SND_PCM_STATE_DRAINING:
|
||||
err = snd_pcm_prepare(p->alsa);
|
||||
CHECK_ALSA_ERROR("pcm prepare error");
|
||||
continue;
|
||||
// Hardware suspend.
|
||||
case SND_PCM_STATE_SUSPENDED:
|
||||
MP_INFO(ao, "PCM in suspend mode, trying to resume.\n");
|
||||
err = snd_pcm_resume(p->alsa);
|
||||
if (err == -EAGAIN) {
|
||||
// Cargo-cult from decades ago, with a cargo cult timeout.
|
||||
MP_INFO(ao, "PCM resume EAGAIN - retrying.\n");
|
||||
sleep(1);
|
||||
continue;
|
||||
}
|
||||
if (err == -ENOSYS) {
|
||||
// As suggested by ALSA doxygen.
|
||||
MP_VERBOSE(ao, "ENOSYS, retrying with snd_pcm_prepare().\n");
|
||||
err = snd_pcm_prepare(p->alsa);
|
||||
}
|
||||
if (err < 0)
|
||||
MP_ERR(ao, "resuming from SUSPENDED: %s\n", snd_strerror(err));
|
||||
continue;
|
||||
// Device lost. OPEN/SETUP are states we never enter after init, so
|
||||
// treat them like DISCONNECTED.
|
||||
case SND_PCM_STATE_DISCONNECTED:
|
||||
case SND_PCM_STATE_OPEN:
|
||||
case SND_PCM_STATE_SETUP:
|
||||
default:
|
||||
if (!p->device_lost) {
|
||||
MP_WARN(ao, "Device lost, trying to recover...\n");
|
||||
ao_request_reload(ao);
|
||||
p->device_lost = true;
|
||||
}
|
||||
goto alsa_error;
|
||||
}
|
||||
}
|
||||
|
||||
if (!state_ok) {
|
||||
MP_ERR(ao, "could not recover\n");
|
||||
}
|
||||
unsigned space = snd_pcm_status_get_avail(status);
|
||||
if (space > p->buffersize) // Buffer underrun?
|
||||
space = p->buffersize;
|
||||
return space / p->outburst * p->outburst;
|
||||
|
||||
alsa_error:
|
||||
|
||||
if (state) {
|
||||
snd_pcm_sframes_t del = state_ok ? snd_pcm_status_get_delay(st) : 0;
|
||||
state->delay = MPMAX(del, 0) / (double)ao->samplerate;
|
||||
state->free_samples = state_ok ? snd_pcm_status_get_avail(st) : 0;
|
||||
state->free_samples = MPCLAMP(state->free_samples, 0, ao->device_buffer);
|
||||
// Align to period size.
|
||||
state->free_samples = state->free_samples / p->outburst * p->outburst;
|
||||
state->queued_samples = ao->device_buffer - state->free_samples;
|
||||
state->playing = pcmst == SND_PCM_STATE_RUNNING ||
|
||||
pcmst == SND_PCM_STATE_PAUSED;
|
||||
}
|
||||
|
||||
return state_ok;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void audio_get_state(struct ao *ao, struct mp_pcm_state *state)
|
||||
/* delay in seconds between first and last sample in buffer */
|
||||
static double get_delay(struct ao *ao)
|
||||
{
|
||||
recover_and_get_state(ao, state);
|
||||
struct priv *p = ao->priv;
|
||||
snd_pcm_sframes_t delay;
|
||||
|
||||
if (p->paused)
|
||||
return p->delay_before_pause;
|
||||
|
||||
if (snd_pcm_delay(p->alsa, &delay) < 0)
|
||||
return 0;
|
||||
|
||||
if (delay < 0) {
|
||||
/* underrun - move the application pointer forward to catch up */
|
||||
snd_pcm_forward(p->alsa, -delay);
|
||||
delay = 0;
|
||||
}
|
||||
return delay / (double)ao->samplerate;
|
||||
}
|
||||
|
||||
static void audio_start(struct ao *ao)
|
||||
// For stream-silence mode: replace remaining buffer with silence.
|
||||
// Tries to cause an instant buffer underrun.
|
||||
static void soft_reset(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
snd_pcm_sframes_t frames = snd_pcm_rewindable(p->alsa);
|
||||
if (frames > 0 && snd_pcm_state(p->alsa) == SND_PCM_STATE_RUNNING) {
|
||||
frames = snd_pcm_rewind(p->alsa, frames);
|
||||
if (frames < 0) {
|
||||
int err = frames;
|
||||
CHECK_ALSA_WARN("pcm rewind error");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void audio_pause(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
int err;
|
||||
|
||||
recover_and_get_state(ao, NULL);
|
||||
if (p->paused)
|
||||
return;
|
||||
|
||||
err = snd_pcm_start(p->alsa);
|
||||
CHECK_ALSA_ERROR("pcm start error");
|
||||
p->delay_before_pause = get_delay(ao);
|
||||
p->prepause_frames = p->delay_before_pause * ao->samplerate;
|
||||
|
||||
if (ao->stream_silence) {
|
||||
soft_reset(ao);
|
||||
} else if (p->can_pause) {
|
||||
if (snd_pcm_state(p->alsa) == SND_PCM_STATE_RUNNING) {
|
||||
err = snd_pcm_pause(p->alsa, 1);
|
||||
CHECK_ALSA_ERROR("pcm pause error");
|
||||
p->prepause_frames = 0;
|
||||
}
|
||||
} else {
|
||||
err = snd_pcm_drop(p->alsa);
|
||||
CHECK_ALSA_ERROR("pcm drop error");
|
||||
}
|
||||
|
||||
p->paused = true;
|
||||
|
||||
alsa_error: ;
|
||||
}
|
||||
|
||||
static void audio_reset(struct ao *ao)
|
||||
static void resume_device(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
int err;
|
||||
|
||||
err = snd_pcm_drop(p->alsa);
|
||||
CHECK_ALSA_ERROR("pcm drop error");
|
||||
err = snd_pcm_prepare(p->alsa);
|
||||
CHECK_ALSA_ERROR("pcm prepare error");
|
||||
if (snd_pcm_state(p->alsa) == SND_PCM_STATE_SUSPENDED) {
|
||||
MP_INFO(ao, "PCM in suspend mode, trying to resume.\n");
|
||||
|
||||
recover_and_get_state(ao, NULL);
|
||||
while ((err = snd_pcm_resume(p->alsa)) == -EAGAIN)
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
static void audio_resume(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
int err;
|
||||
|
||||
if (!p->paused)
|
||||
return;
|
||||
|
||||
resume_device(ao);
|
||||
|
||||
if (ao->stream_silence) {
|
||||
p->paused = false;
|
||||
get_delay(ao); // recovers from underrun (as a side-effect)
|
||||
} else if (p->can_pause) {
|
||||
if (snd_pcm_state(p->alsa) == SND_PCM_STATE_PAUSED) {
|
||||
err = snd_pcm_pause(p->alsa, 0);
|
||||
CHECK_ALSA_ERROR("pcm resume error");
|
||||
}
|
||||
} else {
|
||||
MP_VERBOSE(ao, "resume not supported by hardware\n");
|
||||
err = snd_pcm_prepare(p->alsa);
|
||||
CHECK_ALSA_ERROR("pcm prepare error");
|
||||
}
|
||||
|
||||
if (p->prepause_frames)
|
||||
ao_play_silence(ao, p->prepause_frames);
|
||||
|
||||
alsa_error: ;
|
||||
p->paused = false;
|
||||
}
|
||||
|
||||
static void reset(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
int err;
|
||||
|
||||
p->paused = false;
|
||||
p->prepause_frames = 0;
|
||||
p->delay_before_pause = 0;
|
||||
|
||||
if (ao->stream_silence) {
|
||||
soft_reset(ao);
|
||||
} else {
|
||||
err = snd_pcm_drop(p->alsa);
|
||||
CHECK_ALSA_ERROR("pcm prepare error");
|
||||
err = snd_pcm_prepare(p->alsa);
|
||||
CHECK_ALSA_ERROR("pcm prepare error");
|
||||
}
|
||||
|
||||
alsa_error: ;
|
||||
}
|
||||
|
||||
static bool audio_set_paused(struct ao *ao, bool paused)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
int err;
|
||||
|
||||
recover_and_get_state(ao, NULL);
|
||||
|
||||
if (!p->can_pause)
|
||||
return false;
|
||||
|
||||
snd_pcm_state_t pcmst = snd_pcm_state(p->alsa);
|
||||
if (pcmst == SND_PCM_STATE_RUNNING && paused) {
|
||||
err = snd_pcm_pause(p->alsa, 1);
|
||||
CHECK_ALSA_ERROR("pcm pause error");
|
||||
} else if (pcmst == SND_PCM_STATE_PAUSED && !paused) {
|
||||
err = snd_pcm_pause(p->alsa, 0);
|
||||
CHECK_ALSA_ERROR("pcm resume error");
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
alsa_error:
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool audio_write(struct ao *ao, void **data, int samples)
|
||||
static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
snd_pcm_sframes_t res = 0;
|
||||
if (!(flags & AOPLAY_FINAL_CHUNK))
|
||||
samples = samples / p->outburst * p->outburst;
|
||||
|
||||
if (samples == 0)
|
||||
return 0;
|
||||
ao_convert_inplace(&p->convert, data, samples);
|
||||
|
||||
if (!recover_and_get_state(ao, NULL))
|
||||
return false;
|
||||
do {
|
||||
if (af_fmt_is_planar(ao->format)) {
|
||||
res = snd_pcm_writen(p->alsa, data, samples);
|
||||
} else {
|
||||
res = snd_pcm_writei(p->alsa, data[0], samples);
|
||||
}
|
||||
|
||||
snd_pcm_sframes_t err = 0;
|
||||
if (af_fmt_is_planar(ao->format)) {
|
||||
err = snd_pcm_writen(p->alsa, data, samples);
|
||||
} else {
|
||||
err = snd_pcm_writei(p->alsa, data[0], samples);
|
||||
}
|
||||
if (res == -EINTR || res == -EAGAIN) { /* retry */
|
||||
res = 0;
|
||||
} else if (!check_device_present(ao, res)) {
|
||||
goto alsa_error;
|
||||
} else if (res < 0) {
|
||||
if (res == -ESTRPIPE) { /* suspend */
|
||||
resume_device(ao);
|
||||
} else {
|
||||
MP_ERR(ao, "Write error: %s\n", snd_strerror(res));
|
||||
}
|
||||
res = snd_pcm_prepare(p->alsa);
|
||||
int err = res;
|
||||
CHECK_ALSA_ERROR("pcm prepare error");
|
||||
res = 0;
|
||||
}
|
||||
} while (res == 0);
|
||||
|
||||
CHECK_ALSA_ERROR("pcm write error");
|
||||
if (err >= 0 && err != samples) {
|
||||
MP_ERR(ao, "unexpected partial write (%d of %d frames), dropping audio\n",
|
||||
(int)err, samples);
|
||||
}
|
||||
p->paused = false;
|
||||
|
||||
return true;
|
||||
return res < 0 ? -1 : res;
|
||||
|
||||
alsa_error:
|
||||
return false;
|
||||
return -1;
|
||||
}
|
||||
|
||||
#define MAX_POLL_FDS 20
|
||||
static int audio_wait(struct ao *ao, pthread_mutex_t *lock)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
int err;
|
||||
|
||||
int num_fds = snd_pcm_poll_descriptors_count(p->alsa);
|
||||
if (num_fds <= 0 || num_fds >= MAX_POLL_FDS)
|
||||
goto alsa_error;
|
||||
|
||||
struct pollfd fds[MAX_POLL_FDS];
|
||||
err = snd_pcm_poll_descriptors(p->alsa, fds, num_fds);
|
||||
CHECK_ALSA_ERROR("cannot get pollfds");
|
||||
|
||||
while (1) {
|
||||
int r = ao_wait_poll(ao, fds, num_fds, lock);
|
||||
if (r)
|
||||
return r;
|
||||
|
||||
unsigned short revents;
|
||||
err = snd_pcm_poll_descriptors_revents(p->alsa, fds, num_fds, &revents);
|
||||
CHECK_ALSA_ERROR("cannot read poll events");
|
||||
|
||||
if (revents & POLLERR) {
|
||||
snd_pcm_status_t *status;
|
||||
snd_pcm_status_alloca(&status);
|
||||
|
||||
err = snd_pcm_status(p->alsa, status);
|
||||
check_device_present(ao, err);
|
||||
return -1;
|
||||
}
|
||||
if (revents & POLLOUT)
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
|
||||
alsa_error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static bool is_useless_device(char *name)
|
||||
@@ -1153,11 +1214,15 @@ const struct ao_driver audio_out_alsa = {
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.control = control,
|
||||
.get_state = audio_get_state,
|
||||
.write = audio_write,
|
||||
.start = audio_start,
|
||||
.set_pause = audio_set_paused,
|
||||
.reset = audio_reset,
|
||||
.get_space = get_space,
|
||||
.play = play,
|
||||
.get_delay = get_delay,
|
||||
.pause = audio_pause,
|
||||
.resume = audio_resume,
|
||||
.reset = reset,
|
||||
.drain = drain,
|
||||
.wait = audio_wait,
|
||||
.wakeup = ao_wakeup_poll,
|
||||
.list_devs = list_devs,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.global_opts = &ao_alsa_conf,
|
||||
|
||||
@@ -1,717 +0,0 @@
|
||||
/*
|
||||
* Android AudioTrack audio output driver.
|
||||
* Copyright (C) 2018 Aman Gupta <aman@tmm1.net>
|
||||
* Copyright (C) 2012-2015 VLC authors and VideoLAN, VideoLabs
|
||||
* Authors: Thomas Guillem <thomas@gllm.fr>
|
||||
* Ming Hu <tewilove@gmail.com>
|
||||
*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ao.h"
|
||||
#include "internal.h"
|
||||
#include "common/msg.h"
|
||||
#include "audio/format.h"
|
||||
#include "options/m_option.h"
|
||||
#include "osdep/threads.h"
|
||||
#include "osdep/timer.h"
|
||||
#include "misc/jni.h"
|
||||
|
||||
struct priv {
|
||||
jobject audiotrack;
|
||||
jint samplerate;
|
||||
jint channel_config;
|
||||
jint format;
|
||||
jint size;
|
||||
|
||||
jobject timestamp;
|
||||
int64_t timestamp_fetched;
|
||||
bool timestamp_set;
|
||||
int timestamp_stable;
|
||||
|
||||
uint32_t written_frames; /* requires uint32_t rollover semantics */
|
||||
uint32_t playhead_pos;
|
||||
uint32_t playhead_offset;
|
||||
bool reset_pending;
|
||||
|
||||
void *chunk;
|
||||
int chunksize;
|
||||
jbyteArray bytearray;
|
||||
jshortArray shortarray;
|
||||
jfloatArray floatarray;
|
||||
jobject bbuf;
|
||||
|
||||
int cfg_pcm_float;
|
||||
int cfg_session_id;
|
||||
|
||||
bool needs_timestamp_offset;
|
||||
int64_t timestamp_offset;
|
||||
|
||||
bool thread_terminate;
|
||||
bool thread_created;
|
||||
pthread_t thread;
|
||||
pthread_mutex_t lock;
|
||||
pthread_cond_t wakeup;
|
||||
};
|
||||
|
||||
struct JNIByteBuffer {
|
||||
jclass clazz;
|
||||
jmethodID clear;
|
||||
struct MPJniField mapping[];
|
||||
} ByteBuffer = {.mapping = {
|
||||
#define OFFSET(member) offsetof(struct JNIByteBuffer, member)
|
||||
{"java/nio/ByteBuffer", NULL, NULL, MP_JNI_CLASS, OFFSET(clazz), 1},
|
||||
{"java/nio/ByteBuffer", "clear", "()Ljava/nio/Buffer;", MP_JNI_METHOD, OFFSET(clear), 1},
|
||||
{0},
|
||||
#undef OFFSET
|
||||
}};
|
||||
|
||||
struct JNIAudioTrack {
|
||||
jclass clazz;
|
||||
jmethodID ctor;
|
||||
jmethodID release;
|
||||
jmethodID getState;
|
||||
jmethodID getPlayState;
|
||||
jmethodID play;
|
||||
jmethodID stop;
|
||||
jmethodID flush;
|
||||
jmethodID pause;
|
||||
jmethodID write;
|
||||
jmethodID writeFloat;
|
||||
jmethodID writeV23;
|
||||
jmethodID writeShortV23;
|
||||
jmethodID writeBufferV21;
|
||||
jmethodID getPlaybackHeadPosition;
|
||||
jmethodID getTimestamp;
|
||||
jmethodID getLatency;
|
||||
jmethodID getMinBufferSize;
|
||||
jmethodID getNativeOutputSampleRate;
|
||||
jint STATE_INITIALIZED;
|
||||
jint PLAYSTATE_STOPPED;
|
||||
jint PLAYSTATE_PAUSED;
|
||||
jint PLAYSTATE_PLAYING;
|
||||
jint MODE_STREAM;
|
||||
jint ERROR;
|
||||
jint ERROR_BAD_VALUE;
|
||||
jint ERROR_INVALID_OPERATION;
|
||||
jint WRITE_BLOCKING;
|
||||
jint WRITE_NON_BLOCKING;
|
||||
struct MPJniField mapping[];
|
||||
} AudioTrack = {.mapping = {
|
||||
#define OFFSET(member) offsetof(struct JNIAudioTrack, member)
|
||||
{"android/media/AudioTrack", NULL, NULL, MP_JNI_CLASS, OFFSET(clazz), 1},
|
||||
{"android/media/AudioTrack", "<init>", "(IIIIIII)V", MP_JNI_METHOD, OFFSET(ctor), 1},
|
||||
{"android/media/AudioTrack", "release", "()V", MP_JNI_METHOD, OFFSET(release), 1},
|
||||
{"android/media/AudioTrack", "getState", "()I", MP_JNI_METHOD, OFFSET(getState), 1},
|
||||
{"android/media/AudioTrack", "getPlayState", "()I", MP_JNI_METHOD, OFFSET(getPlayState), 1},
|
||||
{"android/media/AudioTrack", "play", "()V", MP_JNI_METHOD, OFFSET(play), 1},
|
||||
{"android/media/AudioTrack", "stop", "()V", MP_JNI_METHOD, OFFSET(stop), 1},
|
||||
{"android/media/AudioTrack", "flush", "()V", MP_JNI_METHOD, OFFSET(flush), 1},
|
||||
{"android/media/AudioTrack", "pause", "()V", MP_JNI_METHOD, OFFSET(pause), 1},
|
||||
{"android/media/AudioTrack", "write", "([BII)I", MP_JNI_METHOD, OFFSET(write), 1},
|
||||
{"android/media/AudioTrack", "write", "([FIII)I", MP_JNI_METHOD, OFFSET(writeFloat), 1},
|
||||
{"android/media/AudioTrack", "write", "([BIII)I", MP_JNI_METHOD, OFFSET(writeV23), 0},
|
||||
{"android/media/AudioTrack", "write", "([SIII)I", MP_JNI_METHOD, OFFSET(writeShortV23), 0},
|
||||
{"android/media/AudioTrack", "write", "(Ljava/nio/ByteBuffer;II)I", MP_JNI_METHOD, OFFSET(writeBufferV21), 1},
|
||||
{"android/media/AudioTrack", "getTimestamp", "(Landroid/media/AudioTimestamp;)Z", MP_JNI_METHOD, OFFSET(getTimestamp), 1},
|
||||
{"android/media/AudioTrack", "getPlaybackHeadPosition", "()I", MP_JNI_METHOD, OFFSET(getPlaybackHeadPosition), 1},
|
||||
{"android/media/AudioTrack", "getLatency", "()I", MP_JNI_METHOD, OFFSET(getLatency), 1},
|
||||
{"android/media/AudioTrack", "getMinBufferSize", "(III)I", MP_JNI_STATIC_METHOD, OFFSET(getMinBufferSize), 1},
|
||||
{"android/media/AudioTrack", "getNativeOutputSampleRate", "(I)I", MP_JNI_STATIC_METHOD, OFFSET(getNativeOutputSampleRate), 1},
|
||||
{"android/media/AudioTrack", "WRITE_BLOCKING", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(WRITE_BLOCKING), 0},
|
||||
{"android/media/AudioTrack", "WRITE_NON_BLOCKING", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(WRITE_NON_BLOCKING), 0},
|
||||
{"android/media/AudioTrack", "STATE_INITIALIZED", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(STATE_INITIALIZED), 1},
|
||||
{"android/media/AudioTrack", "PLAYSTATE_STOPPED", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(PLAYSTATE_STOPPED), 1},
|
||||
{"android/media/AudioTrack", "PLAYSTATE_PAUSED", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(PLAYSTATE_PAUSED), 1},
|
||||
{"android/media/AudioTrack", "PLAYSTATE_PLAYING", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(PLAYSTATE_PLAYING), 1},
|
||||
{"android/media/AudioTrack", "MODE_STREAM", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(MODE_STREAM), 1},
|
||||
{"android/media/AudioTrack", "ERROR", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ERROR), 1},
|
||||
{"android/media/AudioTrack", "ERROR_BAD_VALUE", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ERROR_BAD_VALUE), 1},
|
||||
{"android/media/AudioTrack", "ERROR_INVALID_OPERATION", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ERROR_INVALID_OPERATION), 1},
|
||||
{0}
|
||||
#undef OFFSET
|
||||
}};
|
||||
|
||||
struct JNIAudioFormat {
|
||||
jclass clazz;
|
||||
jint ENCODING_PCM_8BIT;
|
||||
jint ENCODING_PCM_16BIT;
|
||||
jint ENCODING_PCM_FLOAT;
|
||||
jint ENCODING_IEC61937;
|
||||
jint ENCODING_AC3;
|
||||
jint CHANNEL_OUT_MONO;
|
||||
jint CHANNEL_OUT_STEREO;
|
||||
jint CHANNEL_OUT_FRONT_LEFT;
|
||||
jint CHANNEL_OUT_FRONT_RIGHT;
|
||||
jint CHANNEL_OUT_BACK_LEFT;
|
||||
jint CHANNEL_OUT_BACK_RIGHT;
|
||||
jint CHANNEL_OUT_FRONT_CENTER;
|
||||
jint CHANNEL_OUT_LOW_FREQUENCY;
|
||||
jint CHANNEL_OUT_BACK_CENTER;
|
||||
jint CHANNEL_OUT_5POINT1;
|
||||
jint CHANNEL_OUT_SIDE_LEFT;
|
||||
jint CHANNEL_OUT_SIDE_RIGHT;
|
||||
struct MPJniField mapping[];
|
||||
} AudioFormat = {.mapping = {
|
||||
#define OFFSET(member) offsetof(struct JNIAudioFormat, member)
|
||||
{"android/media/AudioFormat", NULL, NULL, MP_JNI_CLASS, OFFSET(clazz), 1},
|
||||
{"android/media/AudioFormat", "ENCODING_PCM_8BIT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ENCODING_PCM_8BIT), 1},
|
||||
{"android/media/AudioFormat", "ENCODING_PCM_16BIT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ENCODING_PCM_16BIT), 1},
|
||||
{"android/media/AudioFormat", "ENCODING_PCM_FLOAT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ENCODING_PCM_FLOAT), 1},
|
||||
{"android/media/AudioFormat", "ENCODING_AC3", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ENCODING_AC3), 0},
|
||||
{"android/media/AudioFormat", "ENCODING_IEC61937", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ENCODING_IEC61937), 0},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_MONO", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_MONO), 1},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_STEREO", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_STEREO), 1},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_5POINT1", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_5POINT1), 1},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_FRONT_LEFT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_FRONT_LEFT), 1},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_FRONT_RIGHT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_FRONT_RIGHT), 1},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_FRONT_CENTER", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_FRONT_CENTER), 1},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_LOW_FREQUENCY", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_LOW_FREQUENCY), 1},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_BACK_LEFT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_BACK_LEFT), 1},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_BACK_RIGHT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_BACK_RIGHT), 1},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_BACK_CENTER", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_BACK_CENTER), 1},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_SIDE_LEFT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_SIDE_LEFT), 0},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_SIDE_RIGHT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_SIDE_RIGHT), 0},
|
||||
{0}
|
||||
#undef OFFSET
|
||||
}};
|
||||
|
||||
struct JNIAudioManager {
|
||||
jclass clazz;
|
||||
jint ERROR_DEAD_OBJECT;
|
||||
jint STREAM_MUSIC;
|
||||
struct MPJniField mapping[];
|
||||
} AudioManager = {.mapping = {
|
||||
#define OFFSET(member) offsetof(struct JNIAudioManager, member)
|
||||
{"android/media/AudioManager", NULL, NULL, MP_JNI_CLASS, OFFSET(clazz), 1},
|
||||
{"android/media/AudioManager", "STREAM_MUSIC", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(STREAM_MUSIC), 1},
|
||||
{"android/media/AudioManager", "ERROR_DEAD_OBJECT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ERROR_DEAD_OBJECT), 0},
|
||||
{0}
|
||||
#undef OFFSET
|
||||
}};
|
||||
|
||||
struct JNIAudioTimestamp {
|
||||
jclass clazz;
|
||||
jmethodID ctor;
|
||||
jfieldID framePosition;
|
||||
jfieldID nanoTime;
|
||||
struct MPJniField mapping[];
|
||||
} AudioTimestamp = {.mapping = {
|
||||
#define OFFSET(member) offsetof(struct JNIAudioTimestamp, member)
|
||||
{"android/media/AudioTimestamp", NULL, NULL, MP_JNI_CLASS, OFFSET(clazz), 1},
|
||||
{"android/media/AudioTimestamp", "<init>", "()V", MP_JNI_METHOD, OFFSET(ctor), 1},
|
||||
{"android/media/AudioTimestamp", "framePosition", "J", MP_JNI_FIELD, OFFSET(framePosition), 1},
|
||||
{"android/media/AudioTimestamp", "nanoTime", "J", MP_JNI_FIELD, OFFSET(nanoTime), 1},
|
||||
{0}
|
||||
#undef OFFSET
|
||||
}};
|
||||
|
||||
static int AudioTrack_New(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
JNIEnv *env = MP_JNI_GET_ENV(ao);
|
||||
|
||||
jobject audiotrack = MP_JNI_NEW(
|
||||
AudioTrack.clazz,
|
||||
AudioTrack.ctor,
|
||||
AudioManager.STREAM_MUSIC,
|
||||
p->samplerate,
|
||||
p->channel_config,
|
||||
p->format,
|
||||
p->size,
|
||||
AudioTrack.MODE_STREAM,
|
||||
p->cfg_session_id
|
||||
);
|
||||
if (!audiotrack || MP_JNI_EXCEPTION_LOG(ao) < 0) {
|
||||
MP_FATAL(ao, "AudioTrack Init failed\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (MP_JNI_CALL_INT(audiotrack, AudioTrack.getState) != AudioTrack.STATE_INITIALIZED) {
|
||||
MP_JNI_CALL_VOID(audiotrack, AudioTrack.release);
|
||||
MP_JNI_EXCEPTION_LOG(ao);
|
||||
(*env)->DeleteLocalRef(env, audiotrack);
|
||||
MP_ERR(ao, "AudioTrack.getState failed\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
p->audiotrack = (*env)->NewGlobalRef(env, audiotrack);
|
||||
(*env)->DeleteLocalRef(env, audiotrack);
|
||||
if (!p->audiotrack)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int AudioTrack_Recreate(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
JNIEnv *env = MP_JNI_GET_ENV(ao);
|
||||
|
||||
MP_JNI_CALL_VOID(p->audiotrack, AudioTrack.release);
|
||||
MP_JNI_EXCEPTION_LOG(ao);
|
||||
(*env)->DeleteGlobalRef(env, p->audiotrack);
|
||||
p->audiotrack = NULL;
|
||||
return AudioTrack_New(ao);
|
||||
}
|
||||
|
||||
static uint32_t AudioTrack_getPlaybackHeadPosition(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
if (!p->audiotrack)
|
||||
return 0;
|
||||
JNIEnv *env = MP_JNI_GET_ENV(ao);
|
||||
uint32_t pos = 0;
|
||||
int64_t now = mp_raw_time_us();
|
||||
int state = MP_JNI_CALL_INT(p->audiotrack, AudioTrack.getPlayState);
|
||||
|
||||
int stable_count = 20;
|
||||
int64_t wait = p->timestamp_stable < stable_count ? 50000 : 3000000;
|
||||
|
||||
if (state == AudioTrack.PLAYSTATE_PLAYING && p->format != AudioFormat.ENCODING_IEC61937 &&
|
||||
(p->timestamp_fetched == 0 || now - p->timestamp_fetched >= wait)) {
|
||||
if (!p->timestamp_fetched)
|
||||
p->timestamp_stable = 0;
|
||||
|
||||
int64_t utime1 = MP_JNI_GET_LONG(p->timestamp, AudioTimestamp.nanoTime) / 1000;
|
||||
if (MP_JNI_CALL_BOOL(p->audiotrack, AudioTrack.getTimestamp, p->timestamp)) {
|
||||
p->timestamp_set = true;
|
||||
p->timestamp_fetched = now;
|
||||
if (p->timestamp_stable < stable_count) {
|
||||
uint32_t fpos = 0xFFFFFFFFL & MP_JNI_GET_LONG(p->timestamp, AudioTimestamp.framePosition);
|
||||
int64_t utime2 = MP_JNI_GET_LONG(p->timestamp, AudioTimestamp.nanoTime) / 1000;
|
||||
//MP_VERBOSE(ao, "getTimestamp: fpos= %u / time= %"PRId64" / now= %"PRId64" / stable= %d\n", fpos, utime2, now, p->timestamp_stable);
|
||||
if (utime1 != utime2 && utime2 != 0 && fpos != 0) {
|
||||
p->timestamp_stable++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* AudioTrack's framePosition and playbackHeadPosition return a signed integer,
|
||||
* but documentation states it should be interpreted as a 32-bit unsigned integer.
|
||||
*/
|
||||
if (p->timestamp_set) {
|
||||
pos = 0xFFFFFFFFL & MP_JNI_GET_LONG(p->timestamp, AudioTimestamp.framePosition);
|
||||
uint32_t fpos = pos;
|
||||
int64_t utime = MP_JNI_GET_LONG(p->timestamp, AudioTimestamp.nanoTime) / 1000;
|
||||
if (utime == 0)
|
||||
fpos = pos = 0;
|
||||
if (p->needs_timestamp_offset) {
|
||||
if (utime != 0 && !p->timestamp_offset)
|
||||
p->timestamp_offset = now - utime;
|
||||
utime += p->timestamp_offset;
|
||||
}
|
||||
if (fpos != 0 && utime != 0 && state == AudioTrack.PLAYSTATE_PLAYING) {
|
||||
double diff = (double)(now - utime) / 1e6;
|
||||
pos += diff * ao->samplerate;
|
||||
}
|
||||
//MP_VERBOSE(ao, "position = %u via getTimestamp (state = %d / fpos= %u / time= %"PRId64")\n", pos, state, fpos, utime);
|
||||
} else {
|
||||
pos = 0xFFFFFFFFL & MP_JNI_CALL_INT(p->audiotrack, AudioTrack.getPlaybackHeadPosition);
|
||||
//MP_VERBOSE(ao, "playbackHeadPosition = %u (reset_pending=%d)\n", pos, p->reset_pending);
|
||||
}
|
||||
|
||||
|
||||
if (p->format == AudioFormat.ENCODING_IEC61937) {
|
||||
if (p->reset_pending) {
|
||||
// after a flush(), playbackHeadPosition will not reset to 0 right away.
|
||||
// sometimes, it will never reset at all.
|
||||
// save the initial offset after the reset, to subtract it going forward.
|
||||
if (p->playhead_offset == 0)
|
||||
p->playhead_offset = pos;
|
||||
p->reset_pending = false;
|
||||
MP_VERBOSE(ao, "IEC/playbackHead offset = %d\n", pos);
|
||||
}
|
||||
|
||||
// usually shortly after a flush(), playbackHeadPosition will reset to 0.
|
||||
// clear out the position and offset to avoid regular "rollover" below
|
||||
if (pos == 0 && p->playhead_offset != 0) {
|
||||
MP_VERBOSE(ao, "IEC/playbackHeadPosition %d -> %d (flush)\n", p->playhead_pos, pos);
|
||||
p->playhead_offset = 0;
|
||||
p->playhead_pos = 0;
|
||||
}
|
||||
|
||||
// sometimes on a new AudioTrack instance, playbackHeadPosition will reset
|
||||
// to 0 shortly after playback starts for no reason.
|
||||
if (pos == 0 && p->playhead_pos != 0) {
|
||||
MP_VERBOSE(ao, "IEC/playbackHeadPosition %d -> %d (reset)\n", p->playhead_pos, pos);
|
||||
p->playhead_offset = 0;
|
||||
p->playhead_pos = 0;
|
||||
p->written_frames = 0;
|
||||
}
|
||||
}
|
||||
|
||||
p->playhead_pos = pos;
|
||||
return p->playhead_pos - p->playhead_offset;
|
||||
}
|
||||
|
||||
static double AudioTrack_getLatency(struct ao *ao)
|
||||
{
|
||||
JNIEnv *env = MP_JNI_GET_ENV(ao);
|
||||
struct priv *p = ao->priv;
|
||||
if (!p->audiotrack)
|
||||
return 0;
|
||||
|
||||
uint32_t playhead = AudioTrack_getPlaybackHeadPosition(ao);
|
||||
uint32_t diff = p->written_frames - playhead;
|
||||
double delay = diff / (double)(ao->samplerate);
|
||||
if (!p->timestamp_set &&
|
||||
p->format != AudioFormat.ENCODING_IEC61937)
|
||||
delay += (double)MP_JNI_CALL_INT(p->audiotrack, AudioTrack.getLatency)/1000.0;
|
||||
if (delay > 1.0) {
|
||||
//MP_WARN(ao, "getLatency: written=%u playhead=%u diff=%u delay=%f\n", p->written_frames, playhead, diff, delay);
|
||||
p->timestamp_fetched = 0;
|
||||
return 0;
|
||||
}
|
||||
return MPCLAMP(delay, 0.0, 1.0);
|
||||
}
|
||||
|
||||
static int AudioTrack_write(struct ao *ao, int len)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
if (!p->audiotrack)
|
||||
return -1;
|
||||
JNIEnv *env = MP_JNI_GET_ENV(ao);
|
||||
void *buf = p->chunk;
|
||||
|
||||
jint ret;
|
||||
if (p->format == AudioFormat.ENCODING_IEC61937) {
|
||||
(*env)->SetShortArrayRegion(env, p->shortarray, 0, len / 2, buf);
|
||||
if (MP_JNI_EXCEPTION_LOG(ao) < 0) return -1;
|
||||
ret = MP_JNI_CALL_INT(p->audiotrack, AudioTrack.writeShortV23, p->shortarray, 0, len / 2, AudioTrack.WRITE_BLOCKING);
|
||||
if (MP_JNI_EXCEPTION_LOG(ao) < 0) return -1;
|
||||
if (ret > 0) ret *= 2;
|
||||
|
||||
} else if (p->format == AudioFormat.ENCODING_PCM_FLOAT) {
|
||||
(*env)->SetFloatArrayRegion(env, p->floatarray, 0, len / sizeof(float), buf);
|
||||
if (MP_JNI_EXCEPTION_LOG(ao) < 0) return -1;
|
||||
ret = MP_JNI_CALL_INT(p->audiotrack, AudioTrack.writeFloat, p->floatarray, 0, len / sizeof(float), AudioTrack.WRITE_BLOCKING);
|
||||
if (MP_JNI_EXCEPTION_LOG(ao) < 0) return -1;
|
||||
if (ret > 0) ret *= sizeof(float);
|
||||
|
||||
} else if (AudioTrack.writeBufferV21) {
|
||||
jobject bbuf = MP_JNI_CALL_OBJECT(p->bbuf, ByteBuffer.clear);
|
||||
if (MP_JNI_EXCEPTION_LOG(ao) < 0) return -1;
|
||||
(*env)->DeleteLocalRef(env, bbuf);
|
||||
ret = MP_JNI_CALL_INT(p->audiotrack, AudioTrack.writeBufferV21, p->bbuf, len, AudioTrack.WRITE_BLOCKING);
|
||||
if (MP_JNI_EXCEPTION_LOG(ao) < 0) return -1;
|
||||
|
||||
} else {
|
||||
(*env)->SetByteArrayRegion(env, p->bytearray, 0, len, buf);
|
||||
if (MP_JNI_EXCEPTION_LOG(ao) < 0) return -1;
|
||||
ret = MP_JNI_CALL_INT(p->audiotrack, AudioTrack.write, p->bytearray, 0, len);
|
||||
if (MP_JNI_EXCEPTION_LOG(ao) < 0) return -1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void uninit_jni(struct ao *ao)
|
||||
{
|
||||
JNIEnv *env = MP_JNI_GET_ENV(ao);
|
||||
mp_jni_reset_jfields(env, &AudioTrack, AudioTrack.mapping, 1, ao->log);
|
||||
mp_jni_reset_jfields(env, &AudioTimestamp, AudioTimestamp.mapping, 1, ao->log);
|
||||
mp_jni_reset_jfields(env, &AudioManager, AudioManager.mapping, 1, ao->log);
|
||||
mp_jni_reset_jfields(env, &AudioFormat, AudioFormat.mapping, 1, ao->log);
|
||||
mp_jni_reset_jfields(env, &ByteBuffer, ByteBuffer.mapping, 1, ao->log);
|
||||
}
|
||||
|
||||
static int init_jni(struct ao *ao)
|
||||
{
|
||||
JNIEnv *env = MP_JNI_GET_ENV(ao);
|
||||
if (mp_jni_init_jfields(env, &AudioTrack, AudioTrack.mapping, 1, ao->log) < 0 ||
|
||||
mp_jni_init_jfields(env, &ByteBuffer, ByteBuffer.mapping, 1, ao->log) < 0 ||
|
||||
mp_jni_init_jfields(env, &AudioTimestamp, AudioTimestamp.mapping, 1, ao->log) < 0 ||
|
||||
mp_jni_init_jfields(env, &AudioManager, AudioManager.mapping, 1, ao->log) < 0 ||
|
||||
mp_jni_init_jfields(env, &AudioFormat, AudioFormat.mapping, 1, ao->log) < 0) {
|
||||
uninit_jni(ao);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void *playthread(void *arg)
|
||||
{
|
||||
struct ao *ao = arg;
|
||||
struct priv *p = ao->priv;
|
||||
JNIEnv *env = MP_JNI_GET_ENV(ao);
|
||||
mpthread_set_name("audiotrack");
|
||||
pthread_mutex_lock(&p->lock);
|
||||
while (!p->thread_terminate) {
|
||||
int state = AudioTrack.PLAYSTATE_PAUSED;
|
||||
if (p->audiotrack) {
|
||||
state = MP_JNI_CALL_INT(p->audiotrack, AudioTrack.getPlayState);
|
||||
}
|
||||
if (state == AudioTrack.PLAYSTATE_PLAYING) {
|
||||
int read_samples = p->chunksize / ao->sstride;
|
||||
int64_t ts = mp_time_us();
|
||||
ts += (read_samples / (double)(ao->samplerate)) * 1e6;
|
||||
ts += AudioTrack_getLatency(ao) * 1e6;
|
||||
int samples = ao_read_data(ao, &p->chunk, read_samples, ts);
|
||||
int write_samples = read_samples;
|
||||
int ret = AudioTrack_write(ao, write_samples * ao->sstride);
|
||||
if (ret >= 0) {
|
||||
p->written_frames += ret / ao->sstride;
|
||||
} else if (ret == AudioManager.ERROR_DEAD_OBJECT) {
|
||||
MP_WARN(ao, "AudioTrack.write failed with ERROR_DEAD_OBJECT. Recreating AudioTrack...\n");
|
||||
if (AudioTrack_Recreate(ao) < 0) {
|
||||
MP_ERR(ao, "AudioTrack_Recreate failed\n");
|
||||
}
|
||||
} else {
|
||||
MP_ERR(ao, "AudioTrack.write failed with %d\n", ret);
|
||||
}
|
||||
} else {
|
||||
struct timespec wait = mp_rel_time_to_timespec(0.300);
|
||||
pthread_cond_timedwait(&p->wakeup, &p->lock, &wait);
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&p->lock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void uninit(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
JNIEnv *env = MP_JNI_GET_ENV(ao);
|
||||
if (p->audiotrack) {
|
||||
MP_JNI_CALL_VOID(p->audiotrack, AudioTrack.stop);
|
||||
MP_JNI_EXCEPTION_LOG(ao);
|
||||
MP_JNI_CALL_VOID(p->audiotrack, AudioTrack.flush);
|
||||
MP_JNI_EXCEPTION_LOG(ao);
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&p->lock);
|
||||
p->thread_terminate = true;
|
||||
pthread_cond_signal(&p->wakeup);
|
||||
pthread_mutex_unlock(&p->lock);
|
||||
|
||||
if (p->thread_created)
|
||||
pthread_join(p->thread, NULL);
|
||||
|
||||
if (p->audiotrack) {
|
||||
MP_JNI_CALL_VOID(p->audiotrack, AudioTrack.release);
|
||||
MP_JNI_EXCEPTION_LOG(ao);
|
||||
(*env)->DeleteGlobalRef(env, p->audiotrack);
|
||||
p->audiotrack = NULL;
|
||||
}
|
||||
|
||||
if (p->bytearray) {
|
||||
(*env)->DeleteGlobalRef(env, p->bytearray);
|
||||
p->bytearray = NULL;
|
||||
}
|
||||
|
||||
if (p->shortarray) {
|
||||
(*env)->DeleteGlobalRef(env, p->shortarray);
|
||||
p->shortarray = NULL;
|
||||
}
|
||||
|
||||
if (p->floatarray) {
|
||||
(*env)->DeleteGlobalRef(env, p->floatarray);
|
||||
p->floatarray = NULL;
|
||||
}
|
||||
|
||||
if (p->bbuf) {
|
||||
(*env)->DeleteGlobalRef(env, p->bbuf);
|
||||
p->bbuf = NULL;
|
||||
}
|
||||
|
||||
if (p->timestamp) {
|
||||
(*env)->DeleteGlobalRef(env, p->timestamp);
|
||||
p->timestamp = NULL;
|
||||
}
|
||||
|
||||
if (p->chunk) {
|
||||
free(p->chunk);
|
||||
p->chunk = NULL;
|
||||
}
|
||||
|
||||
pthread_cond_destroy(&p->wakeup);
|
||||
pthread_mutex_destroy(&p->lock);
|
||||
|
||||
uninit_jni(ao);
|
||||
}
|
||||
|
||||
static int init(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
JNIEnv *env = MP_JNI_GET_ENV(ao);
|
||||
if (!env)
|
||||
return -1;
|
||||
|
||||
pthread_mutex_init(&p->lock, NULL);
|
||||
pthread_cond_init(&p->wakeup, NULL);
|
||||
|
||||
if (init_jni(ao) < 0)
|
||||
return -1;
|
||||
|
||||
if (af_fmt_is_spdif(ao->format)) {
|
||||
p->format = AudioFormat.ENCODING_IEC61937;
|
||||
} else if (ao->format == AF_FORMAT_U8) {
|
||||
p->format = AudioFormat.ENCODING_PCM_8BIT;
|
||||
} else if (p->cfg_pcm_float && (ao->format == AF_FORMAT_FLOAT || ao->format == AF_FORMAT_FLOATP)) {
|
||||
ao->format = AF_FORMAT_FLOAT;
|
||||
p->format = AudioFormat.ENCODING_PCM_FLOAT;
|
||||
} else {
|
||||
ao->format = AF_FORMAT_S16;
|
||||
p->format = AudioFormat.ENCODING_PCM_16BIT;
|
||||
}
|
||||
|
||||
if (AudioTrack.getNativeOutputSampleRate) {
|
||||
jint samplerate = MP_JNI_CALL_STATIC_INT(
|
||||
AudioTrack.clazz,
|
||||
AudioTrack.getNativeOutputSampleRate,
|
||||
AudioManager.STREAM_MUSIC
|
||||
);
|
||||
if (MP_JNI_EXCEPTION_LOG(ao) == 0) {
|
||||
ao->samplerate = samplerate;
|
||||
MP_VERBOSE(ao, "AudioTrack.nativeOutputSampleRate = %d\n", samplerate);
|
||||
}
|
||||
}
|
||||
p->samplerate = ao->samplerate;
|
||||
|
||||
if (p->format == AudioFormat.ENCODING_IEC61937) {
|
||||
p->channel_config = AudioFormat.CHANNEL_OUT_STEREO;
|
||||
} else if (ao->channels.num == 1) {
|
||||
p->channel_config = AudioFormat.CHANNEL_OUT_MONO;
|
||||
} else if (ao->channels.num == 6) {
|
||||
p->channel_config = AudioFormat.CHANNEL_OUT_5POINT1;
|
||||
ao->channels = (struct mp_chmap)MP_CHMAP6(FL, FR, FC, LFE, BL, BR);
|
||||
} else {
|
||||
p->channel_config = AudioFormat.CHANNEL_OUT_STEREO;
|
||||
ao->channels = (struct mp_chmap)MP_CHMAP_INIT_STEREO;
|
||||
}
|
||||
|
||||
jint buffer_size = MP_JNI_CALL_STATIC_INT(
|
||||
AudioTrack.clazz,
|
||||
AudioTrack.getMinBufferSize,
|
||||
p->samplerate,
|
||||
p->channel_config,
|
||||
p->format
|
||||
);
|
||||
if (buffer_size <= 0 || MP_JNI_EXCEPTION_LOG(ao) < 0) {
|
||||
MP_FATAL(ao, "AudioTrack.getMinBufferSize returned an invalid size: %d", buffer_size);
|
||||
return -1;
|
||||
}
|
||||
p->chunksize = buffer_size;
|
||||
p->chunk = malloc(buffer_size);
|
||||
|
||||
int min = 0.200 * p->samplerate * af_fmt_to_bytes(ao->format);
|
||||
int max = min * 3 / 2;
|
||||
p->size = MPCLAMP(buffer_size * 2, min, max);
|
||||
MP_VERBOSE(ao, "Setting bufferSize = %d (driver=%d, min=%d, max=%d)\n", p->size, buffer_size, min, max);
|
||||
ao->device_buffer = p->size / af_fmt_to_bytes(ao->format);
|
||||
|
||||
jobject timestamp = MP_JNI_NEW(AudioTimestamp.clazz, AudioTimestamp.ctor);
|
||||
if (!timestamp || MP_JNI_EXCEPTION_LOG(ao) < 0) {
|
||||
MP_FATAL(ao, "AudioTimestamp could not be created\n");
|
||||
return -1;
|
||||
}
|
||||
p->timestamp = (*env)->NewGlobalRef(env, timestamp);
|
||||
(*env)->DeleteLocalRef(env, timestamp);
|
||||
|
||||
if (p->format == AudioFormat.ENCODING_IEC61937) {
|
||||
jshortArray shortarray = (*env)->NewShortArray(env, p->chunksize / 2);
|
||||
p->shortarray = (*env)->NewGlobalRef(env, shortarray);
|
||||
(*env)->DeleteLocalRef(env, shortarray);
|
||||
} else if (p->format == AudioFormat.ENCODING_PCM_FLOAT) {
|
||||
jfloatArray floatarray = (*env)->NewFloatArray(env, p->chunksize / sizeof(float));
|
||||
p->floatarray = (*env)->NewGlobalRef(env, floatarray);
|
||||
(*env)->DeleteLocalRef(env, floatarray);
|
||||
} else if (AudioTrack.writeBufferV21) {
|
||||
jobject bbuf = (*env)->NewDirectByteBuffer(env, p->chunk, p->chunksize);
|
||||
p->bbuf = (*env)->NewGlobalRef(env, bbuf);
|
||||
(*env)->DeleteLocalRef(env, bbuf);
|
||||
} else {
|
||||
jbyteArray bytearray = (*env)->NewByteArray(env, p->chunksize);
|
||||
p->bytearray = (*env)->NewGlobalRef(env, bytearray);
|
||||
(*env)->DeleteLocalRef(env, bytearray);
|
||||
}
|
||||
|
||||
/* create AudioTrack object */
|
||||
if (AudioTrack_New(ao) != 0) {
|
||||
MP_FATAL(ao, "Failed to create AudioTrack\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (pthread_create(&p->thread, NULL, playthread, ao)) {
|
||||
MP_ERR(ao, "pthread creation failed\n");
|
||||
goto error;
|
||||
}
|
||||
p->thread_created = true;
|
||||
|
||||
return 1;
|
||||
|
||||
error:
|
||||
uninit(ao);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void stop(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
if (!p->audiotrack) {
|
||||
MP_ERR(ao, "AudioTrack does not exist to stop!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
JNIEnv *env = MP_JNI_GET_ENV(ao);
|
||||
MP_JNI_CALL_VOID(p->audiotrack, AudioTrack.pause);
|
||||
MP_JNI_EXCEPTION_LOG(ao);
|
||||
MP_JNI_CALL_VOID(p->audiotrack, AudioTrack.flush);
|
||||
MP_JNI_EXCEPTION_LOG(ao);
|
||||
|
||||
p->playhead_offset = 0;
|
||||
p->reset_pending = true;
|
||||
p->written_frames = 0;
|
||||
p->timestamp_fetched = 0;
|
||||
p->timestamp_set = false;
|
||||
p->timestamp_offset = 0;
|
||||
}
|
||||
|
||||
static void start(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
if (!p->audiotrack) {
|
||||
MP_ERR(ao, "AudioTrack does not exist to start!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
JNIEnv *env = MP_JNI_GET_ENV(ao);
|
||||
MP_JNI_CALL_VOID(p->audiotrack, AudioTrack.play);
|
||||
MP_JNI_EXCEPTION_LOG(ao);
|
||||
|
||||
pthread_cond_signal(&p->wakeup);
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
const struct ao_driver audio_out_audiotrack = {
|
||||
.description = "Android AudioTrack audio output",
|
||||
.name = "audiotrack",
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.reset = stop,
|
||||
.start = start,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.options = (const struct m_option[]) {
|
||||
{"pcm-float", OPT_FLAG(cfg_pcm_float)},
|
||||
{"session-id", OPT_INT(cfg_session_id)},
|
||||
{0}
|
||||
},
|
||||
.options_prefix = "audiotrack",
|
||||
};
|
||||
Loaded 100 of 638 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user