Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7214f1f076 | ||
|
|
789e04e9a0 | ||
|
|
2ae812ec6d | ||
|
|
417fe22f67 | ||
|
|
b889623768 | ||
|
|
db0af67e7e |
No files matched your search
@@ -1,10 +0,0 @@
|
||||
# To use this config on you editor, follow the instructions at:
|
||||
# http://editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
@@ -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
|
||||
differs 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
|
||||
differs 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
|
||||
differs 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
|
||||
differs 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:meson'
|
||||
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
|
||||
differs from your own.
|
||||
|
||||
### Expected behavior
|
||||
|
||||
### Actual behavior
|
||||
|
||||
### Log file
|
||||
|
||||
Upload meson-logs/meson-log.txt or meson setup build output to https://0x0.st/ or attach
|
||||
it to the github issue, and replace this text with a link to it.
|
||||
|
||||
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.libera.chat/mpv
|
||||
- name: mpv IRC developer channel
|
||||
url: https://mpv.io/community/
|
||||
about: Ask questions related to the development of mpv here
|
||||
irc://irc.libera.chat/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.
|
||||
@@ -1,298 +0,0 @@
|
||||
name: build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- ci
|
||||
- 'release/**'
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
mingw:
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
matrix:
|
||||
target: [i686-w64-mingw32, x86_64-w64-mingw32]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y autoconf automake pkg-config g++-mingw-w64 gcc-multilib python3-pip ninja-build nasm
|
||||
sudo python3 -m pip install meson
|
||||
./bootstrap.py
|
||||
|
||||
- name: Build libraries
|
||||
run: |
|
||||
./ci/build-mingw64.sh
|
||||
env:
|
||||
TARGET: ${{ matrix.target }}
|
||||
|
||||
- name: Build with meson
|
||||
id: build
|
||||
run: |
|
||||
./ci/build-mingw64.sh meson pack
|
||||
env:
|
||||
TARGET: ${{ matrix.target }}
|
||||
|
||||
- name: Print meson log
|
||||
if: ${{ failure() && steps.build.outcome == 'failure' }}
|
||||
run: |
|
||||
cat ./build/meson-logs/meson-log.txt
|
||||
|
||||
- name: Build with waf
|
||||
id: build_waf
|
||||
run: |
|
||||
./ci/build-mingw64.sh waf
|
||||
env:
|
||||
TARGET: ${{ matrix.target }}
|
||||
|
||||
- name: Print waf log
|
||||
if: ${{ failure() && steps.build_waf.outcome == 'failure' }}
|
||||
run: |
|
||||
cat ./build_waf/config.log
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: mpv-${{ matrix.target }}
|
||||
path: mpv-git-*.zip
|
||||
|
||||
macos:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
cc:
|
||||
- "clang"
|
||||
os:
|
||||
- "macos-11"
|
||||
- "macos-12"
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Remove stray upstream python binary symlinks under /usr/local
|
||||
run: |
|
||||
find /usr/local/bin -lname '*/Library/Frameworks/Python.framework/*' -delete -print
|
||||
brew unlink python && brew link --overwrite python
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
brew update
|
||||
brew install autoconf automake pkg-config libtool python freetype fribidi little-cms2 lua@5.1 libass ffmpeg meson
|
||||
|
||||
- name: Build with meson
|
||||
id: build
|
||||
run: |
|
||||
./ci/build-macos.sh meson
|
||||
env:
|
||||
CC: "${{ matrix.cc }}"
|
||||
TRAVIS_OS_NAME: "${{ matrix.os }}"
|
||||
|
||||
- name: Print meson log
|
||||
if: ${{ failure() && steps.build.outcome == 'failure' }}
|
||||
run: |
|
||||
cat ./build/meson-logs/meson-log.txt
|
||||
|
||||
- name: Run meson tests
|
||||
id: tests
|
||||
run: |
|
||||
meson test -C build
|
||||
|
||||
- name: Print meson test log
|
||||
if: ${{ failure() && steps.tests.outcome == 'failure' }}
|
||||
run: |
|
||||
cat ./build/meson-logs/testlog.txt
|
||||
|
||||
- name: Build with waf
|
||||
id: build_waf
|
||||
run: |
|
||||
./ci/build-macos.sh waf
|
||||
env:
|
||||
CC: "${{ matrix.cc }}"
|
||||
TRAVIS_OS_NAME: "${{ matrix.os }}"
|
||||
|
||||
- name: Print waf log
|
||||
if: ${{ failure() && steps.build_waf.outcome == 'failure' }}
|
||||
run: |
|
||||
cat ./build_waf/config.log
|
||||
|
||||
linux:
|
||||
runs-on: "ubuntu-22.04"
|
||||
container:
|
||||
image: "registry.opensuse.org/home/mia/images/images/mpv-ci:stable-deps"
|
||||
env:
|
||||
CC: "${{ matrix.cc }}"
|
||||
strategy:
|
||||
matrix:
|
||||
cc:
|
||||
- "gcc"
|
||||
- "clang"
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
./bootstrap.py
|
||||
# workaround to avoid "fatal: unsafe repository" error
|
||||
git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||
|
||||
- name: Build with meson
|
||||
id: build
|
||||
run: |
|
||||
./ci/build-tumbleweed.sh meson
|
||||
|
||||
- name: Print meson log
|
||||
if: ${{ failure() && steps.build.outcome == 'failure' }}
|
||||
run: |
|
||||
cat ./build/meson-logs/meson-log.txt
|
||||
|
||||
- name: Run meson tests
|
||||
id: tests
|
||||
run: |
|
||||
meson test -C build
|
||||
|
||||
- name: Print meson test log
|
||||
if: ${{ failure() && steps.tests.outcome == 'failure' }}
|
||||
run: |
|
||||
cat ./build/meson-logs/testlog.txt
|
||||
|
||||
- name: Build with waf
|
||||
id: build_waf
|
||||
run: |
|
||||
./ci/build-tumbleweed.sh waf
|
||||
|
||||
- name: Print waf log
|
||||
if: ${{ failure() && steps.build_waf.outcome == 'failure' }}
|
||||
run: |
|
||||
cat ./build_waf/config.log
|
||||
|
||||
freebsd:
|
||||
runs-on: macos-12 # until https://github.com/actions/runner/issues/385
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Test in FreeBSD VM
|
||||
uses: vmactions/freebsd-vm@v0
|
||||
with:
|
||||
mem: 8192
|
||||
copyback: false
|
||||
usesh: true
|
||||
prepare: |
|
||||
# Requested in ci/build-freebsd.sh
|
||||
pkg install -y \
|
||||
cmake \
|
||||
evdev-proto \
|
||||
ffmpeg \
|
||||
libplacebo \
|
||||
libxkbcommon \
|
||||
libXinerama \
|
||||
libxpresent \
|
||||
luajit \
|
||||
meson \
|
||||
openal-soft \
|
||||
pkgconf \
|
||||
python3 \
|
||||
sdl2 \
|
||||
sndio \
|
||||
vulkan-headers \
|
||||
wayland-protocols \
|
||||
#
|
||||
# Optionally auto-enabled
|
||||
pkg install -y \
|
||||
alsa-lib \
|
||||
jackit \
|
||||
libXv \
|
||||
libarchive \
|
||||
libbluray \
|
||||
libcaca \
|
||||
libcdio-paranoia \
|
||||
libdvdnav \
|
||||
mujs \
|
||||
pipewire \
|
||||
pulseaudio \
|
||||
rubberband \
|
||||
sekrit-twc-zimg \
|
||||
uchardet \
|
||||
v4l_compat \
|
||||
#
|
||||
run: |
|
||||
./ci/build-freebsd.sh
|
||||
meson test -C build
|
||||
|
||||
msys2:
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
sys:
|
||||
- clang32
|
||||
- clang64
|
||||
- mingw32
|
||||
- mingw64
|
||||
- ucrt64
|
||||
defaults:
|
||||
run:
|
||||
shell: msys2 {0}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: ${{ matrix.sys }}
|
||||
update: true
|
||||
install: git
|
||||
pacboy: >-
|
||||
angleproject:p
|
||||
ca-certificates:p
|
||||
cc:p
|
||||
diffutils:p
|
||||
ffmpeg:p
|
||||
lcms2:p
|
||||
libarchive:p
|
||||
libass:p
|
||||
libcdio-paranoia:p
|
||||
libdvdnav:p
|
||||
libjpeg-turbo:p
|
||||
libplacebo:p
|
||||
lua51:p
|
||||
ninja:p
|
||||
pkgconf:p
|
||||
python3.11:p
|
||||
rst2pdf:p
|
||||
rubberband:p
|
||||
shaderc:p
|
||||
spirv-cross:p
|
||||
uchardet:p
|
||||
vapoursynth:p
|
||||
vulkan:p
|
||||
|
||||
- name: Build with meson
|
||||
id: build
|
||||
run: |
|
||||
./ci/build-msys2.sh meson
|
||||
|
||||
- name: Print meson log
|
||||
if: ${{ failure() && steps.build.outcome == 'failure' }}
|
||||
run: |
|
||||
cat ./build/meson-logs/meson-log.txt
|
||||
|
||||
- name: Run meson tests
|
||||
id: tests
|
||||
run: |
|
||||
source ./venv/bin/activate
|
||||
meson test -C build
|
||||
|
||||
- name: Print meson test log
|
||||
if: ${{ failure() && steps.tests.outcome == 'failure' }}
|
||||
run: |
|
||||
cat ./build/meson-logs/testlog.txt
|
||||
|
||||
- name: Build with waf
|
||||
id: build_waf
|
||||
run: |
|
||||
./ci/build-msys2.sh waf
|
||||
|
||||
- name: Print waf log
|
||||
if: ${{ failure() && steps.build_waf.outcome == 'failure' }}
|
||||
run: |
|
||||
cat ./build_waf/config.log
|
||||
@@ -1,22 +0,0 @@
|
||||
name: lint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- ci
|
||||
- 'release/**'
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
commit-msg:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 50
|
||||
|
||||
- name: Lint
|
||||
run: |
|
||||
./ci/lint-commit-msg.py
|
||||
@@ -36,5 +36,3 @@
|
||||
|
||||
/old_build
|
||||
/Makefile
|
||||
|
||||
/subprojects
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
language: c
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
- ./waf configure
|
||||
- ./waf build
|
||||
after_failure: cat ./build/config.log
|
||||
after_script: TOOLS/travis-rebuild-website
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
irc:
|
||||
channels:
|
||||
- "irc.freenode.org#mpv-devel"
|
||||
on_success: change
|
||||
on_failure: always
|
||||
|
||||
addons:
|
||||
coverity_scan:
|
||||
project:
|
||||
name: "mpv-player/mpv"
|
||||
description: "Build submitted via Travis CI"
|
||||
notification_email: mpv-team@googlegroups.com
|
||||
build_command_prepend: "./bootstrap.py && ./waf configure"
|
||||
build_command: "./waf build"
|
||||
branch_pattern: coverity_scan
|
||||
@@ -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).
|
||||
@@ -1,71 +0,0 @@
|
||||
# Differences Between Meson and Waf
|
||||
|
||||
mpv currently supports two different build systems: waf and meson. In general,
|
||||
option names between both build systems are mostly the same. In most cases,
|
||||
``--enable-foo`` in waf becomes ``-Dfoo=enabled`` in meson. Likewise,
|
||||
``--disable-foo`` becomes ``-Dfoo=disabled``. For the rest of this document,
|
||||
Waf options will be noted as ``--foo`` while meson options are noted as
|
||||
``foo``.
|
||||
|
||||
## Universal Options
|
||||
|
||||
Meson has several [universal options](https://mesonbuild.com/Builtin-options.html#universal-options)
|
||||
that you get for free. In some cases, these overlapped with custom waf options.
|
||||
|
||||
* ``--libmpv-static`` and ``--libmpv-shared`` were combined into one option:
|
||||
``libmpv``. Use ``default_library`` to control if you want to build static or
|
||||
shared libraries.
|
||||
* Waf had a boolean ``--optimize`` option. In meson, this is a universal option,
|
||||
``optimization``, which can take several different values. In mpv's meson
|
||||
build, the default is ``2``.
|
||||
* Instead of ``--debug-build``, meson simply calls it ``debug``. It is enabled
|
||||
by default.
|
||||
|
||||
## Changed Options
|
||||
|
||||
* The legacy lua names (``52``, ``52deb``, etc.) for ``--lua`` are not
|
||||
supported in the meson build. Instead, pass the generic pkg-config values
|
||||
such as ``lua52``, ``lua5.2``, etc.
|
||||
* ``--lgpl`` was changed to ``gpl``. If ``gpl`` is false, the build is LGPL2.1+.
|
||||
* ``--tests`` was removed since unit tests now explicitly require meson to run
|
||||
|
||||
### Boolean Options
|
||||
|
||||
The following options are all booleans that accept ``true`` or ``false``
|
||||
instead of ``enabled`` or ``disabled``.
|
||||
|
||||
* ``build-date``
|
||||
* ``cplayer``
|
||||
* ``gpl``
|
||||
* ``libmpv``
|
||||
* ``ta-leak-report``
|
||||
* ``tests``
|
||||
|
||||
## Removed Options
|
||||
|
||||
There are options removed with no equivalent in the meson build.
|
||||
|
||||
* ``--asm`` was removed since it doesn't do anything.
|
||||
* ``--android`` was removed since meson knows if the machine is android.
|
||||
* ``--clang-compilation-database`` was removed. Meson can do this on its own
|
||||
by invoking ninja (``ninja -t compdb``).
|
||||
* ``--tvos`` was removed since it doesn't do anything.
|
||||
* ``--static-build`` was removed. Use ``default_library``.
|
||||
* ``--swift-static`` was removed. The swift library always dynamically links.
|
||||
|
||||
## Renamed Options
|
||||
|
||||
These are some other options that were renamed.
|
||||
|
||||
* ``--gl-wayland`` was renamed to ``egl-wayland``.
|
||||
* ``--swift`` was renamed to ``swift-build``.
|
||||
|
||||
## Other
|
||||
|
||||
* The meson build supports passing the ``SOURCE_DATE_EPOCH`` environment variable
|
||||
during the compilation step for those who want reproducibility without having to
|
||||
disable the build date.
|
||||
* The ``Configuration`` line shown by ``mpv -v`` does not show everything passed on
|
||||
cli since meson does not have any easy way to access a user's argv. Instead, it
|
||||
simply shows whatever the value of ``prefix`` is regardless if it was specified
|
||||
or not.
|
||||
+1
-104
@@ -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,110 +32,7 @@ API changes
|
||||
|
||||
::
|
||||
|
||||
--- mpv 0.36.0 ---
|
||||
2.1 - add mpv_del_property()
|
||||
--- mpv 0.35.0 ---
|
||||
2.0 - remove headers/functions of the obsolete opengl_cb API
|
||||
- remove mpv_opengl_init_params.extra_exts field
|
||||
- remove deprecated mpv_detach_destroy. Use mpv_destroy instead.
|
||||
- remove obsolete mpv_suspend and mpv_resume
|
||||
- remove deprecated SCRIPT_INPUT_DISPATCH, PAUSE and UNPAUSE, TRACKS_CHANGED
|
||||
TRACK_SWITCHED, METADATA_UPDATE, CHAPTER_CHANGE events
|
||||
--- 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.
|
||||
+38
-43
@@ -4,43 +4,28 @@ Compiling for Windows
|
||||
Compiling for Windows is supported with MinGW-w64. This can be used to produce
|
||||
both 32-bit and 64-bit executables, and it works for building on Windows and
|
||||
cross-compiling from Linux and Cygwin. MinGW-w64 is available from:
|
||||
https://www.mingw-w64.org/
|
||||
http://mingw-w64.sourceforge.net.
|
||||
|
||||
While building a complete MinGW-w64 toolchain yourself is possible, there are a
|
||||
few build environments and scripts to help ease the process, such as MSYS2 and
|
||||
MXE. Note that MinGW environments included in Linux distributions are often
|
||||
broken, outdated and useless, and usually don't use MinGW-w64.
|
||||
|
||||
**Warning**: the original MinGW (https://osdn.net/projects/mingw/) is unsupported.
|
||||
**Warning**: the original MinGW (http://www.mingw.org) is unsupported.
|
||||
|
||||
Cross-compilation
|
||||
=================
|
||||
|
||||
When cross-compiling, it is recommended to use a meson crossfile to setup
|
||||
the cross compiling environment. A minimal example is included below:
|
||||
When cross-compiling, you have to run mpv's configure with these arguments:
|
||||
|
||||
```ini
|
||||
[binaries]
|
||||
c = 'x86_64-w64-mingw32-gcc'
|
||||
cpp = 'x86_64-w64-mingw32-g++'
|
||||
ar = 'x86_64-w64-mingw32-ar'
|
||||
strip = 'x86_64-w64-mingw32-strip'
|
||||
exe_wrapper = 'wine64'
|
||||
|
||||
[host_machine]
|
||||
system = 'windows'
|
||||
cpu_family = 'x86_64'
|
||||
cpu = 'x86_64'
|
||||
endian = 'little'
|
||||
```bash
|
||||
DEST_OS=win32 TARGET=i686-w64-mingw32 ./waf configure
|
||||
```
|
||||
|
||||
See [meson's documentation](https://mesonbuild.com/Cross-compilation.html) for
|
||||
more information.
|
||||
|
||||
[MXE](https://mxe.cc) makes it very easy to bootstrap a complete MingGW-w64
|
||||
[MXE](http://mxe.cc) makes it very easy to bootstrap a complete MingGW-w64
|
||||
environment from a Linux machine. See a working example below.
|
||||
|
||||
Alternatively, you can try [mpv-winbuild-cmake](https://github.com/shinchiro/mpv-winbuild-cmake),
|
||||
Alternatively, you can try [mingw-w64-cmake](https://github.com/lachs0r/mingw-w64-cmake),
|
||||
which bootstraps a MinGW-w64 environment and builds mpv and dependencies.
|
||||
|
||||
Example with MXE
|
||||
@@ -53,7 +38,7 @@ Example with MXE
|
||||
#
|
||||
# Refer to
|
||||
#
|
||||
# https://mxe.cc/#requirements
|
||||
# http://mxe.cc/#requirements
|
||||
#
|
||||
# Scroll down for disto/OS-specific instructions to install them.
|
||||
|
||||
@@ -83,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
|
||||
@@ -94,8 +79,11 @@ export PATH=/opt/mxe/usr/bin/:$PATH
|
||||
cd ..
|
||||
git clone https://github.com/mpv-player/mpv.git
|
||||
cd mpv
|
||||
meson setup build --crossfile crossfile
|
||||
meson compile -C build
|
||||
python ./bootstrap.py
|
||||
DEST_OS=win32 TARGET=i686-w64-mingw32.static ./waf configure
|
||||
# Or, if 64 bit version,
|
||||
# DEST_OS=win32 TARGET=x86_64-w64-mingw32.static ./waf configure
|
||||
./waf build
|
||||
```
|
||||
|
||||
Native compilation with MSYS2
|
||||
@@ -111,7 +99,7 @@ To build 64-bit mpv on Windows:
|
||||
Installing MSYS2
|
||||
----------------
|
||||
|
||||
1. Download an installer from https://www.msys2.org/
|
||||
1. Download an installer from https://msys2.github.io/
|
||||
|
||||
Both the i686 and the x86_64 version of MSYS2 can build 32-bit and 64-bit
|
||||
mpv binaries when running on a 64-bit version of Windows, but the x86_64
|
||||
@@ -144,49 +132,54 @@ Installing mpv dependencies
|
||||
|
||||
```bash
|
||||
# Install MSYS2 build dependencies and a MinGW-w64 compiler
|
||||
pacman -S git $MINGW_PACKAGE_PREFIX-{python,pkgconf,gcc,meson}
|
||||
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,luajit}
|
||||
pacman -S $MINGW_PACKAGE_PREFIX-{ffmpeg,libjpeg-turbo,lua51,angleproject-git}
|
||||
```
|
||||
|
||||
Building mpv
|
||||
------------
|
||||
|
||||
Clone the latest mpv from git and install waf. **Note:** ``/usr/bin/python3``
|
||||
is invoked directly here, since an MSYS2 version of Python is required.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/mpv-player/mpv.git && cd mpv
|
||||
/usr/bin/python3 bootstrap.py
|
||||
```
|
||||
|
||||
Finally, compile and install mpv. Binaries will be installed to
|
||||
``/mingw64/bin`` or ``/mingw32/bin``.
|
||||
|
||||
```bash
|
||||
meson setup build --prefix=$MSYSTEM_PREFIX
|
||||
meson compile -C build
|
||||
/usr/bin/python3 waf configure CC=gcc.exe --check-c-compiler=gcc --prefix=$MSYSTEM_PREFIX
|
||||
/usr/bin/python3 waf install
|
||||
```
|
||||
|
||||
Or, compile and install both libmpv and mpv:
|
||||
|
||||
```bash
|
||||
meson setup build -Dlibmpv=true --prefix=$MSYSTEM_PREFIX
|
||||
meson compile -C build
|
||||
meson install -C build
|
||||
/usr/bin/python3 waf configure CC=gcc.exe --check-c-compiler=gcc --enable-libmpv-shared --prefix=$MSYSTEM_PREFIX
|
||||
/usr/bin/python3 waf install
|
||||
```
|
||||
|
||||
Linking libmpv with MSVC programs
|
||||
---------------------------------
|
||||
|
||||
mpv/libmpv cannot be built with Visual Studio (Microsoft is too incompetent to
|
||||
support C99/C11 properly and/or hates open source and Linux too much to
|
||||
seriously do it). But you can build C++ programs in Visual Studio and link them
|
||||
with a libmpv built with MinGW.
|
||||
|
||||
To do this, you need a Visual Studio which supports ``stdint.h`` (recent ones do),
|
||||
You can build C++ programs in Visual Studio and link them with libmpv. To do
|
||||
this, you need a Visual Studio which supports ``stdint.h`` (recent ones do),
|
||||
and you need to create a import library for the mpv DLL:
|
||||
|
||||
```bash
|
||||
lib /name:mpv-1.dll /out:mpv.lib /MACHINE:X64
|
||||
lib /def:mpv.def /name:mpv-1.dll /out:mpv.lib /MACHINE:X64
|
||||
```
|
||||
|
||||
The string in the ``/name:`` parameter must match the filename of the DLL (this
|
||||
is simply the filename the MSVC linker will use).
|
||||
is simply the filename the MSVC linker will use). The ``mpv.def`` can be
|
||||
retrieved from the mpv build directory, or can be produced by MingGW's
|
||||
gendef.exe helper from the mpv DLL.
|
||||
|
||||
Static linking is not possible.
|
||||
|
||||
@@ -211,4 +204,6 @@ Use of the ANGLE OpenGL backend requires a copy of the D3D compiler DLL that
|
||||
matches the version of the D3D SDK that ANGLE was built with
|
||||
(``d3dcompiler_43.dll`` in case of MinGW-built ANGLE) in the path or in the
|
||||
same folder as mpv. It must be of the same architecture (x86_64 / i686) as the
|
||||
mpv you compiled.
|
||||
mpv you compiled. You can find copies here:
|
||||
|
||||
https://mpv.srsfckn.biz/d3dcompiler.7z
|
||||
+30
-118
@@ -5,60 +5,35 @@ General
|
||||
-------
|
||||
|
||||
The main contact for mpv development is IRC, specifically #mpv
|
||||
and #mpv-devel on Libera.chat. 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,16 +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``.
|
||||
|
||||
Sample prefixes: ``vo_gpu: ...``, ``command: ...``, ``DOCS/input: ...``,
|
||||
``TOOLS/osxbundle: ...``, ``osc.lua: ...``, etc. You can always check the git
|
||||
log for commits which modify specific files to see which prefixes are used.
|
||||
|
||||
- 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
|
||||
@@ -86,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
|
||||
---------------
|
||||
@@ -145,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) {
|
||||
@@ -178,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:
|
||||
@@ -208,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.
|
||||
@@ -240,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
|
||||
-------------------------
|
||||
|
||||
@@ -266,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
-240
@@ -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,184 +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.
|
||||
|
||||
Global metadata
|
||||
===============
|
||||
|
||||
The special ``global_tags`` header can set metadata fields (aka tags) of the EDL
|
||||
file. This metadata is supposed to be informational, much like for example ID3
|
||||
tags in audio files. Due to lack of separation of different kinds of metadata it
|
||||
is unspecified what names are allowed, how they are interpreted, and whether
|
||||
some of them affect playback functionally. (Much of this is unfortunately
|
||||
inherited from FFmpeg. Another consequence of this is that FFmpeg "normalized"
|
||||
tags are recognized, or stuff like replaygain tags.)
|
||||
|
||||
Example::
|
||||
|
||||
!global_tags,title=bla,something_arbitrary=even_more_arbitrary
|
||||
|
||||
Any parameter names are allowed. Repeated use of this adds to the tag list. If
|
||||
``!new_stream`` is used, the location doesn't matter.
|
||||
|
||||
May possibly be ignored in some cases, such as delayed media opening.
|
||||
|
||||
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
|
||||
================
|
||||
|
||||
@@ -328,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::
|
||||
|
||||
@@ -338,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
|
||||
==================
|
||||
|
||||
|
||||
+45
-48
@@ -3,26 +3,27 @@ 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]
|
||||
-ovc outvideocodec [-ovcopts outvideocodecoptions] \
|
||||
-oac outaudiocodec [-oacopts outaudiocodecoptions]
|
||||
|
||||
Help for these options is provided if giving help as parameter, as in::
|
||||
|
||||
mpv --ovc=help
|
||||
mpv -ovc help
|
||||
|
||||
The suboptions of these generally are identical to ffmpeg's (as option parsing
|
||||
is simply delegated to ffmpeg). The option --ocopyts enables copying timestamps
|
||||
is simply delegated to ffmpeg). The option -ocopyts enables copying timestamps
|
||||
from the source as-is, instead of fixing them to match audio playback time
|
||||
(note: this doesn't work with all output container formats); --orawts even turns
|
||||
(note: this doesn't work with all output container formats); -orawts even turns
|
||||
off discontinuity fixing.
|
||||
|
||||
Note that if neither --ofps nor --oautofps is specified, VFR encoding is assumed
|
||||
and the time base is 24000fps. --oautofps sets --ofps to a guessed fps number
|
||||
Note that if neither -ofps nor -oautofps is specified, VFR encoding is assumed
|
||||
and the time base is 24000fps. -oautofps sets -ofps to a guessed fps number
|
||||
from the input video. 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.
|
||||
-ofps or -oautofps to force CFR encoding in these cases.
|
||||
|
||||
Of course, the options can be stored in a profile, like this .config/mpv/mpv.conf
|
||||
section::
|
||||
@@ -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
|
||||
@@ -48,7 +45,7 @@ to encoding. This is not the case anymore.)
|
||||
|
||||
One can then encode using this profile using the command::
|
||||
|
||||
mpv infile --o=outfile.mp4 --profile=myencprofile
|
||||
mpv infile -o outfile.mp4 -profile myencprofile
|
||||
|
||||
Some example profiles are provided in a file
|
||||
etc/encoding-profiles.conf; as for this, see below.
|
||||
@@ -62,33 +59,33 @@ for.
|
||||
|
||||
Typical MPEG-4 Part 2 ("ASP", "DivX") encoding, AVI container::
|
||||
|
||||
mpv infile --o=outfile.avi \
|
||||
--vf=fps=25 \
|
||||
--ovc=mpeg4 --ovcopts=qscale=4 \
|
||||
--oac=libmp3lame --oacopts=b=128k
|
||||
mpv infile -o outfile.avi \
|
||||
-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::
|
||||
|
||||
mpv infile --o=outfile.mkv \
|
||||
--ovc=libx264 --ovcopts=preset=medium,crf=23,profile=baseline \
|
||||
--oac=libopus --oacopts=qscale=3
|
||||
mpv infile -o outfile.mkv \
|
||||
-ovc libx264 -ovcopts preset=medium,crf=23,profile=baseline \
|
||||
-oac libvorbis -oacopts qscale=3
|
||||
|
||||
Typical MPEG-4 Part 10 ("AVC", "H.264") encoding, MPEG-4 (MP4) container::
|
||||
|
||||
mpv infile --o=outfile.mp4 \
|
||||
--ovc=libx264 --ovcopts=preset=medium,crf=23,profile=baseline \
|
||||
--oac=aac --oacopts=b=128k
|
||||
mpv infile -o outfile.mp4 \
|
||||
-ovc libx264 -ovcopts preset=medium,crf=23,profile=baseline \
|
||||
-oac aac -oacopts ab=128k
|
||||
|
||||
Typical VP8 encoding, WebM (restricted Matroska) container::
|
||||
|
||||
mpv infile -o outfile.mkv \
|
||||
--of=webm \
|
||||
--ovc=libvpx --ovcopts=qmin=6,b=1000000k \
|
||||
--oac=libopus --oacopts=qscale=3
|
||||
-of webm \
|
||||
-ovc libvpx -ovcopts qmin=6,b=1000000k \
|
||||
-oac libvorbis -oacopts qscale=3
|
||||
|
||||
|
||||
Device targets
|
||||
@@ -111,18 +108,18 @@ section.
|
||||
Refer to the top of that file for more comments - in a nutshell, the following
|
||||
options are added by it::
|
||||
|
||||
--profile=enc-to-dvdpal # DVD-Video PAL, use dvdauthor -v pal+4:3 -a ac3+en
|
||||
--profile=enc-to-dvdntsc # DVD-Video NTSC, use dvdauthor -v ntsc+4:3 -a ac3+en
|
||||
--profile=enc-to-bb-9000 # MP4 for Blackberry Bold 9000
|
||||
--profile=enc-to-nok-6300 # 3GP for Nokia 6300
|
||||
--profile=enc-to-psp # MP4 for PlayStation Portable
|
||||
--profile=enc-to-iphone # MP4 for iPhone
|
||||
--profile=enc-to-iphone-4 # MP4 for iPhone 4 (double res)
|
||||
--profile=enc-to-iphone-5 # MP4 for iPhone 5 (even larger res)
|
||||
-profile enc-to-dvdpal DVD-Video PAL, use dvdauthor -v pal+4:3 -a ac3+en
|
||||
-profile enc-to-dvdntsc DVD-Video NTSC, use dvdauthor -v ntsc+4:3 -a ac3+en
|
||||
-profile enc-to-bb-9000 MP4 for Blackberry Bold 9000
|
||||
-profile enc-to-nok-6300 3GP for Nokia 6300
|
||||
-profile enc-to-psp MP4 for PlayStation Portable
|
||||
-profile enc-to-iphone MP4 for iPhone
|
||||
-profile enc-to-iphone-4 MP4 for iPhone 4 (double res)
|
||||
-profile enc-to-iphone-5 MP4 for iPhone 5 (even larger res)
|
||||
|
||||
You can encode using these with a command line like::
|
||||
|
||||
mpv infile --o=outfile.mp4 --profile=enc-to-bb-9000
|
||||
mpv infile -o outfile.mp4 -profile enc-to-bb-9000
|
||||
|
||||
Of course, you are free to override options set by these profiles by specifying
|
||||
them after the -profile option.
|
||||
@@ -132,20 +129,20 @@ What works
|
||||
==========
|
||||
|
||||
* Encoding at variable frame rate (default)
|
||||
* Encoding at constant frame rate using --vf=fps=RATE
|
||||
* 2-pass encoding (specify flags=+pass1 in the first pass's --ovcopts, specify
|
||||
* 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
|
||||
configure mpv for the subtitles as usual)
|
||||
* Hardcoding any other mpv OSD (e.g. time codes, using --osdlevel=3 and
|
||||
--vf=expand=::::1)
|
||||
* Hardcoding any other mpv OSD (e.g. time codes, using -osdlevel 3 and -vf
|
||||
expand=::::1)
|
||||
* Encoding directly from a DVD, network stream, webcam, or any other source
|
||||
mpv supports
|
||||
* Using x264 presets/tunings/profiles (by using profile=, tune=, preset= in the
|
||||
--ovcopts)
|
||||
-ovcopts)
|
||||
* Deinterlacing/Inverse Telecine with any of mpv's filters for that
|
||||
* Audio file converting: mpv --o=outfile.m4a infile.flac --no-video
|
||||
--oac=aac --oacopts=b=320k
|
||||
* Audio file converting: mpv -o outfile.mp3 infile.flac -no-video -oac
|
||||
libmp3lame -oacopts ab=320k
|
||||
|
||||
What does not work yet
|
||||
======================
|
||||
|
||||
+3
-414
@@ -7,428 +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.36.0 ---
|
||||
- add `--target-contrast`
|
||||
- Target luminance value is now also applied when ICC profile is used.
|
||||
`--icc-use-luma` has been added to use ICC profile luminance value.
|
||||
If target luminance and ICC luminance is not used, old behavior apply,
|
||||
defaulting to 203 nits. (Only applies for `--vo=gpu-next`)
|
||||
- `playlist/N/title` gets set upon opening the file if it wasn't already set
|
||||
and a title is available.
|
||||
- add the `--vo=kitty` video output driver, as well as the options
|
||||
`--vo-kitty-cols`, `--vo-kitty-rows`, `--vo-kitty-width`,
|
||||
`--vo-kitty-height`, `--vo-kitty-left`, `--vo-kitty-top`,
|
||||
`--vo-kitty-config-clear`, `--vo-kitty-alt-screen` and
|
||||
`--vo-kitty-use-shm`
|
||||
- add `--force-render`
|
||||
- add `--vo-sixel-config-clear`, `--vo-sixel-alt-screen` and
|
||||
`--vo-sixel-buffered`
|
||||
- add `--wayland-content-type`
|
||||
- deprecate `--vo-sixel-exit-clear` and alias it to
|
||||
`--vo-sixel-alt-screen`
|
||||
- deprecate `--drm-atomic`
|
||||
- add `--demuxer-hysteresis-secs`
|
||||
- add `--video-sync=display-tempo`
|
||||
- the `start` option is no longer unconditionally written by
|
||||
watch-later. It is still written by default but you may
|
||||
need to explicitly add `start` depending on how you have
|
||||
`--watch-later-options` configured.
|
||||
- add `--vd-lavc-dr=auto` and make it the default
|
||||
- add support for the fractional scale protocol in wayland
|
||||
- in wayland, hidpi window scaling now scales the window by the compositor's
|
||||
dpi scale factor by default (can be disabled with --no-hidpi-window-scale
|
||||
if fractional scaling support exists).
|
||||
- change --screenshot-tag-colorspace default value from `no` to `yes`
|
||||
- undeprecate vf_sub
|
||||
- add `--tone-mapping=st2094-40` and `--tone-mapping=st2094-10`
|
||||
- change `--screenshot-jxl-effort` default from `3` to `4`.
|
||||
- add `--tone-mapping-visualize`
|
||||
- change type of `--brightness`, `--saturation`, `--contrast`, `--hue` and
|
||||
`--gamma` to float.
|
||||
- add `platform` property
|
||||
- add `--auto-window-resize`
|
||||
- `--save-position-on-quit` and its associated commands now store state files in
|
||||
the XDG_STATE_HOME directory by default. This only has an effect on linux/bsd
|
||||
systems.
|
||||
- mpv now implictly saves cache files in XDG_CACHE_HOME by default. This only has
|
||||
an effect if the user enables options that would lead to cache being stored and
|
||||
only makes a difference on linux/bsd systems.
|
||||
- `--cache-on-disk` no longer requires explictly setting the `--cache-dir` option
|
||||
- add `--icc-cache` and `--gpu-shader-cache` options to control whether or not to
|
||||
save cache files for these features; explictly setting `--icc-cache-dir` and
|
||||
`--gpu-shader-cache` is no longer required
|
||||
- remove the `--tone-mapping-crosstalk` option
|
||||
- add `--gamut-mapping-mode=perceptual|relative|saturation|absolute|linear`
|
||||
- add `--corner-rounding` option
|
||||
- change `--subs-with-matching-audio` default from `yes` to `no`
|
||||
- change `--slang` default from blank to `auto`
|
||||
- add `--input-cursor-passthrough` option to allow pointer events to completely
|
||||
passthrough the mpv window
|
||||
- icc and gpu-shader cache are now saved by default (use --no-icc-shader-cache and
|
||||
--no-gpu-shader-cache to disable)
|
||||
- add `--directory-mode=recursive|lazy|ignore`
|
||||
- `--hwdec=yes` is now mapped to `auto-safe` rather than `auto` (also used
|
||||
by ctrl+h keybind)
|
||||
- add `--hdr-contrast-recovery` and `--hdr-contrast-smoothness`
|
||||
- include `--hdr-contrast-recovery` in the `gpu-hq` profile
|
||||
--- mpv 0.35.0 ---
|
||||
- add the `--vo=gpu-next` video output driver, as well as the options
|
||||
`--allow-delayed-peak-detect`, `--builtin-scalers`,
|
||||
`--interpolation-preserve` `--lut`, `--lut-type`, `--image-lut`,
|
||||
`--image-lut-type` and `--target-lut` along with it.
|
||||
- add `--target-colorspace-hint`
|
||||
- add `--tone-mapping-crosstalk`
|
||||
- add `--tone-mapping` options `auto`, `spline` and `bt.2446a`
|
||||
- add `--inverse-tone-mapping`
|
||||
- add `--gamut-mapping-mode`, replacing `--gamut-clipping` and `--gamut-warning`
|
||||
- add `--tone-mapping-mode`, replacing `--tone-mapping-desaturate` and
|
||||
`--tone-mapping-desaturate-exponent`.
|
||||
- add `dolbyvision` sub-parameter to `format` video filter
|
||||
- `--sub-visibility` no longer has any effect on secondary subtitles
|
||||
- add `film-grain` sub-parameter to `format` video filter
|
||||
- add experimental `--vo=dmabuf-wayland` video output driver
|
||||
- add `--x11-present` for controlling whether to use xorg's present extension
|
||||
- add `engine` option to the `rubberband` audio filter to support the new
|
||||
engine introduced in rubberband 3.0.0. Defaults to `finer` (new engine).
|
||||
- add `--wayland-configure-bounds` option
|
||||
- deprecate `--gamma-factor`
|
||||
- deprecate `--gamma-auto`
|
||||
- remove `--vulkan-disable-events`
|
||||
- add `--glsl-shader-opts`
|
||||
--- mpv 0.34.0 ---
|
||||
- deprecate selecting by card number with `--drm-connector`, add
|
||||
`--drm-device` which can be used instead
|
||||
- add `--screen-name` and `--fs-screen-name` flags to allow selecting the
|
||||
screen by its name instead of the index
|
||||
- add `--macos-geometry-calculation` to change the rectangle used for screen
|
||||
position and size calculation. the old behavior used the whole screen,
|
||||
which didn't take the menu bar and Dock into account. The new default
|
||||
behaviour includes both. To revert to the old behavior set this to
|
||||
`whole`.
|
||||
- add an additional optional `albumart` argument to the `video-add` command,
|
||||
which tells mpv to load the given video as album art.
|
||||
- undeprecate `--cache-secs` option
|
||||
- remove `--icc-contrast` and introduce `--icc-force-contrast`. The latter
|
||||
defaults to the equivalent of the old `--icc-contrast=inf`, and can
|
||||
instead be used to specifically set the contrast to any value.
|
||||
- add a `--watch-later-options` option to allow configuring which
|
||||
options quit-watch-later saves
|
||||
- make `current-window-scale` writeable and use it in the default input.conf
|
||||
- add `--input-builtin-bindings` flag to control loading of built-in key
|
||||
bindings during start-up (default: yes).
|
||||
- add ``track-list/N/image`` sub-property
|
||||
- remove `--opengl-restrict` option
|
||||
- js custom-init: use filename ~~/init.js instead of ~~/.init.js (dot)
|
||||
--- 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
|
||||
@@ -469,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
|
||||
|
||||
+36
-52
@@ -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>``
|
||||
@@ -162,28 +194,6 @@ Available filters are:
|
||||
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
|
||||
of ``scaletempo``, and will be used to adjust audio pitch when playing
|
||||
@@ -198,7 +208,7 @@ Available filters are:
|
||||
for each option. The options are not documented here, because they are
|
||||
merely passed to librubberband. Look at the librubberband documentation
|
||||
to learn what each option does:
|
||||
https://breakfastquay.com/rubberband/code-doc/classRubberBand_1_1RubberBandStretcher.html
|
||||
http://breakfastquay.com/rubberband/code-doc/classRubberBand_1_1RubberBandStretcher.html
|
||||
(The mapping of the mpv rubberband filter sub-option names and values to
|
||||
those of librubberband follows a simple pattern: ``"Option" + Name + Value``.)
|
||||
|
||||
@@ -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.
|
||||
+35
-49
@@ -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. On BSD systems, ``--ao=oss`` is preferred.
|
||||
where PulseAudio is used. On BSD systems, ``--ao=oss`` or ``--ao=sndio``
|
||||
may work (the latter being experimental).
|
||||
|
||||
Available audio output drivers are:
|
||||
|
||||
@@ -26,12 +27,11 @@ 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.
|
||||
|
||||
You can also try `using the upmix plugin
|
||||
<https://github.com/mpv-player/mpv/wiki/ALSA-Surround-Sound-and-Upmixing>`_.
|
||||
You can also try `using the upmix plugin <http://git.io/vfuAy>`_.
|
||||
This setup enables multichannel audio on the ``default`` device
|
||||
with automatic upmixing with shared access, so playing stereo
|
||||
and multichannel audio at the same time will work as expected.
|
||||
@@ -39,6 +39,15 @@ Available audio output drivers are:
|
||||
``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.
|
||||
|
||||
@@ -69,8 +78,8 @@ Available audio output drivers are:
|
||||
mode is probably not very useful, other than for debugging or when used
|
||||
with fixed setups.
|
||||
|
||||
``coreaudio`` (macOS only)
|
||||
Native macOS audio output driver using AudioUnits and the CoreAudio
|
||||
``coreaudio`` (Mac OS X only)
|
||||
Native Mac OS X audio output driver using AudioUnits and the CoreAudio
|
||||
sound server.
|
||||
|
||||
Automatically redirects to ``coreaudio_exclusive`` when playing compressed
|
||||
@@ -96,24 +105,15 @@ Available audio output drivers are:
|
||||
extreme care.
|
||||
|
||||
|
||||
``coreaudio_exclusive`` (macOS only)
|
||||
Native macOS audio output driver using direct device access and
|
||||
``coreaudio_exclusive`` (Mac OS X only)
|
||||
Native Mac OS X audio output driver using direct device access and
|
||||
exclusive mode (bypasses the sound server).
|
||||
|
||||
``openal``
|
||||
OpenAL audio output driver.
|
||||
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. Default: yes.
|
||||
.. note:: This driver is not very useful. Playing multi-channel audio with
|
||||
it is slow.
|
||||
|
||||
``pulse``
|
||||
PulseAudio audio output driver
|
||||
@@ -128,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. "native" lets the sound server determine buffers.
|
||||
changes. Default: 250.
|
||||
|
||||
``--pulse-latency-hacks=<yes|no>``
|
||||
Enable hacks to workaround PulseAudio timing bugs (default: no). If
|
||||
@@ -141,31 +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.
|
||||
|
||||
``pipewire``
|
||||
PipeWire audio output driver
|
||||
|
||||
The following global options are supported by this audio output:
|
||||
|
||||
``--pipewire-buffer=<1-2000|native>``
|
||||
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. "native" lets the sound server determine buffers.
|
||||
|
||||
``--pipewire-remote=<remote>``
|
||||
Specify the PipeWire remote daemon name to connect to via local UNIX
|
||||
sockets.
|
||||
An empty <remote> string uses the default remote named ``pipewire-0``.
|
||||
|
||||
``--pipewire-volume-mode=<channel|global>``
|
||||
Specify if the ``ao-volume`` property should apply to the channel
|
||||
volumes or the global volume.
|
||||
By default the channel volumes are used.
|
||||
|
||||
``sdl``
|
||||
SDL 1.2+ audio output driver. Should work on any platform supported by SDL
|
||||
1.2, but may require the ``SDL_AUDIODRIVER`` environment variable to be set
|
||||
@@ -183,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.
|
||||
@@ -219,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
|
||||
|
||||
@@ -239,9 +215,19 @@ 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.)
|
||||
|
||||
|
||||
@@ -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,155 +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, Ctrl+J and Ctrl+M
|
||||
Run the typed command.
|
||||
|
||||
Shift+ENTER
|
||||
Type a literal newline character.
|
||||
|
||||
LEFT and Ctrl+B
|
||||
Move the cursor to the previous character.
|
||||
|
||||
RIGHT and Ctrl+F
|
||||
Move the cursor to the next character.
|
||||
|
||||
Ctrl+LEFT and Alt+B
|
||||
Move the cursor to the beginning of the current word, or if between words,
|
||||
to the beginning of the previous word.
|
||||
|
||||
Ctrl+RIGHT and Alt+F
|
||||
Move the cursor to the end of the current word, or if between words, to the
|
||||
end of the next word.
|
||||
|
||||
HOME and Ctrl+A
|
||||
Move the cursor to the start of the current line.
|
||||
|
||||
END and Ctrl+E
|
||||
Move the cursor to the end of the current line.
|
||||
|
||||
BACKSPACE and Ctrl+H
|
||||
Delete the previous character.
|
||||
|
||||
Ctrl+D
|
||||
Hide the console if the current line is empty, otherwise delete the next
|
||||
character.
|
||||
|
||||
Ctrl+BACKSPACE and Ctrl+W
|
||||
Delete text from the cursor to the beginning of the current word, or if
|
||||
between words, to the beginning of the previous word.
|
||||
|
||||
Ctrl+DEL and Alt+D
|
||||
Delete text from the cursor to the end of the current word, or if between
|
||||
words, to the end of the next word.
|
||||
|
||||
Ctrl+U
|
||||
Delete text from the cursor to the beginning of the current line.
|
||||
|
||||
Ctrl+K
|
||||
Delete text from the cursor to the end of the current line.
|
||||
|
||||
Ctrl+C
|
||||
Clear the current line.
|
||||
|
||||
UP and Ctrl+P
|
||||
Move back in the command history.
|
||||
|
||||
DOWN and Ctrl+N
|
||||
Move forward in the command history.
|
||||
|
||||
PGUP
|
||||
Go to the first command in the history.
|
||||
|
||||
PGDN
|
||||
Stop navigating the command history.
|
||||
|
||||
INSERT
|
||||
Toggle insert mode.
|
||||
|
||||
Ctrl+V
|
||||
Paste text (uses the clipboard on X11 and Wayland).
|
||||
|
||||
Shift+INSERT
|
||||
Paste text (uses the primary selection on X11 and Wayland).
|
||||
|
||||
TAB and Ctrl+I
|
||||
Complete the command or property name at the cursor.
|
||||
|
||||
Ctrl+L
|
||||
Clear all log messages from the console.
|
||||
|
||||
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".
|
||||
|
||||
``border_size``
|
||||
Default: 1
|
||||
|
||||
Set the font border size used for the REPL and the console.
|
||||
|
||||
``history_dedup``
|
||||
Default: true
|
||||
|
||||
Remove duplicate entries in history as to only keep the latest one.
|
||||
+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.
|
||||
+428
-1686
File diff suppressed because it is too large.
Load diff
+11
-130
@@ -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:
|
||||
|
||||
@@ -116,11 +107,8 @@ can also be present. See `List of events`_ for a list of all supported events.
|
||||
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 interpret 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.
|
||||
verbatim into the response. mpv does not intrepret the ``request_id`` in any
|
||||
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 not 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.
|
||||
+35
-92
@@ -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``
|
||||
@@ -58,7 +58,7 @@ Language features - ECMAScript 5
|
||||
The scripting backend which mpv currently uses is MuJS - a compatible minimal
|
||||
ES5 interpreter. As such, ``String.substring`` is implemented for instance,
|
||||
while the common but non-standard ``String.substr`` is not. Please consult the
|
||||
MuJS pages on language features and platform support - https://mujs.com .
|
||||
MuJS pages on language features and platform support - http://mujs.com .
|
||||
|
||||
Unsupported Lua APIs and their JS alternatives
|
||||
----------------------------------------------
|
||||
@@ -67,22 +67,34 @@ 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)``
|
||||
|
||||
``utils.to_string(v)`` see ``dump`` below.
|
||||
|
||||
``mp.suspend()`` JS: none (deprecated).
|
||||
|
||||
``mp.resume()`` JS: none (deprecated).
|
||||
|
||||
``mp.resume_all()`` JS: none (deprecated).
|
||||
|
||||
``mp.get_next_timeout()`` see event loop below.
|
||||
|
||||
``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)
|
||||
|
||||
@@ -90,13 +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, ``error`` is empty string on success.
|
||||
|
||||
``mp.abort_async_command(id)``
|
||||
|
||||
``mp.del_property(name)`` (LE)
|
||||
|
||||
``mp.get_property(name [,def])`` (LE)
|
||||
|
||||
``mp.get_property_osd(name [,def])`` (LE)
|
||||
@@ -135,26 +140,16 @@ success, ``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(...)``
|
||||
@@ -175,8 +170,7 @@ success, ``error`` is empty string on success.
|
||||
|
||||
``mp.utils.readdir(path [, filter])`` (LE)
|
||||
|
||||
``mp.utils.file_info(path)`` (LE) Note: like lua - this does NOT expand
|
||||
meta-paths like ``~~/foo`` (other JS file functions do expand meta paths).
|
||||
``mp.utils.file_info(path)`` (LE)
|
||||
|
||||
``mp.utils.split_path(path)``
|
||||
|
||||
@@ -186,14 +180,7 @@ meta-paths like ``~~/foo`` (other JS file functions do expand meta paths).
|
||||
|
||||
``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
|
||||
--------------------
|
||||
@@ -217,9 +204,8 @@ Additional utilities
|
||||
``undefined`` if the variable is not defined.
|
||||
|
||||
``mp.utils.get_user_path(path)``
|
||||
Trivial wrapper of the ``expand-path`` mpv command, returns a string.
|
||||
``read_file``, ``write_file``, ``append_file`` and ``require`` already
|
||||
expand the path internally and accept mpv meta-paths like ``~~desktop/foo``.
|
||||
Expands (mpv) meta paths like ``~/x``, ``~~/y``, ``~~desktop/z`` etc.
|
||||
``read_file``, ``write_file`` and ``require`` already use this internaly.
|
||||
|
||||
``mp.utils.read_file(fname [,max])``
|
||||
Returns the content of file ``fname`` as string. If ``max`` is provided and
|
||||
@@ -230,12 +216,7 @@ Additional utilities
|
||||
prefixed with ``file://`` as simple protection against accidental arguments
|
||||
switch, e.g. ``mp.utils.write_file("file://~/abc.txt", "hello world")``.
|
||||
|
||||
``mp.utils.append_file(fname, str)``
|
||||
Same as ``mp.utils.write_file`` if the file ``fname`` does not exist. If it
|
||||
does exist then append instead of overwrite.
|
||||
|
||||
Note: ``read_file``, ``write_file`` and ``append_file`` throw on errors, allow
|
||||
text content only.
|
||||
Note: ``read_file`` and ``write_file`` throw on errors, allow text content only.
|
||||
|
||||
``mp.get_time_ms()``
|
||||
Same as ``mp.get_time()`` but in ms instead of seconds.
|
||||
@@ -252,9 +233,6 @@ 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)
|
||||
---------------
|
||||
|
||||
@@ -287,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
|
||||
@@ -307,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``
|
||||
@@ -328,21 +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``
|
||||
(do NOT do ``mp.module_paths = ["/foo"];`` because this will remove existing
|
||||
paths - like ``<script-dir>/modules`` for scripts which load from a directory).
|
||||
|
||||
The custom-init file is ignored if mpv is invoked with ``--no-config``.
|
||||
|
||||
Before mpv 0.34, the file name was ``.init.js`` (with dot) at the same dir.
|
||||
|
||||
The event loop
|
||||
--------------
|
||||
|
||||
@@ -364,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);
|
||||
}
|
||||
@@ -387,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``.
|
||||
+6
-6
@@ -10,9 +10,9 @@ Since libmpv merely allows access to underlying mechanisms that can control
|
||||
mpv, further documentation is spread over a few places:
|
||||
|
||||
- https://github.com/mpv-player/mpv/blob/master/libmpv/client.h
|
||||
- https://mpv.io/manual/master/#options
|
||||
- https://mpv.io/manual/master/#list-of-input-commands
|
||||
- https://mpv.io/manual/master/#properties
|
||||
- http://mpv.io/manual/master/#options
|
||||
- http://mpv.io/manual/master/#list-of-input-commands
|
||||
- http://mpv.io/manual/master/#properties
|
||||
- https://github.com/mpv-player/mpv-examples/tree/master/libmpv
|
||||
|
||||
C PLUGINS
|
||||
@@ -21,8 +21,8 @@ C PLUGINS
|
||||
You can write C plugins for mpv. These use the libmpv API, although they do not
|
||||
use the libmpv library itself.
|
||||
|
||||
They are available on Linux/BSD platforms only and enabled by default if the
|
||||
compiler supports linking with the ``-rdynamic`` flag.
|
||||
Currently, they must be explicitly enabled at build time with
|
||||
``--enable-cplugins``. They are available on Linux/BSD platforms only.
|
||||
|
||||
C plugins location
|
||||
------------------
|
||||
@@ -64,7 +64,7 @@ Linkage to libmpv
|
||||
-----------------
|
||||
|
||||
The current implementation requires that your plugins are **not** linked against
|
||||
libmpv. What your plugins use are not symbols from a libmpv binary, but
|
||||
libmpv. What your plugins uses are not symbols from a libmpv binary, but
|
||||
symbols from the mpv host binary.
|
||||
|
||||
Examples
|
||||
|
||||
+186
-241
@@ -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,55 +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.)
|
||||
|
||||
mpv internally loads the script's name by stripping the ``.lua`` extension and
|
||||
replacing all nonalphanumeric characters with ``_``. E.g., ``my-tools.lua``
|
||||
becomes ``my_tools``. If there are several scripts with the same name, it is
|
||||
made unique by appending a number. This is the name returned by
|
||||
``mp.get_script_name()``.
|
||||
|
||||
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
|
||||
--------------------------------------------------
|
||||
|
||||
@@ -107,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
|
||||
------------
|
||||
@@ -153,58 +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.
|
||||
|
||||
``fn`` is always called asynchronously, even if the command failed to start.
|
||||
|
||||
``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.del_property(name [,def])``
|
||||
Delete the given property. See ``mp.get_property`` and `Properties`_ for more
|
||||
information about properties. Most properties cannot be deleted.
|
||||
|
||||
Returns true on success, or ``nil, error`` on error.
|
||||
|
||||
``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
|
||||
@@ -299,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:
|
||||
@@ -312,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``.
|
||||
@@ -418,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 ``==``
|
||||
@@ -487,8 +370,8 @@ 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
|
||||
of ``.`` (Refer to https://www.lua.org/manual/5.2/manual.html#3.4.9 .)
|
||||
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:
|
||||
|
||||
@@ -514,16 +397,11 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
Return the name of the current script. The name is usually made of the
|
||||
filename of the script, with directory and file extension removed. If
|
||||
there are several scripts which would have the same name, it's made unique
|
||||
by appending a number. Any nonalphanumeric characters are replaced with ``_``.
|
||||
by appending a number.
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
The script ``/path/to/foo-script.lua`` becomes ``foo_script``.
|
||||
|
||||
``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.
|
||||
The script ``/path/to/fooscript.lua`` becomes ``fooscript``.
|
||||
|
||||
``mp.osd_message(text [,duration])``
|
||||
Show an OSD message on the screen. ``duration`` is in seconds, and is
|
||||
@@ -535,9 +413,21 @@ Advanced mp functions
|
||||
These also live in the ``mp`` module, but are documented separately as they
|
||||
are useful only in special situations.
|
||||
|
||||
``mp.suspend()``
|
||||
This function has been deprecated in mpv 0.21.0 and does nothing starting
|
||||
with mpv 0.23.0 (no replacement).
|
||||
|
||||
``mp.resume()``
|
||||
This function has been deprecated in mpv 0.21.0 and does nothing starting
|
||||
with mpv 0.23.0 (no replacement).
|
||||
|
||||
``mp.resume_all()``
|
||||
This function has been deprecated in mpv 0.21.0 and does nothing starting
|
||||
with mpv 0.23.0 (no replacement).
|
||||
|
||||
``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``
|
||||
@@ -589,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 outputs 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
|
||||
----------------
|
||||
|
||||
@@ -655,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::
|
||||
@@ -690,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.
|
||||
|
||||
@@ -765,7 +606,7 @@ strictly part of the guaranteed API.
|
||||
``mtime``
|
||||
time of last modification
|
||||
``ctime``
|
||||
time of last metadata change
|
||||
time of last metadata change (Linux) / time of creation (Windows)
|
||||
``is_file``
|
||||
Whether ``path`` is a regular file (boolean)
|
||||
``is_dir``
|
||||
@@ -777,7 +618,7 @@ strictly part of the guaranteed API.
|
||||
The booleans ``is_file`` and ``is_dir`` are provided as a convenience;
|
||||
they can be and are derived from ``mode``.
|
||||
|
||||
On error (e.g. path does not exist), ``nil, error`` is returned.
|
||||
On error (eg. path does not exist), ``nil, error`` is returned.
|
||||
|
||||
``utils.split_path(path)``
|
||||
Split a path into directory component and filename component, and return
|
||||
@@ -786,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.
|
||||
@@ -821,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
|
||||
@@ -877,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
|
||||
------
|
||||
@@ -891,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
|
||||
|
||||
+167
-730
File diff suppressed because it is too large.
Load diff
+948
-2833
File diff suppressed because it is too large.
Load diff
+5
-139
@@ -43,8 +43,7 @@ pl next
|
||||
============= ================================================
|
||||
|
||||
title
|
||||
| Displays current media-title, filename, custom title, or target chapter
|
||||
name while hovering the seekbar.
|
||||
| Displays current media-title, filename, or custom title
|
||||
|
||||
============= ================================================
|
||||
left-click show playlist position and length and full title
|
||||
@@ -129,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.
|
||||
|
||||
@@ -168,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 ``diamond``
|
||||
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. If set to ``yes``,
|
||||
default seeking mode is used (usually keyframes, but player defaults and
|
||||
heuristics can change it to exact). If set to ``no``, 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
|
||||
@@ -236,11 +198,6 @@ Configurable Options
|
||||
|
||||
Enable the OSC when fullscreen
|
||||
|
||||
``idlescreen``
|
||||
Default: yes
|
||||
|
||||
Show the mpv logo and message when idle
|
||||
|
||||
``scalewindowed``
|
||||
Default: 1.0
|
||||
|
||||
@@ -315,14 +272,6 @@ Configurable Options
|
||||
|
||||
Display timecodes with milliseconds
|
||||
|
||||
``tcspace``
|
||||
Default: 100 (allowed: 50-200)
|
||||
|
||||
Adjust space reserved for timecodes (current time and time remaining) in
|
||||
the ``bottombar`` and ``topbar`` layouts. The timecode width depends on the
|
||||
font, and with some fonts the spacing near the timecodes becomes too small.
|
||||
Use values above 100 to increase that spacing, or below 100 to decrease it.
|
||||
|
||||
``visibility``
|
||||
Default: auto (auto hide/show on mouse move)
|
||||
|
||||
@@ -336,85 +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.)
|
||||
|
||||
``livemarkers``
|
||||
Default: yes
|
||||
|
||||
Update chapter markers positions on duration changes, e.g. live streams.
|
||||
The updates are unoptimized - consider disabling it on very low-end systems.
|
||||
|
||||
``chapters_osd``, ``playlist_osd``
|
||||
Default: yes
|
||||
|
||||
Whether to display the chapters/playlist at the OSD when left-clicking the
|
||||
next/previous OSC buttons, respectively.
|
||||
|
||||
``chapter_fmt``
|
||||
Default: ``Chapter: %s``
|
||||
|
||||
Template for the chapter-name display when hovering the seekbar.
|
||||
Use ``no`` to disable chapter display on hover. Otherwise it's a lua
|
||||
``string.format`` template and ``%s`` is replaced with the actual name.
|
||||
|
||||
``unicodeminus``
|
||||
Default: no
|
||||
|
||||
Use a Unicode minus sign instead of an ASCII hyphen when displaying
|
||||
the remaining playback time.
|
||||
|
||||
|
||||
Script Commands
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -437,10 +307,6 @@ to set auto mode (the default) with ``b``::
|
||||
a script-message osc-visibility never
|
||||
b script-message osc-visibility auto
|
||||
|
||||
``osc-idlescreen``
|
||||
Controls the visibility of the mpv logo on idle. Valid arguments are ``yes``,
|
||||
``no``, and ``cycle`` to toggle between yes and no.
|
||||
|
||||
``osc-playlist``, ``osc-chapterlist``, ``osc-tracklist``
|
||||
Shows a limited view of the respective type of list using the OSC. First
|
||||
argument is duration in seconds.
|
||||
|
||||
+20
-86
@@ -22,51 +22,40 @@ stats:
|
||||
|
||||
==== ==================
|
||||
1 Show usual stats
|
||||
2 Show frame timings (scroll)
|
||||
3 Input cache stats
|
||||
4 Active key bindings (scroll)
|
||||
0 Internal stuff (scroll)
|
||||
2 Show frame timings
|
||||
==== ==================
|
||||
|
||||
On pages which support scroll, these key bindings are also active:
|
||||
Font
|
||||
~~~~
|
||||
|
||||
==== ==================
|
||||
UP Scroll one line up
|
||||
DOWN Scroll one line down
|
||||
==== ==================
|
||||
For optimal visual experience, a font with support for many font weights and
|
||||
monospaced digits is recommended. By default, the open source font
|
||||
`Source Sans Pro <https://github.com/adobe-fonts/source-sans-pro>`_ is used.
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
This script can be customized through a config file ``script-opts/stats.conf``
|
||||
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`_.
|
||||
|
||||
Configurable Options
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
``key_oneshot``
|
||||
Default: i
|
||||
``key_toggle``
|
||||
Default: I
|
||||
|
||||
Key bindings to display stats.
|
||||
|
||||
``key_page_1``
|
||||
Default: 1
|
||||
``key_page_2``
|
||||
Default: 2
|
||||
``key_page_3``
|
||||
Default: 3
|
||||
``key_page_4``
|
||||
Default: 4
|
||||
``key_page_0``
|
||||
Default: 0
|
||||
|
||||
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
|
||||
|
||||
@@ -103,13 +92,13 @@ Configurable Options
|
||||
Clear data buffers used for drawing graphs when toggling.
|
||||
|
||||
``font``
|
||||
Default: sans-serif
|
||||
Default: Source Sans Pro
|
||||
|
||||
Font name. Should support as many font weights as possible for optimal
|
||||
visual experience.
|
||||
|
||||
``font_mono``
|
||||
Default: monospace
|
||||
Default: Source Sans Pro
|
||||
|
||||
Font name for parts where monospaced characters are necessary to align
|
||||
text. Currently, monospaced digits are sufficient.
|
||||
@@ -160,69 +149,14 @@ Note: colors are given as hexadecimal values and use ASS tag order: BBGGRR
|
||||
Different key bindings
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Additional keys can be configured in ``input.conf`` to display the stats::
|
||||
A different key binding can be defined with the aforementioned options
|
||||
``key_oneshot`` and ``key_toggle`` but also with commands in ``input.conf``,
|
||||
for example::
|
||||
|
||||
e script-binding stats/display-stats
|
||||
E script-binding stats/display-stats-toggle
|
||||
|
||||
And to display a certain page directly::
|
||||
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
|
||||
|
||||
Active key bindings page
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Lists the active key bindings and the commands they're bound to, excluding the
|
||||
interactive keys of the stats script itself. See also ``--input-test`` for more
|
||||
detailed view of each binding.
|
||||
|
||||
The keys are grouped automatically using a simple analysis of the command
|
||||
string, and one should not expect documentation-level grouping accuracy,
|
||||
however, it should still be reasonably useful.
|
||||
|
||||
Using ``--idle --script-opts=stats-bindlist=yes`` will print the list to the
|
||||
terminal and quit immediately. By default long lines are shortened to 79 chars,
|
||||
and terminal escape sequences are enabled. A different length limit can be
|
||||
set by changing ``yes`` to a number (at least 40), and escape sequences can be
|
||||
disabled by adding ``-`` before the value, e.g. ``...=-yes`` or ``...=-120``.
|
||||
|
||||
Like with ``--input-test``, the list includes bindings from ``input.conf`` and
|
||||
from user scripts. Use ``--no-config`` to list only built-in bindings.
|
||||
|
||||
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).
|
||||
+100
-337
@@ -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,23 +41,16 @@ 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::
|
||||
|
||||
To get a full list of available video filters, see ``--vf=help`` and
|
||||
https://ffmpeg.org/ffmpeg-filters.html .
|
||||
http://ffmpeg.org/ffmpeg-filters.html .
|
||||
|
||||
Also, keep in mind that most actual filters are available via the ``lavfi``
|
||||
wrapper, which gives you access to most of libavfilter's filters. This
|
||||
@@ -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,69 +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``.
|
||||
|
||||
.. note::
|
||||
|
||||
Conversion between hardware formats is supported in some cases.
|
||||
eg: ``cuda`` to ``vulkan``, or ``vaapi`` to ``vulkan``.
|
||||
|
||||
``<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.
|
||||
|
||||
When converting between hardware formats, this parameter has no effect,
|
||||
and the only conversion that is done is the format conversion.
|
||||
|
||||
.. 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
|
||||
@@ -194,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
|
||||
@@ -237,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
|
||||
@@ -281,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
|
||||
@@ -316,31 +239,18 @@ Available mpv-only filters are:
|
||||
:709-1886: Scene-referred using the BT709+BT1886 interaction
|
||||
:gamma1.2: Scene-referred using a pure power OOTF (gamma=1.2)
|
||||
|
||||
``<dolbyvision=yes|no>``
|
||||
Whether or not to include Dolby Vision metadata (default: yes). If
|
||||
disabled, any Dolby Vision metadata will be stripped from frames.
|
||||
|
||||
``<film-grain=yes|no>``
|
||||
Whether or not to include film grain metadata (default: yes). If
|
||||
disabled, any film grain metadata will be stripped from frames.
|
||||
|
||||
``<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
|
||||
@@ -351,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.
|
||||
@@ -405,7 +314,7 @@ Available mpv-only filters are:
|
||||
option gives the flags which should be passed to libswscale. This
|
||||
option is numeric and takes a bit-wise combination of ``SWS_`` flags.
|
||||
|
||||
See ``https://git.videolan.org/?p=ffmpeg.git;a=blob;f=libswscale/swscale.h``.
|
||||
See ``http://git.videolan.org/?p=ffmpeg.git;a=blob;f=libswscale/swscale.h``.
|
||||
|
||||
``<o>``
|
||||
Set AVFilterGraph options. These should be documented by FFmpeg.
|
||||
@@ -442,38 +351,29 @@ 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:
|
||||
|
||||
::
|
||||
|
||||
import vapoursynth as vs
|
||||
from vapoursynth import core
|
||||
core = vs.get_core()
|
||||
core.std.AddBorders(video_in, 10, 10, 20, 20).set_output()
|
||||
|
||||
.. warning::
|
||||
@@ -484,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
|
||||
@@ -547,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
|
||||
@@ -575,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.
|
||||
@@ -583,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).
|
||||
|
||||
@@ -595,26 +469,22 @@ 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
|
||||
this interpretation was more obvious and intuitive, it was
|
||||
this interpretation was more obvious and inuitive, it was
|
||||
apparently wrong, and not shared by Intel driver developers.
|
||||
:yes: Use Intel interpretation of surface forward and backwards
|
||||
references (default). This is what Intel drivers and newer Mesa
|
||||
@@ -622,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>``
|
||||
@@ -665,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).
|
||||
@@ -679,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 to 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.
|
||||
|
||||
+123
-339
@@ -21,46 +21,6 @@ in the list.
|
||||
|
||||
Available video output drivers are:
|
||||
|
||||
``gpu``
|
||||
General purpose, customizable, GPU-accelerated video output driver. It
|
||||
supports extended scaling methods, dithering, color management, custom
|
||||
shaders, HDR, and more.
|
||||
|
||||
See `GPU renderer options`_ for options specific to this VO.
|
||||
|
||||
By default, it tries to use fast and fail-safe settings. Use the
|
||||
``gpu-hq`` profile to use this driver with defaults set to high quality
|
||||
rendering. The profile can be applied with ``--profile=gpu-hq`` and its
|
||||
contents can be viewed with ``--show-profile=gpu-hq``.
|
||||
|
||||
This VO abstracts over several possible graphics APIs and windowing
|
||||
contexts, which can be influenced using the ``--gpu-api`` and
|
||||
``--gpu-context`` options.
|
||||
|
||||
Hardware decoding over OpenGL-interop is supported to some degree. Note
|
||||
that in this mode, some corner case might not be gracefully handled, and
|
||||
color space conversion and chroma upsampling is generally in the hand of
|
||||
the hardware decoder APIs.
|
||||
|
||||
``gpu`` makes use of FBOs by default. Sometimes you can achieve better
|
||||
quality or performance by changing the ``--fbo-format`` option to
|
||||
``rgb16f``, ``rgb32f`` or ``rgb``. Known problems include Mesa/Intel not
|
||||
accepting ``rgb16``, Mesa sometimes not being compiled with float texture
|
||||
support, and some macOS setups being very slow with ``rgb16`` but fast
|
||||
with ``rgb32f``. If you have problems, you can also try enabling the
|
||||
``--gpu-dumb-mode=yes`` option.
|
||||
|
||||
``gpu-next``
|
||||
Experimental video renderer based on ``libplacebo``. This supports almost
|
||||
the same set of features as ``--vo=gpu``. See `GPU renderer options`_ for a
|
||||
list.
|
||||
|
||||
Should generally be faster and higher quality, but some features may still
|
||||
be missing or misbehave. Expect (and report!) bugs. See here for a list of
|
||||
known differences and bugs:
|
||||
|
||||
https://github.com/mpv-player/mpv/wiki/GPU-Next-vs-GPU
|
||||
|
||||
``xv`` (X11 only)
|
||||
Uses the XVideo extension to enable hardware-accelerated display. This is
|
||||
the most compatible VO on X, but may be low-quality, and has issues with
|
||||
@@ -111,16 +71,11 @@ 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)
|
||||
Uses the VDPAU interface to display and optionally also decode video.
|
||||
Hardware decoding is used with ``--hwdec=vdpau``. Note that there is
|
||||
absolutely no reason to use this, other than compatibility. We strongly
|
||||
recommend that you use ``--vo=gpu`` with ``--hwdec=nvdec`` instead.
|
||||
Hardware decoding is used with ``--hwdec=vdpau``.
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -141,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``.)
|
||||
|
||||
@@ -223,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
|
||||
@@ -270,13 +273,42 @@ Available video output drivers are:
|
||||
``--vo-direct3d-exact-backbuffer``
|
||||
Always resize the backbuffer to window size.
|
||||
|
||||
``gpu``
|
||||
General purpose, customizable, GPU-accelerated video output driver. It
|
||||
supports extended scaling methods, dithering, color management, custom
|
||||
shaders, HDR, and more.
|
||||
|
||||
See `GPU renderer options`_ for options specific to this VO.
|
||||
|
||||
By default, it tries to use fast and fail-safe settings. Use the
|
||||
``gpu-hq`` profile to use this driver with defaults set to high quality
|
||||
rendering. The profile can be applied with ``--profile=gpu-hq`` and its
|
||||
contents can be viewed with ``--show-profile=gpu-hq``.
|
||||
|
||||
This VO abstracts over several possible graphics APIs and windowing
|
||||
contexts, which can be influenced using the ``--gpu-api`` and
|
||||
``--gpu-context`` options.
|
||||
|
||||
Hardware decoding over OpenGL-interop is supported to some degree. Note
|
||||
that in this mode, some corner case might not be gracefully handled, and
|
||||
color space conversion and chroma upsampling is generally in the hand of
|
||||
the hardware decoder APIs.
|
||||
|
||||
``gpu`` makes use of FBOs by default. Sometimes you can achieve better
|
||||
quality or performance by changing the ``--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
|
||||
with ``rgb32f``. If you have problems, you can also try enabling the
|
||||
``--gpu-dumb-mode=yes`` option.
|
||||
|
||||
``sdl``
|
||||
SDL 2.0+ Render video output driver, depending on system with or without
|
||||
hardware acceleration. Should work on all platforms supported by SDL 2.0.
|
||||
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:
|
||||
|
||||
@@ -286,23 +318,13 @@ Available video output drivers are:
|
||||
``--sdl-switch-mode``
|
||||
Instruct SDL to switch the monitor video mode when going fullscreen.
|
||||
|
||||
``dmabuf-wayland``
|
||||
Experimental Wayland output driver designed for use with either drm stateless
|
||||
or VA API hardware decoding. The driver is designed to avoid any GPU to CPU copies,
|
||||
and to perform scaling and color space conversion using fixed-function hardware,
|
||||
if available, rather than GPU shaders. This frees up GPU resources for other tasks.
|
||||
It is highly recommended to use this VO with the appropriate ``--hwdec`` option such
|
||||
as ``auto-safe``. It can still work in some circumstances without ``--hwdec`` due to
|
||||
mpv's internal conversion filters, but this is not recommended as it's a needless
|
||||
extra step. Correct output depends on support from your GPU, drivers, and compositor.
|
||||
Weston and wlroots-based compositors like Sway and Intel GPUs are known to generally
|
||||
work.
|
||||
|
||||
``vaapi``
|
||||
Intel VA API video output driver with support for hardware decoding. Note
|
||||
that there is absolutely no reason to use this, other than compatibility.
|
||||
This is low quality, and has issues with OSD. We strongly recommend that
|
||||
you use ``--vo=gpu`` with ``--hwdec=vaapi`` instead.
|
||||
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=opengl`` too.
|
||||
|
||||
The following global options are supported by this video output:
|
||||
|
||||
@@ -358,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 output 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.
|
||||
@@ -386,160 +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.
|
||||
|
||||
``kitty``
|
||||
Graphical output for the terminal, using the kitty graphics protocol.
|
||||
Tested with kitty and Konsole.
|
||||
|
||||
You may need to use ``--profile=sw-fast`` to get decent performance.
|
||||
|
||||
Kitty size and alignment options:
|
||||
|
||||
``--vo-kitty-cols=<columns>``, ``--vo-kitty-rows=<rows>`` (default: 0)
|
||||
Specify the terminal size in character cells, otherwise (0) read it
|
||||
from the terminal, or fall back to 80x25.
|
||||
|
||||
``--vo-kitty-width=<width>``, ``--vo-kitty-height=<height>`` (default: 0)
|
||||
Specify the available size in pixels, otherwise (0) read it from the
|
||||
terminal, or fall back to 320x240.
|
||||
|
||||
``--vo-kitty-left=<col>``, ``--vo-kitty-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-kitty-config-clear=<yes|no>`` (default: yes)
|
||||
Whether or not to clear the terminal whenever the output is
|
||||
reconfigured (e.g. when video size changes).
|
||||
|
||||
``--vo-kitty-alt-screen=<yes|no>`` (default: yes)
|
||||
Whether or not to use the alternate screen buffer and return the
|
||||
terminal to its previous state on exit. When set to no, the last
|
||||
kitty image stays on screen after quit, with the cursor following it.
|
||||
|
||||
``--vo-kitty-use-shm=<yes|no>`` (default: no)
|
||||
Use shared memory objects to transfer image data to the terminal.
|
||||
This is much faster than sending the data as escape codes, but is not
|
||||
supported by as many terminals. It also only works on the local machine
|
||||
and not via e.g. SSH connections.
|
||||
|
||||
This option is not implemented on Windows.
|
||||
|
||||
``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.
|
||||
On some platforms, using the ``--vo-sixel-buffered`` option may work as
|
||||
well.
|
||||
|
||||
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.
|
||||
|
||||
``--vo-sixel-config-clear=<yes|no>`` (default: yes)
|
||||
Whether or not to clear the terminal whenever the output is
|
||||
reconfigured (e.g. when video size changes).
|
||||
|
||||
``--vo-sixel-alt-screen=<yes|no>`` (default: yes)
|
||||
Whether or not to use the alternate screen buffer and return the
|
||||
terminal to its previous state on exit. When set to no, the last
|
||||
sixel image stays on screen after quit, with the cursor following it.
|
||||
|
||||
``--vo-sixel-exit-clear`` is a deprecated alias for this option and
|
||||
may be removed in the future.
|
||||
|
||||
``--vo-sixel-buffered=<yes|no>`` (default: no)
|
||||
Buffers the full output sequence before writing it to the terminal.
|
||||
On POSIX platforms, this can help prevent interruption (including from
|
||||
other applications) and thus broken images, but may come at a
|
||||
performance cost with some terminals and is subject to implementation
|
||||
details.
|
||||
|
||||
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 (Default)
|
||||
Let libsixel choose the dithering method.
|
||||
none
|
||||
Don't diffuse
|
||||
atkinson
|
||||
Diffuse with Bill Atkinson's method.
|
||||
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)
|
||||
Has no effect with fixed palette. 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.
|
||||
|
||||
``--vo-sixel-threshold=<threshold>`` (default: -1)
|
||||
Has no effect with fixed 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``). The default (-1) will choose a palette
|
||||
on every frame and will have better quality.
|
||||
|
||||
``image``
|
||||
Output each frame into an image file in the current directory. Each file
|
||||
takes the frame number padded with leading zeros as name.
|
||||
@@ -555,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)
|
||||
@@ -567,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 macOS 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).
|
||||
@@ -620,101 +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
|
||||
connectors. The ``<gpu_number>`` argument can be used to disambiguate
|
||||
multiple graphic cards, but is deprecated in favor of ``--drm-device``.
|
||||
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-device=<path>``
|
||||
Select the DRM device file to use. If specified this overrides automatic
|
||||
card selection and any card number specified ``--drm-connector``.
|
||||
(default: empty)
|
||||
``--drm-mode=<number>``
|
||||
Mode ID to use (resolution, bit depth and frame rate).
|
||||
(default: 0)
|
||||
|
||||
``--drm-mode=<preferred|highest|N|WxH[@R]>``
|
||||
Mode to use (resolution and frame rate).
|
||||
Possible values:
|
||||
|
||||
: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-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-overlay 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-overlay 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-overlay 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-overlay hwdec interop this option will
|
||||
just cause the video to get rendered at a different resolution and then
|
||||
scaled to screen size.
|
||||
|
||||
(default: display resolution)
|
||||
|
||||
``--drm-vrr-enabled=<no|yes|auto>``
|
||||
Toggle use of Variable Refresh Rate (VRR), aka Freesync or Adaptive Sync
|
||||
on compatible systems. VRR allows for the display to be refreshed at any
|
||||
rate within a range (usually ~40Hz-60Hz for 60Hz displays). This can help
|
||||
with playback of 24/25/50fps content. Support depends on the use of a
|
||||
compatible monitor, GPU, and a sufficiently new kernel with drivers
|
||||
that support the feature.
|
||||
|
||||
:no: Do not attempt to enable VRR. (default)
|
||||
:yes: Attempt to enable VRR, whether the capability is reported or not.
|
||||
:auto: Attempt to enable VRR if support is reported.
|
||||
``--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``.
|
||||
@@ -725,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 ``--hwdec=mediacodec``
|
||||
or ``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.
|
||||
To use hardware decoding with ``--vo-gpu`` instead, use
|
||||
``--hwdec=mediacodec-copy`` along with ``--gpu-context=android``.
|
||||
@@ -52,7 +52,7 @@ Input
|
||||
* Classic LIRC support was removed. Install remotes as input devices instead.
|
||||
This way they will send X11 key events to the mpv window, which can be bound
|
||||
using the normal ``input.conf``.
|
||||
Also see: https://github.com/mpv-player/mpv/wiki/IR-remotes
|
||||
Also see: http://github.com/mpv-player/mpv/wiki/IR-remotes
|
||||
* Joystick support was removed. It was considered useless and was the cause
|
||||
of some problems (e.g. a laptop's accelerator being recognized as joystick).
|
||||
* Support for relative seeking by percentage.
|
||||
@@ -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``
|
||||
|
||||
+26
-47
@@ -1,15 +1,13 @@
|
||||
Release Policy
|
||||
==============
|
||||
|
||||
Once or twice a year, a new release is cut off of the master branch and is
|
||||
Once or twice a month, a new release is cut off of the master branch and is
|
||||
assigned a 0.X.Y version number, where X is incremented each time a release
|
||||
contains breaking changes, such as changed options or added/removed features,
|
||||
and Y is incremented if a release contains only bugfixes and other minor
|
||||
changes.
|
||||
|
||||
Releases are tagged on the master branch and will not be maintained separately.
|
||||
Patch releases may be made if the amount or severity of bugs justify it, or in
|
||||
the event of security issues.
|
||||
|
||||
The goal of releases is to provide Linux distributions with something to
|
||||
package. If you want the newest features, just use the master branch.
|
||||
@@ -22,42 +20,33 @@ 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.
|
||||
|
||||
- Push release branch (`release/0.X`) and tag to GitHub.
|
||||
- Add -UNKNOWN suffix to version in `VERSION` file.
|
||||
|
||||
- Commit changes, push branch and tag to GitHub.
|
||||
|
||||
- Create a new GitHub release using the content of `RELEASE_NOTES` related to
|
||||
the new version.
|
||||
|
||||
- Readd -UNKNOWN suffix to version in `VERSION` file.
|
||||
|
||||
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.
|
||||
|
||||
This does not apply to patch releases, which are tagged directly on the
|
||||
`release/0.X` branch. The master branch always remains at v0.X.0.
|
||||
|
||||
Release notes template
|
||||
----------------------
|
||||
|
||||
Here is a template that can be used for writing the `RELEASE_NOTES` file:
|
||||
Here is a template that can be used for writing the `RELEASE_NOTES` file.
|
||||
|
||||
```markdown
|
||||
Release 0.X.Y
|
||||
=============
|
||||
|
||||
This release requires FFmpeg <ver> or newer.
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
@@ -66,18 +55,20 @@ New
|
||||
|
||||
- List of new features
|
||||
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- List of changed features
|
||||
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- List of removed features
|
||||
|
||||
Deprecated
|
||||
~~~~~~~~~~
|
||||
|
||||
- List of deprecated features
|
||||
|
||||
Behavior
|
||||
--------
|
||||
|
||||
- List of user-visible changes in behavior
|
||||
|
||||
Options and Commands
|
||||
--------------------
|
||||
@@ -87,52 +78,40 @@ Added
|
||||
|
||||
- List of added options and commands
|
||||
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- List of changed options and commands
|
||||
|
||||
Renamed
|
||||
~~~~~~~
|
||||
|
||||
- List of renamed options and commands
|
||||
|
||||
Deprecated
|
||||
~~~~~~~~~~
|
||||
|
||||
- List of deprecated options and commands
|
||||
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- List of removed options and commands
|
||||
|
||||
|
||||
Fixes and Minor Enhancements
|
||||
----------------------------
|
||||
|
||||
- List of fixes and minor enhancements
|
||||
|
||||
|
||||
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 <start>..<end>`
|
||||
in the git repository.
|
||||
This listing is not complete. There are many more bug fixes and changes. The
|
||||
complete change log can be viewed by running `git log <start>..<end>` in
|
||||
the git repository.
|
||||
```
|
||||
|
||||
Note that the "Release 0.X.Y" title should be removed when creating a new GitHub
|
||||
release.
|
||||
|
||||
When creating a new point release its changes should be added on top of the
|
||||
`RELEASE_NOTES` file (with the appropriate title) so that all the changes in
|
||||
the current 0.X branch will be included. This way the `RELEASE_NOTES` file
|
||||
can be used by distributors as changelog for point releases too.
|
||||
|
||||
The changelog of lists all changes since the last release, including those
|
||||
that have been backported to patch releases already.
|
||||
|
||||
Some additional advice:
|
||||
- Especially for features, try to reword the messages so that the user-visible
|
||||
change is clear to the reader. But don't simplify too much or be too verbose.
|
||||
- It often makes sense to merge multiple related changes into one line
|
||||
- Changes that have been made and reverted within the same release must not
|
||||
appear in the changelog
|
||||
- Limit the "Options and Commands" section to relevant changes
|
||||
- When filling in the GitHub release, remove the "Release 0.X.Y" heading
|
||||
+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 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 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.
|
||||
@@ -46,7 +46,7 @@ mpv's custom configure step on top of waf
|
||||
|
||||
To some extents mpv has a custom build system written on top of waf. This
|
||||
document will not go over the standard waf behaviour as that is documented in
|
||||
the `Waf book <https://waf.io/book/>`_.
|
||||
the `Waf book <http://docs.waf.googlecode.com/git/book_17/single.html>`_.
|
||||
|
||||
All of the configuration process is handled with a declarative approach. Lists
|
||||
of dictionaries define the checks, and some custom Python code traverses these
|
||||
|
||||
+13
-12
@@ -1,8 +1,8 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||
51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
@@ -15,7 +15,7 @@ software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
the GNU Library General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
@@ -55,7 +55,7 @@ patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
@@ -110,7 +110,7 @@ above, provided that you also meet all of these conditions:
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
@@ -168,7 +168,7 @@ access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
@@ -225,7 +225,7 @@ impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
@@ -278,7 +278,7 @@ PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
@@ -303,9 +303,10 @@ the "copyright" line and a pointer to where the full notice is found.
|
||||
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 this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
@@ -335,5 +336,5 @@ necessary. Here is a sample; alter the names:
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
library. If this is what you want to do, use the GNU Library General
|
||||
Public License instead of this License.
|
||||
+9
-9
@@ -55,7 +55,7 @@ modified by someone else and passed on, the recipients should know
|
||||
that what they have is not the original version, so that the original
|
||||
author's reputation will not be affected by problems that might be
|
||||
introduced by others.
|
||||
|
||||
|
||||
Finally, software patents pose a constant threat to the existence of
|
||||
any free program. We wish to make sure that a company cannot
|
||||
effectively restrict the users of a free program by obtaining a
|
||||
@@ -111,7 +111,7 @@ modification follow. Pay close attention to the difference between a
|
||||
"work based on the library" and a "work that uses the library". The
|
||||
former contains code derived from the library, whereas the latter must
|
||||
be combined with the library in order to run.
|
||||
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
@@ -158,7 +158,7 @@ Library.
|
||||
You may charge a fee for the physical act of transferring a copy,
|
||||
and you may at your option offer warranty protection in exchange for a
|
||||
fee.
|
||||
|
||||
|
||||
2. You may modify your copy or copies of the Library or any portion
|
||||
of it, thus forming a work based on the Library, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
@@ -216,7 +216,7 @@ instead of to this License. (If a newer version than version 2 of the
|
||||
ordinary GNU General Public License has appeared, then you can specify
|
||||
that version instead if you wish.) Do not make any other change in
|
||||
these notices.
|
||||
|
||||
|
||||
Once this change is made in a given copy, it is irreversible for
|
||||
that copy, so the ordinary GNU General Public License applies to all
|
||||
subsequent copies and derivative works made from that copy.
|
||||
@@ -267,7 +267,7 @@ Library will still fall under Section 6.)
|
||||
distribute the object code for the work under the terms of Section 6.
|
||||
Any executables containing that work also fall under Section 6,
|
||||
whether or not they are linked directly with the Library itself.
|
||||
|
||||
|
||||
6. As an exception to the Sections above, you may also combine or
|
||||
link a "work that uses the Library" with the Library to produce a
|
||||
work containing portions of the Library, and distribute that work
|
||||
@@ -329,7 +329,7 @@ restrictions of other proprietary libraries that do not normally
|
||||
accompany the operating system. Such a contradiction means you cannot
|
||||
use both them and the Library together in an executable that you
|
||||
distribute.
|
||||
|
||||
|
||||
7. You may place library facilities that are a work based on the
|
||||
Library side-by-side in a single library together with other library
|
||||
facilities not covered by this License, and distribute such a combined
|
||||
@@ -370,7 +370,7 @@ subject to these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties with
|
||||
this License.
|
||||
|
||||
|
||||
11. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
@@ -422,7 +422,7 @@ conditions either of that version or of any later version published by
|
||||
the Free Software Foundation. If the Library does not specify a
|
||||
license version number, you may choose any version ever published by
|
||||
the Free Software Foundation.
|
||||
|
||||
|
||||
14. If you wish to incorporate parts of the Library into other free
|
||||
programs whose distribution conditions are incompatible with these,
|
||||
write to the author to ask for permission. For software which is
|
||||
@@ -456,7 +456,7 @@ SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
|
||||
How to Apply These Terms to Your New Libraries
|
||||
|
||||
If you develop a new library, and you want it to be of the greatest
|
||||
|
||||
@@ -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]
|
||||
* [Manual](https://mpv.io/manual/master/)
|
||||
* [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,20 +43,18 @@ 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
|
||||
|
||||
|
||||
For semi-official builds and third-party packages please see
|
||||
[mpv.io/installation](https://mpv.io/installation/).
|
||||
[mpv.io/installation](http://mpv.io/installation/).
|
||||
|
||||
## Changelog
|
||||
|
||||
@@ -73,30 +74,11 @@ Changes to the default key bindings are indicated in
|
||||
|
||||
|
||||
Compiling with full features requires development files for several
|
||||
external libraries. One of the two build systems supported by mpv is required:
|
||||
[meson](https://mesonbuild.com/index.html) or [waf](https://waf.io/). Meson
|
||||
can be obtained from your distro or PyPI. Waf can be downloaded by using the
|
||||
`./bootstrap.py` script. It will get the latest version of waf that was tested
|
||||
with mpv. Some documentation about the differences between the build systems are
|
||||
located in [build-system-differences][build-system-differences].
|
||||
external libraries. Below is a list of some important requirements.
|
||||
|
||||
**Note**: Building with waf is considered *deprecated* and will be removed in the
|
||||
future.
|
||||
|
||||
### Meson
|
||||
|
||||
After creating your build directory (e.g. `meson setup build`), you can view a list
|
||||
of all the build options via `meson configure build`. You could also just simply
|
||||
look at the `meson_options.txt` file. Logs are stored in `meson-logs` within
|
||||
your build directory.
|
||||
|
||||
Example:
|
||||
|
||||
meson setup build
|
||||
meson compile -C build
|
||||
meson install -C build
|
||||
|
||||
### Waf
|
||||
The mpv build system uses [waf](https://waf.io/), but we don't store it in the
|
||||
repository. The `./bootstrap.py` script will download the latest version
|
||||
of waf that was tested with the build system.
|
||||
|
||||
For a list of the available build options use `./waf configure --help`. If
|
||||
you think you have support for some feature installed but configure fails to
|
||||
@@ -104,10 +86,10 @@ detect it, the file `build/config.log` may contain information about the
|
||||
reasons for the failure.
|
||||
|
||||
NOTE: To avoid cluttering the output with unreadable spam, `--help` only shows
|
||||
one of the two switches for each option. If the option is autodetected or
|
||||
enabled by default, the `--disable-***` switch is printed; if the option is
|
||||
disabled by default, the `--enable-***` switch is printed. Either way, you can
|
||||
use `--enable-***` or `--disable-**` regardless of what is printed by `--help`.
|
||||
one of the two switches for each option. If the option is autodetected by
|
||||
default, the `--disable-***` switch is printed; if the option is disabled by
|
||||
default, the `--enable-***` switch is printed. Either way, you can use
|
||||
`--enable-***` or `--disable-**` regardless of what is printed by `--help`.
|
||||
|
||||
To build the software you can use `./waf build`: the result of the compilation
|
||||
will be located in `build/mpv`. You can use `./waf install` to install mpv
|
||||
@@ -134,27 +116,23 @@ 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)
|
||||
- harfbuzz (required for correct rendering of combining characters, particularly
|
||||
for correct rendering of non-English text on OSX, and Arabic/Indic scripts on
|
||||
any platform)
|
||||
- harfbuzz (optional, required for correct rendering of combining characters,
|
||||
particularly for correct rendering of non-English text on OSX, and
|
||||
Arabic/Indic scripts on any platform)
|
||||
|
||||
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,
|
||||
@@ -167,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
|
||||
@@ -200,37 +200,46 @@ changes come and talk to us on IRC before you start working on them. It will
|
||||
make code review easier for both parties later on.
|
||||
|
||||
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%3Ameta%3Afeature-request)
|
||||
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.libera.chat`
|
||||
- **Developer IRC Channel**: `#mpv-devel` on `irc.libera.chat`
|
||||
- **GitHub issue tracker**: [issue tracker][issue-tracker] (report bugs here)
|
||||
- **User IRC Channel**: `#mpv` on `irc.freenode.net`
|
||||
- **Developer IRC Channel**: `#mpv-devel` on `irc.freenode.net`
|
||||
|
||||
To contact the `mpv` team in private write to `mpv-team@googlegroups.com`. Use
|
||||
only if discretion is required.
|
||||
|
||||
[FAQ]: https://github.com/mpv-player/mpv/wiki/FAQ
|
||||
[releases]: https://github.com/mpv-player/mpv/releases
|
||||
[mpv-build]: https://github.com/mpv-player/mpv-build
|
||||
[homebrew-mpv]: https://github.com/mpv-player/homebrew-mpv
|
||||
[issue-tracker]: https://github.com/mpv-player/mpv/issues
|
||||
[ffmpeg_vs_libav]: https://github.com/mpv-player/mpv/wiki/FFmpeg-versus-Libav
|
||||
[release-policy]: https://github.com/mpv-player/mpv/blob/master/DOCS/release-policy.md
|
||||
[windows_compilation]: https://github.com/mpv-player/mpv/blob/master/DOCS/compile-windows.md
|
||||
[mplayer-changes]: https://github.com/mpv-player/mpv/blob/master/DOCS/mplayer-changes.rst
|
||||
[interface-changes]: https://github.com/mpv-player/mpv/blob/master/DOCS/interface-changes.rst
|
||||
[api-changes]: https://github.com/mpv-player/mpv/blob/master/DOCS/client-api-changes.rst
|
||||
[restore-old-bindings]: https://github.com/mpv-player/mpv/blob/master/etc/restore-old-bindings.conf
|
||||
[contribute.md]: https://github.com/mpv-player/mpv/blob/master/DOCS/contribute.md
|
||||
[build-system-differences]: https://github.com/mpv-player/mpv/blob/master/DOCS/build-system-differences.md
|
||||
+134
-175
@@ -1,57 +1,70 @@
|
||||
Release 0.36.0
|
||||
Release 0.28.2
|
||||
==============
|
||||
|
||||
This release requires FFmpeg 4.4 or newer.
|
||||
This release contains an additional fix for CVE-2018-6360.
|
||||
|
||||
This is the last release to contain the waf build system.
|
||||
Meson is the primary and actively supported build system while waf is considered deprecated.
|
||||
Fixes and Minor Enhancements
|
||||
----------------------------
|
||||
- ytdl_hook: whitelist subtitle URLs as well (#5456)
|
||||
|
||||
Release 0.28.1
|
||||
==============
|
||||
|
||||
This release fixes CVE-2018-6360.
|
||||
|
||||
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
|
||||
--------
|
||||
|
||||
New
|
||||
~~~
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- player: add window-id property
|
||||
- vo_kitty: introduce modern sixel alternative
|
||||
- wayland: add wp-fractional-scale-v1 support
|
||||
- player/command: add `user-data` property (replacement for `shared-script-properties`)
|
||||
- vo_gpu_next: support mapping HDR10+ dynamic metadata
|
||||
- vo_gpu_next: allow screenshots in native colorspace
|
||||
- vo_gpu_next: parse Dolby Vision metadata for dynamic scene brightness
|
||||
- video: allow csp equalizer adjustments with floating point precision
|
||||
- command: add platform property
|
||||
- demux_mkv: support ARIB captions
|
||||
- hwdec_vulkan: add support for Vulkan-based hardware decoding
|
||||
- player: better handling of forced-only subtitles and a toggle in the OSC
|
||||
- vo_dmabuf_wayland: add osd support
|
||||
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- TOOLS/umpv: prefer $XDG_RUNTIME_DIR
|
||||
- ffmpeg: increase minimum required version to 4.4
|
||||
- player: choose speed of smallest acceptable factor for display sync
|
||||
- player: don't force saving `start` in watch-later-options
|
||||
- build: officially deprecate waf
|
||||
- vf_sub: undeprecate
|
||||
- player: set playlist title to media title if not set already
|
||||
- player: use and respect XDG_STATE_HOME and XDG_CACHE_HOME by default
|
||||
- wayland: bump required version to 1.20 and wayland-protocols to 1.25
|
||||
- ao_pipewire: require at least libpipewire 0.3.48
|
||||
- vo: hwdec: prioritise drmprime over drmprime_overlay
|
||||
- vo_gpu/vo_gpu_next: enable gpu shader and icc cache 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
|
||||
~~~~~~~
|
||||
|
||||
- drm: remove support for legacy (non-atomic) API
|
||||
- stream/dvb: drop support for DVB API before Linux 3.7
|
||||
- hwdec_cuda: drop support for PL_HANDLE_WIN32_KMT (Windows 7 only)
|
||||
- vo_dmabuf_wayland: drop linux-dmabuf-v2 support
|
||||
- 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
|
||||
@@ -60,160 +73,106 @@ Options and Commands
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- player: add --force-render option
|
||||
- demux: add --demuxer-hysteresis-secs option to save power with caching
|
||||
- ao_pipewire: allow usage of global volume control via --pipewire-volume-mode
|
||||
- vo_gpu_next: add --tone-mapping-visualize
|
||||
- sub: add --sub-fonts-dir and --osd-fonts-dir options
|
||||
- player: add --auto-window-resize
|
||||
- console.lua: add a script-opt for the border size
|
||||
- player: add --drag-and-drop option
|
||||
- vo_gpu_next: add --corner-rounding option
|
||||
- player: add more precise sub fallback options --subs-fallback and --subs-fallback-forced
|
||||
- player: add --input-cursor-passthrough option
|
||||
- TOOLS/lua/autoload: allow extending ext sets from script-opts
|
||||
- demux_playlist: add --directory-mode option to control recursive directory loading
|
||||
- vo_gpu_next: add --target-contrast and --hdr-contrast-recovery/smoothness
|
||||
- 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
|
||||
~~~~~~~
|
||||
|
||||
- vd_lavc: add "auto" choice for --vd-lavc-dr and make it the default
|
||||
(notably this change was also backported to v0.35.1)
|
||||
- wayland: add auto choice to --wayland-configure-bounds
|
||||
- image_writer: change --screenshot-tag-colorspace default to yes
|
||||
- vo_gpu_next: expose --tone-mapping=st2094-40 and st2094-10
|
||||
- wayland: make --wayland-edge-pixels-pointer default to 16
|
||||
- options: enable scripts related opts also with cplugins
|
||||
- player/screenshot: add filename return field
|
||||
- options: set --subs-with-matching-audio to off by default
|
||||
- options: default --slang to auto, which uses the settings of the user's OS
|
||||
- vd_lavc: allow user to specify a priority list in --hwdec
|
||||
- vd_lavc: map hwdec=yes to hwdec=auto-safe and change ctrl+h to use auto-safe
|
||||
|
||||
|
||||
Deprecated
|
||||
~~~~~~~~~~
|
||||
|
||||
- drm: deprecate `--drm-atomic` (now always enabled)
|
||||
- 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
|
||||
~~~~~~~
|
||||
|
||||
- vo_gpu_next: remove --tone-mapping-crosstalk
|
||||
- options: remove --heartbeat-cmd and --heartbeat-interval
|
||||
(incidentally fixes #4888)
|
||||
|
||||
|
||||
Fixes and Minor Enhancements
|
||||
----------------------------
|
||||
|
||||
- ao_pipewire: log version and other useful debug info
|
||||
- wayland: error out if essential protocol support is missing
|
||||
- wayland: add support for content-type protocol
|
||||
- wayland: also log refresh rate on surface entrance
|
||||
- vo_gpu_next: fix undefined behavior on alpha-first formats
|
||||
- meson: prepend MPV_CONFDIR path with prefix
|
||||
- meson: unbreak dl check on BSDs without libdl
|
||||
- lcms: fix crash with lcms2-related options if lcms is disabled
|
||||
- meson: fix stdatomic detection on bsd
|
||||
- osc: don't spam window-controls bindings on every render
|
||||
- wayland: check for resize/move in touch event first
|
||||
- ao_coreaudio: use device's nominal sample rate for latency properties
|
||||
- hwdec_drmprime: support yuv420p format
|
||||
- ao_pipewire: properly clean up resources
|
||||
- vo/{sixel,tct}: use the alternate buffer to restore terminal
|
||||
- vo_sixel: add option to skip clear while drawing
|
||||
- filter_kernels: fix kaiser
|
||||
- hwdec/d3d11va: fix a possible memory leak
|
||||
- external_files: set log level for potential files to trace
|
||||
- external_files: recognize webp files as cover art
|
||||
- vo_opengl: do not blindly reject all Microsoft's OpenGL implementations
|
||||
- ao_coreaudio: use AudioUnitReset as ao_driver.reset to prevent long restart
|
||||
- hwdec_drmprime: fix memory leak
|
||||
- vo: hwdec: fix libdrm-related memory leak
|
||||
- draw_bmp: ensure last slice width is less than total width
|
||||
- demux: boost read EBU R128 gain values to ReplayGain's reference level
|
||||
- version.py: bump copyright year
|
||||
- TOOLS/umpv: support shell-quotes in $MPV
|
||||
- vulkan: fix build error for 32bit builds with clang
|
||||
- TOOLS/lua/autoload: improve and optimize the natural sorting
|
||||
- vo_wlshm: properly support video panscan
|
||||
- vo_dmabuf_wayland: support panscan and panning
|
||||
- ra_d3d11: fix incorrect type
|
||||
- meson: also search for rst2html with .py extension
|
||||
- audio: fix crash during uninit with ao_lavc
|
||||
- wayland: only warn about GNOME when actually missing idle inhibit support
|
||||
- mp_image: fix XYZ primaries default
|
||||
- msg: preserve early messages when log-file is set in mpv.conf
|
||||
- x11: fix issue with xpresent timing feedback
|
||||
- meson: reuse libmpv objects for cplayer to save 50% of compile steps
|
||||
- player/client: support observing sub-properties
|
||||
- m_property: implement new deletion operation on properties
|
||||
- vo_lavc: set frame rate on encoder to fix non-conforming outputs
|
||||
- player/video: don't resync audio if video is an image
|
||||
- stream: accept webdav:// and webdavs:// urls
|
||||
- video/image_writer: avoid stripping colorspace info when writing image
|
||||
- stats.lua: display HDR peak in nits
|
||||
- image_pool: fix memory leak with frames
|
||||
- options: transition from OPT_FLAG to OPT_BOOL
|
||||
- vd_lavc: sort hwdecs without hwdevices last for autoprobing
|
||||
- ytdl_hook: don't overwrite force-media-title
|
||||
- lua: use user-data for interop between osc.lua and console.lua
|
||||
- vo: fix race condition with redraw requests
|
||||
- various: more fixes to ensure correct playback of XYZ colorspace
|
||||
- ytdl_hook: fix fragment-related issue that broke YouTube DASH playback
|
||||
- command: expose hls-bitrate and program-id as track-list subproperties
|
||||
- ao_pipewire: use realtime scheduling for data thread
|
||||
- auto_profiles: check for non-existent properties
|
||||
- d3d11: retry device creation without debug, if SDK is not available
|
||||
- win32: follow Windows settings and update dark mode state
|
||||
- ytdl_hook.lua: fix clip start and end
|
||||
- hwdec_drmprime: support custom rpi4_8 and rpi4_10 formats
|
||||
- sub: fix UPDATE_SUB_HARD for converted and external subtitles
|
||||
- charset_conv: fix memory corruption in mp_iconv_to_utf8
|
||||
- options: read config file as stream
|
||||
- wayland: improve guessing when mpv is focused
|
||||
- player: always try to detect subtitle language from file name
|
||||
- vo: fix mp_frame_perf thread safety
|
||||
- lua: read_options: find script-opts prefix at index 1 exactly
|
||||
- path: handle URLs consistently in mp_basename
|
||||
- ao_wasapi: remove infinite loop hack in AOCONTROL_UPDATE_STREAM_TITLE
|
||||
- ao_wasapi: use client name instead of hardcoded string
|
||||
- vd_lavc: fix crash if hwdec devices fail to create
|
||||
- vo_drm: fix null dereference and using closed fd
|
||||
- image_writer: respect jpeg-quality when using ffmpeg for writing
|
||||
- image_writer: add support for AVIF
|
||||
- TOOLS/lua/autoload: avoid unnecessary playlist manipulation, performance
|
||||
- various: correctly ignore cache files with --no-config
|
||||
- terminal-unix: better error detection logic
|
||||
- json: raise parse depth to 50 for the sake of utils.parse_json()
|
||||
- player: delete watch_later file only after successful load
|
||||
- wayland: fix memory leak with multiple monitors
|
||||
- vd_lavc: prefer d3d11va-copy over dxva2-copy
|
||||
- vd_lavc: fix hwdec for videos with less than less than 3 frames
|
||||
- wayland: add support for suspended toplevel state
|
||||
- draw_bmp: ensure last slice is less than total width (again)
|
||||
- console.lua: sort the output from the help command
|
||||
- vd_lavc: try other hwdecs when falling back after an hwdec failure
|
||||
- sd_ass: don't reconfigure ass on every frame (performance regression)
|
||||
- sd_ass: fix converted subtitles having too-wide borders
|
||||
- loadfile: compute audio language for sub selection when using lavfi-complex
|
||||
- wayland: fix modifier keys in certain situations
|
||||
- vd_lavc: corrections towards hwdec and swdec fallback
|
||||
- zimg: fix broken sig_peak (HDR) handling
|
||||
- screenshot: implement `screenshot window` in sw for most VOs
|
||||
- osc: don't add margins to osc-deadzonesize
|
||||
- vo_vaapi: fix segfault in draw_osd
|
||||
- vo_dmabuf_wayland: correctly handle force-window
|
||||
- ao_pipewire: fix error with pipewire 0.3.75 or later
|
||||
- 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.35.0..v0.36.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.35.0...v0.36.0 or
|
||||
https://git.srsfckn.biz/mpv/log/?qt=range&q=v0.35.0..v0.36.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
|
||||
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/bash
|
||||
set -e
|
||||
|
||||
export DEST_OS=win32
|
||||
export CC=gcc
|
||||
export PKG_CONFIG=/usr/bin/pkg-config
|
||||
export PERL=/usr/bin/perl
|
||||
export PYTHON=/usr/bin/python3
|
||||
|
||||
"$PYTHON" bootstrap.py
|
||||
"$PYTHON" waf configure \
|
||||
--check-c-compiler=gcc \
|
||||
--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 \
|
||||
--enable-uchardet \
|
||||
--enable-vulkan
|
||||
"$PYTHON" waf build
|
||||
Executable
+72
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/bash
|
||||
set -e
|
||||
|
||||
# Write an empty fonts.conf to speed up fc-cache
|
||||
export FONTCONFIG_FILE=/dummy-fonts.conf
|
||||
cat >"$FONTCONFIG_FILE" <<EOF
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
||||
<fontconfig></fontconfig>
|
||||
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-lua51 \
|
||||
$MINGW_PACKAGE_PREFIX-ninja \
|
||||
$MINGW_PACKAGE_PREFIX-rubberband \
|
||||
$MINGW_PACKAGE_PREFIX-uchardet \
|
||||
$MINGW_PACKAGE_PREFIX-vulkan
|
||||
|
||||
# Delete unused packages to reduce space used in the Appveyor cache
|
||||
pacman -Sc --noconfirm
|
||||
|
||||
# Compile ffmpeg
|
||||
(
|
||||
git clone --depth=1 https://github.com/FFmpeg/ffmpeg.git && cd ffmpeg
|
||||
|
||||
mkdir build && cd build
|
||||
../configure \
|
||||
--prefix=$MINGW_PREFIX \
|
||||
--target-os=mingw32 \
|
||||
--arch=$MSYSTEM_CARCH \
|
||||
--disable-static \
|
||||
--disable-doc \
|
||||
--disable-asm \
|
||||
--enable-gpl \
|
||||
--enable-version3 \
|
||||
--enable-shared \
|
||||
--enable-pic \
|
||||
--enable-d3d11va \
|
||||
--enable-dxva2 \
|
||||
--enable-schannel
|
||||
make -j4 install
|
||||
)
|
||||
|
||||
# Compile shaderc
|
||||
(
|
||||
git clone --depth=1 https://github.com/google/shaderc && cd shaderc
|
||||
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 crossc
|
||||
(
|
||||
git clone --depth=1 https://github.com/rossy/crossc && cd crossc
|
||||
git submodule update --init
|
||||
|
||||
make -j4 install prefix=$MINGW_PREFIX
|
||||
)
|
||||
@@ -1,67 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Wrapper around docutils rst2x commands,
|
||||
converting their dependency files to a format understood by meson/ninja.
|
||||
"""
|
||||
|
||||
#
|
||||
# This file is part of mpv.
|
||||
#
|
||||
# mpv is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2.1 of the License, or (at your option) any later version.
|
||||
#
|
||||
# mpv is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
def convert_depfile(output, depfile):
|
||||
with open(depfile, 'r') as f:
|
||||
deps = f.readlines()
|
||||
|
||||
with open(depfile, 'w') as f:
|
||||
f.write(os.path.abspath(output))
|
||||
f.write(': \\\n')
|
||||
for dep in deps:
|
||||
dep = dep[:-1]
|
||||
f.write('\t')
|
||||
f.write(os.path.abspath(dep))
|
||||
f.write(' \\\n')
|
||||
|
||||
def remove(path):
|
||||
try:
|
||||
os.remove(path)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
argv = sys.argv[1:]
|
||||
|
||||
depfile = None
|
||||
output = argv[-1]
|
||||
|
||||
for opt, optarg in zip(argv, argv[1:]):
|
||||
if opt == '--record-dependencies':
|
||||
depfile = optarg
|
||||
|
||||
try:
|
||||
proc = subprocess.run(argv, check=True)
|
||||
if depfile is not None:
|
||||
convert_depfile(output, depfile)
|
||||
except:
|
||||
remove(output)
|
||||
if depfile is not None:
|
||||
remove(depfile)
|
||||
sys.exit(1)
|
||||
|
||||
sys.exit(proc.returncode)
|
||||
+25
-61
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python
|
||||
|
||||
import re
|
||||
import os
|
||||
@@ -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()
|
||||
+10
-10
@@ -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,27 +22,27 @@
|
||||
# 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__":
|
||||
if len(sys.argv) < 2:
|
||||
outfile = sys.stdout
|
||||
else:
|
||||
outfile = open(sys.argv[2], "w")
|
||||
|
||||
with open(sys.argv[1], 'rb') as infile:
|
||||
file2string(sys.argv[1], infile, outfile)
|
||||
file2string(sys.argv[1], infile, sys.stdout)
|
||||
+1
-1
@@ -22,7 +22,7 @@
|
||||
testfun()
|
||||
{
|
||||
$ILDETECT_MPV "$@" \
|
||||
--vf-add=lavfi="[idet]" --msg-level=ffmpeg=v \
|
||||
--vf-add=lavfi="[idet]" --msg-level ffmpeg=v \
|
||||
--o= --vo=null --no-audio --untimed \
|
||||
$ILDETECT_MPVFLAGS \
|
||||
| { if [ -n "$ILDETECT_QUIET" ]; then cat; else tee /dev/stderr; fi } \
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
-- filter including key bindings for adjusting parameters.
|
||||
--
|
||||
-- See https://ffmpeg.org/ffmpeg-filters.html#acompressor for explanation
|
||||
-- of the parameters.
|
||||
-- of the parameteres.
|
||||
|
||||
local mp = require 'mp'
|
||||
local options = require 'mp.options'
|
||||
|
||||
+87
-296
@@ -1,323 +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.
|
||||
|
||||
suppress_osd: bool - Whether the OSD shouldn't be used when filters
|
||||
are applied and removed.
|
||||
--]]
|
||||
|
||||
-- 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,
|
||||
detect_seconds = 1,
|
||||
suppress_osd = false,
|
||||
}
|
||||
read_options(options)
|
||||
script_name = mp.get_script_name()
|
||||
cropdetect_label = string.format("%s-cropdetect", script_name)
|
||||
crop_label = string.format("%s-crop", script_name)
|
||||
|
||||
local label_prefix = mp.get_script_name()
|
||||
local labels = {
|
||||
crop = string.format("%s-crop", label_prefix),
|
||||
cropdetect = string.format("%s-cropdetect", label_prefix)
|
||||
}
|
||||
-- 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
|
||||
end
|
||||
|
||||
timers = {
|
||||
auto_delay = nil,
|
||||
detect_crop = nil
|
||||
}
|
||||
|
||||
local command_prefix = options.suppress_osd and 'no-osd' or ''
|
||||
|
||||
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(time_needed)
|
||||
if mp.get_property_native('current-tracks/video/image') ~= false then
|
||||
mp.msg.warn("autocrop only works for videos.")
|
||||
return false
|
||||
end
|
||||
|
||||
if not is_enough_time(time_needed) then
|
||||
mp.msg.warn("Not enough time to detect crop.")
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function remove_filter(label)
|
||||
if is_filter_present(label) then
|
||||
mp.command(string.format('%s vf remove @%s', command_prefix, 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()
|
||||
local time_needed = options.detect_seconds
|
||||
|
||||
if not is_cropable(time_needed) then
|
||||
function autocrop_start()
|
||||
-- exit if cropdetection is already in progress
|
||||
if timer then
|
||||
mp.msg.warn("already cropdetecting!")
|
||||
return
|
||||
end
|
||||
|
||||
-- Insert the cropdetect filter.
|
||||
local limit = options.detect_limit
|
||||
local round = options.detect_round
|
||||
-- if there's a crop filter, remove it and exit
|
||||
if del_filter_if_present(crop_label) then
|
||||
return
|
||||
end
|
||||
|
||||
mp.command(
|
||||
-- insert the cropdetect filter
|
||||
ret=mp.command(
|
||||
string.format(
|
||||
'%s vf pre @%s:cropdetect=limit=%s:round=%d:reset=0',
|
||||
command_prefix, 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("%s vf pre @%s:lavfi-crop=w=%s:h=%s:x=%s:y=%s",
|
||||
command_prefix, 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_cropable(time_needed) then
|
||||
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)
|
||||
+60
-138
@@ -2,42 +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
|
||||
additional_image_exts=list,of,ext
|
||||
additional_video_exts=list,of,ext
|
||||
additional_audio_exts=list,of,ext
|
||||
ignore_hidden=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,
|
||||
additional_image_exts = "",
|
||||
additional_video_exts = "",
|
||||
additional_audio_exts = "",
|
||||
ignore_hidden = true
|
||||
disabled = false
|
||||
}
|
||||
options.read_options(o)
|
||||
|
||||
@@ -47,48 +20,19 @@ 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
|
||||
|
||||
function Split (s)
|
||||
local set = {}
|
||||
for v in string.gmatch(s, '([^,]+)') do set[v] = true end
|
||||
return set
|
||||
end
|
||||
|
||||
EXTENSIONS_VIDEO = Set {
|
||||
'3g2', '3gp', 'avi', 'flv', 'm2ts', 'm4v', 'mj2', 'mkv', 'mov',
|
||||
'mp4', 'mpeg', 'mpg', 'ogv', 'rmvb', 'webm', 'wmv', 'y4m'
|
||||
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 {
|
||||
'aiff', 'ape', 'au', 'flac', 'm4a', 'mka', 'mp3', 'oga', 'ogg',
|
||||
'ogm', 'opus', 'wav', 'wma'
|
||||
}
|
||||
mputils = require 'mp.utils'
|
||||
|
||||
EXTENSIONS_IMAGES = Set {
|
||||
'avif', 'bmp', 'gif', 'j2k', 'jp2', 'jpeg', 'jpg', 'jxl', 'png',
|
||||
'svg', 'tga', 'tif', 'tiff', 'webp'
|
||||
}
|
||||
|
||||
EXTENSIONS_VIDEO = SetUnion(EXTENSIONS_VIDEO, Split(o.additional_video_exts))
|
||||
EXTENSIONS_AUDIO = SetUnion(EXTENSIONS_AUDIO, Split(o.additional_audio_exts))
|
||||
EXTENSIONS_IMAGES = SetUnion(EXTENSIONS_IMAGES, Split(o.additional_image_exts))
|
||||
|
||||
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
|
||||
|
||||
function add_files(files)
|
||||
function add_files_at(index, files)
|
||||
index = index - 1
|
||||
local oldcount = mp.get_property_number("playlist-count", 1)
|
||||
for i = 1, #files do
|
||||
mp.commandv("loadfile", files[i][1], "append")
|
||||
mp.commandv("playlist-move", oldcount + i - 1, files[i][2])
|
||||
mp.commandv("loadfile", files[i], "append")
|
||||
mp.commandv("playlist-move", oldcount + i - 1, index + i - 1)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -109,73 +53,57 @@ table.filter = function(t, iter)
|
||||
end
|
||||
end
|
||||
|
||||
-- alphanum sorting for humans in Lua
|
||||
-- http://notebook.kulchenko.com/algorithms/alphanumeric-natural-sorting-for-humans-in-lua
|
||||
-- splitbynum and alnumcomp from alphanum.lua (C) Andre Bogus
|
||||
-- Released under the MIT License
|
||||
-- http://www.davekoelle.com/files/alphanum.lua
|
||||
|
||||
function alphanumsort(filenames)
|
||||
local function padnum(n, d)
|
||||
return #d > 0 and ("%03d%s%.12f"):format(#n, n, tonumber(d) / (10 ^ #d))
|
||||
or ("%03d%s"):format(#n, n)
|
||||
-- split a string into a table of number and string values
|
||||
function splitbynum(s)
|
||||
local result = {}
|
||||
for x, y in (s or ""):gmatch("(%d*)(%D*)") do
|
||||
if x ~= "" then table.insert(result, tonumber(x)) end
|
||||
if y ~= "" then table.insert(result, y) end
|
||||
end
|
||||
|
||||
local tuples = {}
|
||||
for i, f in ipairs(filenames) do
|
||||
tuples[i] = {f:lower():gsub("0*(%d+)%.?(%d*)", padnum), f}
|
||||
end
|
||||
table.sort(tuples, function(a, b)
|
||||
return a[1] == b[1] and #b[2] < #a[2] or a[1] < b[1]
|
||||
end)
|
||||
for i, tuple in ipairs(tuples) do filenames[i] = tuple[2] end
|
||||
return filenames
|
||||
return result
|
||||
end
|
||||
|
||||
local autoloaded = nil
|
||||
function clean_key(k)
|
||||
k = (' '..k..' '):gsub("%s+", " "):sub(2, -2):lower()
|
||||
return splitbynum(k)
|
||||
end
|
||||
|
||||
function get_playlist_filenames(playlist)
|
||||
local filenames = {}
|
||||
for i = 1, #playlist do
|
||||
local _, file = utils.split_path(playlist[i].filename)
|
||||
filenames[file] = true
|
||||
-- compare two strings
|
||||
function alnumcomp(x, y)
|
||||
local xt, yt = clean_key(x), clean_key(y)
|
||||
for i = 1, math.min(#xt, #yt) do
|
||||
local xe, ye = xt[i], yt[i]
|
||||
if type(xe) == "string" then ye = tostring(ye)
|
||||
elseif type(ye) == "string" then xe = tostring(xe) end
|
||||
if xe ~= ye then return xe < ye end
|
||||
end
|
||||
return filenames
|
||||
return #xt < #yt
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
local pl_count = mp.get_property_number("playlist-count", 1)
|
||||
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)
|
||||
-- The current file could be a hidden file, ignoring it doesn't load other
|
||||
-- files from the current directory.
|
||||
if (o.ignore_hidden and not (v == filename) and string.match(v, "^%.")) then
|
||||
if string.match(v, "^%.") then
|
||||
return false
|
||||
end
|
||||
local ext = get_extension(v)
|
||||
@@ -184,12 +112,14 @@ function find_and_add_entries()
|
||||
end
|
||||
return EXTENSIONS[string.lower(ext)]
|
||||
end)
|
||||
alphanumsort(files)
|
||||
table.sort(files, alnumcomp)
|
||||
|
||||
if dir == "." then
|
||||
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
|
||||
@@ -201,46 +131,38 @@ function find_and_add_entries()
|
||||
if current == nil then
|
||||
return
|
||||
end
|
||||
msg.trace("current file position in files: "..current)
|
||||
|
||||
local append = {[-1] = {}, [1] = {}}
|
||||
local filenames = get_playlist_filenames(pl)
|
||||
for direction = -1, 1, 2 do -- 2 iterations, with direction = -1 and +1
|
||||
for i = 1, MAXENTRIES do
|
||||
local pos = current + i * direction
|
||||
local file = files[pos]
|
||||
local file = files[current + i * direction]
|
||||
local pl_e = pl[pl_current + i * direction]
|
||||
if file == nil or file[1] == "." then
|
||||
break
|
||||
end
|
||||
|
||||
local filepath = dir .. file
|
||||
-- skip files already in playlist
|
||||
if not filenames[file] then
|
||||
if direction == -1 then
|
||||
msg.info("Prepending " .. file)
|
||||
table.insert(append[-1], 1, {filepath, pos - 1})
|
||||
else
|
||||
msg.info("Adding " .. file)
|
||||
if pl_count > 1 then
|
||||
table.insert(append[1], {filepath, pos - 1})
|
||||
else
|
||||
mp.commandv("loadfile", filepath, "append")
|
||||
end
|
||||
if pl_e then
|
||||
-- If there's a playlist entry, and it's the same file, stop.
|
||||
if pl_e.filename == filepath then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if pl_count == 1 and direction == -1 and #append[-1] > 0 then
|
||||
for i = 1, #append[-1] do
|
||||
mp.commandv("loadfile", append[-1][i][1], "append")
|
||||
|
||||
if direction == -1 then
|
||||
if pl_current == 1 then -- never add additional entries in the middle
|
||||
mp.msg.info("Prepending " .. file)
|
||||
table.insert(append[-1], 1, filepath)
|
||||
end
|
||||
else
|
||||
mp.msg.info("Adding " .. file)
|
||||
table.insert(append[1], filepath)
|
||||
end
|
||||
mp.commandv("playlist-move", 0, current)
|
||||
end
|
||||
end
|
||||
|
||||
if pl_count > 1 then
|
||||
add_files(append[1])
|
||||
add_files(append[-1])
|
||||
end
|
||||
add_files_at(pl_current + 1, append[1])
|
||||
add_files_at(pl_current, append[-1])
|
||||
end
|
||||
|
||||
mp.register_event("start-file", find_and_add_entries)
|
||||
@@ -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
|
||||
@@ -1,68 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Logic copied from compiler_swift.py in the waf build. This checks for the sdk
|
||||
# path, the sdk version, and the sdk build version. The sdk path is returned
|
||||
# along with what is selected as the sdk version.
|
||||
|
||||
import re
|
||||
import os
|
||||
import string
|
||||
import sys
|
||||
from distutils.version import StrictVersion
|
||||
from shutil import which
|
||||
from subprocess import check_output
|
||||
|
||||
def find_macos_sdk():
|
||||
sdk = os.environ.get('MACOS_SDK', '')
|
||||
sdk_version = os.environ.get('MACOS_SDK_VERSION', '0.0')
|
||||
xcrun = which('xcrun')
|
||||
|
||||
if not xcrun:
|
||||
return sdk,sdk_version
|
||||
|
||||
if not sdk:
|
||||
sdk = check_output([xcrun, '--sdk', 'macosx', '--show-sdk-path'],
|
||||
encoding="UTF-8")
|
||||
|
||||
# find macOS SDK paths and version
|
||||
if sdk_version == '0.0':
|
||||
# show-sdk-build-version: is not available on older command line tools, but returns a build version (eg 17A360)
|
||||
# show-sdk-version: is always available, but on older dev tools it's only the major version
|
||||
sdk_build_version = check_output([xcrun, '--sdk', 'macosx',
|
||||
'--show-sdk-build-version'], encoding="UTF-8")
|
||||
|
||||
sdk_version = check_output([xcrun, '--sdk', 'macosx', '--show-sdk-version'],
|
||||
encoding="UTF-8")
|
||||
|
||||
if sdk:
|
||||
build_version = '10.10.0'
|
||||
|
||||
# convert build version to a version string
|
||||
# first 2 two digits are the major version, starting with 15 which is 10.11 (offset of 4)
|
||||
# 1 char is the minor version, A => 0, B => 1 and ongoing
|
||||
# last digits are bugfix version, which are not relevant for us
|
||||
# eg 16E185 => 10.12.4, 17A360 => 10.13, 18B71 => 10.14.1
|
||||
if sdk_build_version and isinstance(sdk_build_version, str):
|
||||
verRe = re.compile("(\d+)(\D+)(\d+)")
|
||||
version_parts = verRe.search(sdk_build_version)
|
||||
major = int(version_parts.group(1)) - 4
|
||||
minor = string.ascii_lowercase.index(version_parts.group(2).lower())
|
||||
build_version = '10.' + str(major) + '.' + str(minor)
|
||||
# from 20 onwards macOS 11.0 starts
|
||||
if int(version_parts.group(1)) >= 20:
|
||||
build_version = '11.' + str(minor)
|
||||
|
||||
if not isinstance(sdk_version, str):
|
||||
sdk_version = '10.10.0'
|
||||
|
||||
# pick the higher version, always pick sdk over build if newer
|
||||
if StrictVersion(build_version) > StrictVersion(sdk_version):
|
||||
return sdk,build_version
|
||||
else:
|
||||
return sdk,sdk_version
|
||||
|
||||
return sdk,sdk_version
|
||||
|
||||
if __name__ == "__main__":
|
||||
sdk_info = find_macos_sdk()
|
||||
sys.stdout.write(','.join(sdk_info))
|
||||
@@ -1,42 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Finds the macos swift library directory and prints the full path to stdout.
|
||||
# First argument is the path to the swift executable.
|
||||
|
||||
import os
|
||||
import sys
|
||||
from shutil import which
|
||||
from subprocess import check_output
|
||||
|
||||
def find_swift_lib():
|
||||
swift_lib_dir = os.environ.get('SWIFT_LIB_DYNAMIC', '')
|
||||
if swift_lib_dir:
|
||||
return swift_lib_dir
|
||||
|
||||
# first check for lib dir relative to swift executable
|
||||
xcode_dir = os.path.dirname(os.path.dirname(sys.argv[1]))
|
||||
swift_lib_dir = os.path.join(xcode_dir, "lib", "swift", "macosx")
|
||||
|
||||
if os.path.isdir(swift_lib_dir):
|
||||
return swift_lib_dir
|
||||
|
||||
# fallback to xcode-select path
|
||||
xcode_select = which("xcode-select")
|
||||
if not xcode_select:
|
||||
sys.exit(1)
|
||||
|
||||
xcode_path = check_output([xcode_select, "-p"], encoding="UTF-8")
|
||||
|
||||
swift_lib_dir = os.path.join(xcode_path, "Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx")
|
||||
if os.path.isdir(swift_lib_dir):
|
||||
return swift_lib_dir
|
||||
|
||||
# last resort if we still haven't found a path
|
||||
swift_lib_dir = os.path.join(xcode_path, "usr/lib/swift/macosx")
|
||||
if not os.path.isdir(swift_lib_dir):
|
||||
sys.exit(1)
|
||||
return swift_lib_dir
|
||||
|
||||
if __name__ == "__main__":
|
||||
swift_lib_dir = find_swift_lib()
|
||||
sys.stdout.write(swift_lib_dir)
|
||||
+10
-13
@@ -21,6 +21,8 @@ Can also be used to directly parse Matroska files and display their contents.
|
||||
# License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
# for compatibility with Python 2.x
|
||||
from __future__ import print_function
|
||||
|
||||
elements_ebml = (
|
||||
'EBML, 1a45dfa3, sub', (
|
||||
@@ -211,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',
|
||||
@@ -220,8 +221,7 @@ elements_matroska = (
|
||||
'SimpleTag*, 67c8, sub', (
|
||||
'TagName, 45a3, str',
|
||||
'TagLanguage, 447a, str',
|
||||
'TagString, 4487, str',
|
||||
'TagDefault, 4484, uint',
|
||||
'TagString, 4487, str'
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -274,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 = []
|
||||
@@ -298,8 +298,8 @@ parse_elems(elements_ebml, 'EBML')
|
||||
parse_elems(elements_matroska, 'MATROSKA')
|
||||
|
||||
def printf(out, *args):
|
||||
out.write(' '.join(str(x) for x in args))
|
||||
out.write('\n')
|
||||
out.write(u' '.join([str(x) for x in args]))
|
||||
out.write(u'\n')
|
||||
|
||||
def generate_C_header(out):
|
||||
printf(out, '// Generated by TOOLS/matroska.py, do not edit manually')
|
||||
@@ -459,16 +459,13 @@ if __name__ == "__main__":
|
||||
def parse_toplevel(s):
|
||||
parse_one(s, 0, None, 1 << 63)
|
||||
|
||||
if len(sys.argv) < 3:
|
||||
outfile = sys.stdout
|
||||
else:
|
||||
outfile = open(sys.argv[2], "w")
|
||||
|
||||
if sys.argv[1] == '--generate-header':
|
||||
generate_C_header(outfile)
|
||||
generate_C_header(sys.stdout)
|
||||
elif sys.argv[1] == '--generate-definitions':
|
||||
generate_C_definitions(outfile)
|
||||
generate_C_definitions(sys.stdout)
|
||||
else:
|
||||
if sys.version_info.major < 3:
|
||||
raise Exception("Dumping requires Python 3.")
|
||||
s = open(sys.argv[1], "rb")
|
||||
while 1:
|
||||
start = s.tell()
|
||||
|
||||
@@ -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
|
||||
+5
-4
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
@@ -6,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
|
||||
@@ -36,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),
|
||||
@@ -79,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.")
|
||||
|
||||
|
||||
@@ -188,11 +188,6 @@
|
||||
<string>${VERSION}</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true/>
|
||||
<key>LSEnvironment</key>
|
||||
<dict>
|
||||
<key>MallocNanoZone</key>
|
||||
<string>0</string>
|
||||
</dict>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
@@ -222,8 +217,6 @@
|
||||
<string>ftp</string>
|
||||
<string>udp</string>
|
||||
<string>smb</string>
|
||||
<string>srt</string>
|
||||
<string>rist</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:
|
||||
|
||||
Executable
+139
@@ -0,0 +1,139 @@
|
||||
#!/usr/bin/ruby
|
||||
|
||||
class TravisDepsBuilder
|
||||
def self.make(name)
|
||||
instance = klass.new(name)
|
||||
instance.fill_data
|
||||
instance.deps
|
||||
instance.build
|
||||
end
|
||||
|
||||
def self.klass
|
||||
Module.const_get([self.name, self.os.capitalize].join)
|
||||
rescue NameError
|
||||
self
|
||||
end
|
||||
|
||||
def self.os
|
||||
ENV['TRAVIS_OS_NAME']
|
||||
end
|
||||
|
||||
attr_reader :name, :url, :action, :os
|
||||
|
||||
def initialize(name)
|
||||
@name, @os = name, self.class.os
|
||||
end
|
||||
|
||||
def fill_data
|
||||
data = build_map.fetch(name)
|
||||
@url, @action = data[:url], data[:action]
|
||||
end
|
||||
|
||||
def build
|
||||
send(action)
|
||||
end
|
||||
|
||||
def deps; end
|
||||
|
||||
private
|
||||
def package_manager_update
|
||||
# yes class variable, you wanna update only once across all instances
|
||||
@@updated ||= false
|
||||
return if @@updated
|
||||
sh({'linux' => 'sudo apt-get update -y', 'osx' => 'brew update'}[os])
|
||||
@@updated = true
|
||||
end
|
||||
|
||||
def package_install(*packages)
|
||||
cmd = {
|
||||
'linux' => 'sudo apt-get install %s -y',
|
||||
'osx' => 'brew install %s'
|
||||
}[os] % [packages.join(" ")]
|
||||
|
||||
sh cmd
|
||||
end
|
||||
|
||||
def git
|
||||
sh "git clone --depth=1 #{url} #{name}"
|
||||
compile name
|
||||
end
|
||||
|
||||
def stable
|
||||
filename = File.basename(url)
|
||||
sh "wget #{url}"
|
||||
sh "tar -xzvf #{filename}"
|
||||
dirname = File.basename(url, ".tar.gz" )
|
||||
compile dirname
|
||||
end
|
||||
|
||||
def package
|
||||
package_install(url)
|
||||
end
|
||||
|
||||
def compile(dirname)
|
||||
sh "cd #{dirname} && #{configure} && make && sudo make install"
|
||||
sh "cd $TRAVIS_BUILD_DIR"
|
||||
end
|
||||
|
||||
def configure
|
||||
"./configure"
|
||||
end
|
||||
|
||||
def sh(command)
|
||||
`#{command}`
|
||||
end
|
||||
end
|
||||
|
||||
class Libav < TravisDepsBuilder
|
||||
def build_map
|
||||
{
|
||||
"libav-git" => {
|
||||
:action => :git,
|
||||
:url => "git://git.libav.org/libav.git"
|
||||
},
|
||||
"ffmpeg-git" => {
|
||||
:action => :git,
|
||||
:url => "https://github.com/FFmpeg/FFmpeg.git"
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def configure
|
||||
[super, "--cc=#{ENV['CC']} --disable-asm"].join(" ")
|
||||
end
|
||||
end
|
||||
|
||||
class LibavOsx < Libav
|
||||
def build_map
|
||||
{
|
||||
"ffmpeg-git" => { :action => :package, :url => 'ffmpeg' },
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
class Libass < TravisDepsBuilder
|
||||
def build_map
|
||||
{
|
||||
"libass-stable" => {
|
||||
:action => :stable,
|
||||
:url => 'https://github.com/libass/libass/releases/download/0.12.1/libass-0.12.1.tar.gz'
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
class Dependencies < TravisDepsBuilder
|
||||
def deps
|
||||
packages = {
|
||||
'linux' => 'pkg-config fontconfig libfribidi-dev yasm libxinerama-dev libxrandr-dev',
|
||||
'osx' => 'pkg-config fontconfig freetype fribidi yasm'
|
||||
}
|
||||
package_manager_update
|
||||
package_install(packages.fetch(os))
|
||||
end
|
||||
end
|
||||
|
||||
Dependencies.new(:deps).deps
|
||||
|
||||
Libass.make(ARGV[0])
|
||||
Libav.make(ARGV[1])
|
||||
Executable
+29
@@ -0,0 +1,29 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ "x$LIBAV" != "xffmpeg-git" ] || [ "x$TRAVIS_OS_NAME" != "xlinux" ]; then
|
||||
# trigger build only on one of the matrix nodes
|
||||
exit;
|
||||
fi
|
||||
|
||||
if [ "x$TRAVIS_BRANCH" != "xmaster" ]; then
|
||||
# only rebuild website with pushes to master
|
||||
exit;
|
||||
fi
|
||||
|
||||
TRAVIS_COMMIT_RANGE=${TRAVIS_COMMIT_RANGE:-HEAD^..}
|
||||
if git diff --quiet --name-only --exit-code "$TRAVIS_COMMIT_RANGE" DOCS/ VERSION; then
|
||||
exit;
|
||||
fi
|
||||
|
||||
body='{
|
||||
"request": {
|
||||
"branch":"master"
|
||||
}}'
|
||||
|
||||
curl -s -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Accept: application/json" \
|
||||
-H "Travis-API-Version: 3" \
|
||||
-H "Authorization: token $WEBSITE_TRAVIS_TOKEN" \
|
||||
-d "$body" \
|
||||
https://api.travis-ci.org/repo/mpv-player%2Fmpv.io/requests
|
||||
+28
-18
@@ -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,11 +32,11 @@ 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
|
||||
import stat
|
||||
import string
|
||||
import shlex
|
||||
|
||||
files = sys.argv[1:]
|
||||
|
||||
@@ -51,36 +55,42 @@ def make_abs(filename):
|
||||
if not is_url(filename):
|
||||
return os.path.abspath(filename)
|
||||
return filename
|
||||
files = (make_abs(f) for f in files)
|
||||
files = [make_abs(f) for f in files]
|
||||
|
||||
SOCK = os.path.join(os.getenv("XDG_RUNTIME_DIR", 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 = shlex.split(os.getenv("MPV") or "mpv")
|
||||
opts.extend(["--no-terminal", "--force-window", "--input-ipc-server=" + SOCK,
|
||||
opts = (os.getenv("MPV") or "mpv").split()
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- appveyor
|
||||
|
||||
environment:
|
||||
global:
|
||||
PATH: C:\msys64\usr\bin;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem
|
||||
matrix:
|
||||
- MSYSTEM: MINGW64
|
||||
- MSYSTEM: MINGW32
|
||||
|
||||
shallow_clone: true
|
||||
test: off
|
||||
|
||||
install:
|
||||
# Update core packages
|
||||
- C:\msys64\usr\bin\pacman -Syyuu --noconfirm
|
||||
# Update non-core packages
|
||||
- C:\msys64\usr\bin\pacman -Suu --noconfirm
|
||||
# Install required MSYS2 packages
|
||||
- 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:
|
||||
- C:\msys64\usr\bin\bash -lc "cd \"$APPVEYOR_BUILD_FOLDER\" && exec ./TOOLS/appveyor-build.sh"
|
||||
on_failure:
|
||||
- type "%APPVEYOR_BUILD_FOLDER%\build\config.log"
|
||||
|
||||
cache:
|
||||
- C:\msys64\var\cache\pacman\pkg
|
||||
@@ -20,29 +20,52 @@
|
||||
#include <libavutil/samplefmt.h>
|
||||
#include <libavutil/channel_layout.h>
|
||||
#include <libavutil/mathematics.h>
|
||||
#include <libswresample/swresample.h>
|
||||
|
||||
#include "audio/aframe.h"
|
||||
#include "audio/fmt-conversion.h"
|
||||
#include "audio/format.h"
|
||||
#include "config.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];
|
||||
@@ -57,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_BOOL(linear)},
|
||||
{"audio-resample-cutoff", OPT_DOUBLE(cutoff), M_RANGE(0, 1)},
|
||||
{"audio-normalize-downmix", OPT_BOOL(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);
|
||||
@@ -120,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++) {
|
||||
@@ -155,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;
|
||||
|
||||
@@ -179,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);
|
||||
@@ -194,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;
|
||||
@@ -294,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;
|
||||
}
|
||||
@@ -306,23 +332,67 @@ 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)
|
||||
{
|
||||
int format = af_fmt_from_planar(mp_aframe_get_format(mpa));
|
||||
int num_planes = mp_aframe_get_planes(mpa);
|
||||
uint8_t **planes = mp_aframe_get_data_rw(mpa);
|
||||
if (!planes)
|
||||
return;
|
||||
for (int p = 0; p < num_planes; p++) {
|
||||
void *ptr = planes[p];
|
||||
int total = mp_aframe_get_total_plane_samples(mpa);
|
||||
if (format == AF_FORMAT_FLOAT) {
|
||||
for (int s = 0; s < total; s++)
|
||||
((float *)ptr)[s] = av_clipf(((float *)ptr)[s], -1.0f, 1.0f);
|
||||
} else if (format == AF_FORMAT_DOUBLE) {
|
||||
for (int s = 0; s < total; s++)
|
||||
((double *)ptr)[s] = MPCLAMP(((double *)ptr)[s], -1.0, 1.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This relies on the tricky way mpa was allocated.
|
||||
@@ -332,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);
|
||||
@@ -361,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)
|
||||
@@ -401,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);
|
||||
@@ -422,148 +484,53 @@ 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)
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (in) {
|
||||
extra_output_conversion(out);
|
||||
|
||||
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
|
||||
@@ -571,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);
|
||||
+21
-274
@@ -18,12 +18,9 @@
|
||||
#include <libavutil/frame.h>
|
||||
#include <libavutil/mem.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "common/common.h"
|
||||
|
||||
#include "chmap.h"
|
||||
#include "chmap_avchannel.h"
|
||||
#include "fmt-conversion.h"
|
||||
#include "format.h"
|
||||
#include "aframe.h"
|
||||
@@ -35,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)
|
||||
@@ -51,10 +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();
|
||||
MP_HANDLE_OOM(frame->av_frame);
|
||||
if (!frame->av_frame)
|
||||
abort();
|
||||
talloc_set_destructor(frame, free_frame);
|
||||
mp_aframe_reset(frame);
|
||||
return frame;
|
||||
}
|
||||
|
||||
@@ -68,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)
|
||||
@@ -88,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.
|
||||
@@ -102,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.
|
||||
@@ -123,16 +101,6 @@ struct mp_aframe *mp_aframe_from_avframe(struct AVFrame *av_frame)
|
||||
if (!av_frame || av_frame->width > 0 || av_frame->height > 0)
|
||||
return NULL;
|
||||
|
||||
#if HAVE_AV_CHANNEL_LAYOUT
|
||||
if (!av_channel_layout_check(&av_frame->ch_layout))
|
||||
return NULL;
|
||||
|
||||
struct mp_chmap converted_map = { 0 };
|
||||
if (!mp_chmap_from_av_layout(&converted_map, &av_frame->ch_layout)) {
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
int format = af_from_avformat(av_frame->format);
|
||||
if (!format && av_frame->format != AV_SAMPLE_FMT_NONE)
|
||||
return NULL;
|
||||
@@ -144,21 +112,14 @@ struct mp_aframe *mp_aframe_from_avframe(struct AVFrame *av_frame)
|
||||
abort();
|
||||
|
||||
frame->format = format;
|
||||
#if !HAVE_AV_CHANNEL_LAYOUT
|
||||
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);
|
||||
#else
|
||||
frame->chmap = converted_map;
|
||||
#endif
|
||||
|
||||
if (av_frame->opaque_ref) {
|
||||
struct avframe_opaque *op = (void *)av_frame->opaque_ref->data;
|
||||
frame->speed = op->speed;
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
@@ -176,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);
|
||||
}
|
||||
|
||||
@@ -220,15 +171,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;
|
||||
|
||||
#if !HAVE_AV_CHANNEL_LAYOUT
|
||||
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;
|
||||
#else
|
||||
if (av_channel_layout_copy(&dst->av_frame->ch_layout,
|
||||
&src->av_frame->ch_layout) < 0)
|
||||
abort();
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -237,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;
|
||||
|
||||
@@ -338,20 +283,14 @@ bool mp_aframe_set_chmap(struct mp_aframe *frame, struct mp_chmap *in)
|
||||
return false;
|
||||
if (mp_aframe_is_allocated(frame) && in->num != frame->chmap.num)
|
||||
return false;
|
||||
|
||||
#if !HAVE_AV_CHANNEL_LAYOUT
|
||||
uint64_t lavc_layout = mp_chmap_to_lavc_unchecked(in);
|
||||
if (!lavc_layout && in->num)
|
||||
return false;
|
||||
#endif
|
||||
frame->chmap = *in;
|
||||
|
||||
#if !HAVE_AV_CHANNEL_LAYOUT
|
||||
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;
|
||||
#else
|
||||
mp_chmap_to_av_layout(&frame->av_frame->ch_layout, in);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
@@ -377,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)
|
||||
{
|
||||
@@ -431,96 +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);
|
||||
}
|
||||
|
||||
// sanitize a floating point sample value
|
||||
#define sanitizef(f) do { \
|
||||
if (!isnormal(f)) \
|
||||
(f) = 0; \
|
||||
} while (0)
|
||||
|
||||
void mp_aframe_sanitize_float(struct mp_aframe *mpa)
|
||||
{
|
||||
int format = af_fmt_from_planar(mp_aframe_get_format(mpa));
|
||||
if (format != AF_FORMAT_FLOAT && format != AF_FORMAT_DOUBLE)
|
||||
return;
|
||||
int num_planes = mp_aframe_get_planes(mpa);
|
||||
uint8_t **planes = mp_aframe_get_data_rw(mpa);
|
||||
if (!planes)
|
||||
return;
|
||||
for (int p = 0; p < num_planes; p++) {
|
||||
void *ptr = planes[p];
|
||||
int total = mp_aframe_get_total_plane_samples(mpa);
|
||||
switch (format) {
|
||||
case AF_FORMAT_FLOAT:
|
||||
for (int s = 0; s < total; s++)
|
||||
sanitizef(((float *)ptr)[s]);
|
||||
break;
|
||||
case AF_FORMAT_DOUBLE:
|
||||
for (int s = 0; s < total; s++)
|
||||
sanitizef(((double *)ptr)[s]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
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) {
|
||||
@@ -528,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);
|
||||
}
|
||||
@@ -541,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);
|
||||
@@ -551,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;
|
||||
@@ -669,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))
|
||||
@@ -697,23 +451,16 @@ int mp_aframe_pool_allocate(struct mp_aframe_pool *pool, struct mp_aframe *frame
|
||||
AVFrame *av_frame = frame->av_frame;
|
||||
if (av_frame->extended_data != av_frame->data)
|
||||
av_freep(&av_frame->extended_data); // sigh
|
||||
if (planes > AV_NUM_DATA_POINTERS) {
|
||||
av_frame->extended_data =
|
||||
av_calloc(planes, sizeof(av_frame->extended_data[0]));
|
||||
MP_HANDLE_OOM(av_frame->extended_data);
|
||||
} else {
|
||||
av_frame->extended_data = av_frame->data;
|
||||
}
|
||||
av_frame->extended_data =
|
||||
av_mallocz_array(planes, sizeof(av_frame->extended_data[0]));
|
||||
if (!av_frame->extended_data)
|
||||
abort();
|
||||
av_frame->buf[0] = av_buffer_pool_get(pool->avpool);
|
||||
if (!av_frame->buf[0])
|
||||
return -1;
|
||||
av_frame->linesize[0] = samples * sstride;
|
||||
for (int n = 0; n < planes; n++)
|
||||
av_frame->extended_data[n] = av_frame->buf[0]->data + n * plane_size;
|
||||
if (planes > AV_NUM_DATA_POINTERS) {
|
||||
for (int n = 0; n < AV_NUM_DATA_POINTERS; n++)
|
||||
av_frame->data[n] = av_frame->extended_data[n];
|
||||
}
|
||||
av_frame->nb_samples = samples;
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -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,37 +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);
|
||||
void mp_aframe_sanitize_float(struct mp_aframe *f);
|
||||
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
|
||||
@@ -470,23 +470,6 @@ char *mp_chmap_to_str_hr_buf(char *buf, size_t buf_size, const struct mp_chmap *
|
||||
return mp_chmap_to_str_buf(buf, buf_size, &map);
|
||||
}
|
||||
|
||||
mp_ch_layout_tuple *mp_iterate_builtin_layouts(void **opaque)
|
||||
{
|
||||
uintptr_t i = (uintptr_t)*opaque;
|
||||
|
||||
if (i >= MP_ARRAY_SIZE(std_layout_names) ||
|
||||
!std_layout_names[i][0])
|
||||
return NULL;
|
||||
|
||||
*opaque = (void *)(i + 1);
|
||||
|
||||
if (std_layout_names[i][1][0] == '\0') {
|
||||
return mp_iterate_builtin_layouts(opaque);
|
||||
}
|
||||
|
||||
return &std_layout_names[i];
|
||||
}
|
||||
|
||||
void mp_chmap_print_help(struct mp_log *log)
|
||||
{
|
||||
mp_info(log, "Speakers:\n");
|
||||
|
||||
@@ -75,8 +75,6 @@ struct mp_chmap {
|
||||
uint8_t speaker[MP_NUM_CHANNELS];
|
||||
};
|
||||
|
||||
typedef const char * const (mp_ch_layout_tuple)[2];
|
||||
|
||||
#define MP_SP(speaker) MP_SPEAKER_ID_ ## speaker
|
||||
|
||||
#define MP_CHMAP2(a, b) \
|
||||
@@ -131,19 +129,6 @@ char *mp_chmap_to_str_hr_buf(char *buf, size_t buf_size, const struct mp_chmap *
|
||||
|
||||
bool mp_chmap_from_str(struct mp_chmap *dst, bstr src);
|
||||
|
||||
/**
|
||||
* Iterate over all built-in channel layouts which have mapped channels.
|
||||
*
|
||||
* @param opaque a pointer where the iteration state is stored. Must point
|
||||
* to nullptr to start the iteration.
|
||||
*
|
||||
* @return nullptr when the iteration is finished.
|
||||
* Otherwise a pointer to an array of two char pointers.
|
||||
* - [0] being the human-readable layout name.
|
||||
* - [1] being the string representation of the layout.
|
||||
*/
|
||||
mp_ch_layout_tuple *mp_iterate_builtin_layouts(void **opaque);
|
||||
|
||||
struct mp_log;
|
||||
void mp_chmap_print_help(struct mp_log *log);
|
||||
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <libavutil/channel_layout.h>
|
||||
|
||||
#include "chmap.h"
|
||||
#include "chmap_avchannel.h"
|
||||
|
||||
bool mp_chmap_from_av_layout(struct mp_chmap *dst, const AVChannelLayout *src)
|
||||
{
|
||||
*dst = (struct mp_chmap) {0};
|
||||
|
||||
switch (src->order) {
|
||||
case AV_CHANNEL_ORDER_UNSPEC:
|
||||
mp_chmap_from_channels(dst, src->nb_channels);
|
||||
return dst->num == src->nb_channels;
|
||||
case AV_CHANNEL_ORDER_NATIVE:
|
||||
mp_chmap_from_lavc(dst, src->u.mask);
|
||||
return dst->num == src->nb_channels;
|
||||
default:
|
||||
// TODO: handle custom layouts
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void mp_chmap_to_av_layout(AVChannelLayout *dst, const struct mp_chmap *src)
|
||||
{
|
||||
*dst = (AVChannelLayout){
|
||||
.order = AV_CHANNEL_ORDER_UNSPEC,
|
||||
.nb_channels = src->num,
|
||||
};
|
||||
|
||||
// TODO: handle custom layouts
|
||||
if (!mp_chmap_is_unknown(src)) {
|
||||
av_channel_layout_from_mask(dst, mp_chmap_to_lavc(src));
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libavutil/channel_layout.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "chmap.h"
|
||||
|
||||
#if HAVE_AV_CHANNEL_LAYOUT
|
||||
|
||||
bool mp_chmap_from_av_layout(struct mp_chmap *dst, const AVChannelLayout *src);
|
||||
|
||||
void mp_chmap_to_av_layout(AVChannelLayout *dst, const struct mp_chmap *src);
|
||||
|
||||
#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 */
|
||||
+82
-127
@@ -26,121 +26,105 @@
|
||||
#include <libavutil/common.h>
|
||||
#include <libavutil/intreadwrite.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "mpv_talloc.h"
|
||||
#include "audio/aframe.h"
|
||||
#include "audio/chmap_avchannel.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;
|
||||
AVPacket *avpkt;
|
||||
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;
|
||||
bool downmix;
|
||||
int downmix;
|
||||
int threads;
|
||||
char **avopts;
|
||||
};
|
||||
|
||||
const struct m_sub_options ad_lavc_conf = {
|
||||
.opts = (const m_option_t[]) {
|
||||
{"ac3drc", OPT_FLOAT(ac3drc), M_RANGE(0, 6)},
|
||||
{"downmix", OPT_BOOL(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 = 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;
|
||||
const AVCodec *lavc_codec;
|
||||
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);
|
||||
ctx->avctx = lavc_context;
|
||||
ctx->avframe = av_frame_alloc();
|
||||
ctx->avpkt = av_packet_alloc();
|
||||
MP_HANDLE_OOM(ctx->avctx && ctx->avframe && ctx->avpkt);
|
||||
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) {
|
||||
const struct mp_chmap *requested_layout =
|
||||
&mpopts->audio_output_channels.chmaps[0];
|
||||
#if !HAVE_AV_CHANNEL_LAYOUT
|
||||
lavc_context->request_channel_layout =
|
||||
mp_chmap_to_lavc(requested_layout);
|
||||
#else
|
||||
AVChannelLayout av_layout = { 0 };
|
||||
mp_chmap_to_av_layout(&av_layout, requested_layout);
|
||||
|
||||
// Always try to set requested output layout - currently only something
|
||||
// supported by AC3, MLP/TrueHD, DTS and the fdk-aac wrapper.
|
||||
av_opt_set_chlayout(lavc_context, "downmix", &av_layout,
|
||||
AV_OPT_SEARCH_CHILDREN);
|
||||
|
||||
av_channel_layout_uninit(&av_layout);
|
||||
#endif
|
||||
mp_chmap_to_lavc(&mpopts->audio_output_channels.chmaps[0]);
|
||||
}
|
||||
|
||||
// Always try to set - option only exists for AC3 at the moment
|
||||
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);
|
||||
@@ -148,36 +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);
|
||||
mp_free_av_packet(&ctx->avpkt);
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -188,15 +177,20 @@ static int send_packet(struct mp_filter *da, struct demux_packet *mpkt)
|
||||
if (mpkt && priv->next_pts == MP_NOPTS_VALUE)
|
||||
priv->next_pts = mpkt->pts;
|
||||
|
||||
mp_set_av_packet(priv->avpkt, mpkt, &priv->codec_timebase);
|
||||
AVPacket pkt;
|
||||
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;
|
||||
|
||||
int ret = avcodec_send_packet(avctx, mpkt ? priv->avpkt : NULL);
|
||||
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;
|
||||
@@ -206,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;
|
||||
@@ -236,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) {
|
||||
@@ -243,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.
|
||||
@@ -262,56 +257,11 @@ static int receive_frame(struct mp_filter *da, struct mp_frame *out)
|
||||
priv->trim_samples -= trim;
|
||||
}
|
||||
|
||||
// Strip possibly bogus float values like Infinity, NaN, denormalized
|
||||
mp_aframe_sanitize_float(mpframe);
|
||||
|
||||
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)
|
||||
@@ -319,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,
|
||||
};
|
||||
+89
-108
@@ -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
|
||||
|
||||
@@ -41,7 +36,6 @@ struct spdifContext {
|
||||
struct mp_log *log;
|
||||
enum AVCodecID codec_id;
|
||||
AVFormatContext *lavf_ctx;
|
||||
AVPacket *avpkt;
|
||||
int out_buffer_len;
|
||||
uint8_t out_buffer[OUTBUF_SIZE];
|
||||
bool need_close;
|
||||
@@ -49,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)
|
||||
@@ -68,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;
|
||||
@@ -81,12 +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;
|
||||
}
|
||||
mp_free_av_packet(&spdif_ctx->avpkt);
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -118,7 +126,7 @@ static void determine_codec_params(struct mp_filter *da, AVPacket *pkt,
|
||||
if (profile != FF_PROFILE_UNKNOWN || spdif_ctx->codec_id != AV_CODEC_ID_DTS)
|
||||
return;
|
||||
|
||||
const AVCodec *codec = avcodec_find_decoder(spdif_ctx->codec_id);
|
||||
AVCodec *codec = avcodec_find_decoder(spdif_ctx->codec_id);
|
||||
if (!codec)
|
||||
goto done;
|
||||
|
||||
@@ -149,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;
|
||||
|
||||
@@ -169,7 +177,8 @@ static int init_filter(struct mp_filter *da, AVPacket *pkt)
|
||||
goto fail;
|
||||
|
||||
void *buffer = av_mallocz(OUTBUF_SIZE);
|
||||
MP_HANDLE_OOM(buffer);
|
||||
if (!buffer)
|
||||
abort();
|
||||
lavf_ctx->pb = avio_alloc_context(buffer, OUTBUF_SIZE, 1, spdif_ctx, NULL,
|
||||
write_packet, NULL);
|
||||
if (!lavf_ctx->pb) {
|
||||
@@ -177,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)
|
||||
@@ -207,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;
|
||||
@@ -268,71 +271,85 @@ 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;
|
||||
|
||||
mp_set_av_packet(spdif_ctx->avpkt, mpkt, NULL);
|
||||
spdif_ctx->avpkt->pts = spdif_ctx->avpkt->dts = 0;
|
||||
double pts = spdif_ctx->queued_packet->pts;
|
||||
|
||||
AVPacket pkt;
|
||||
mp_set_av_packet(&pkt, spdif_ctx->queued_packet, NULL);
|
||||
pkt.pts = pkt.dts = 0;
|
||||
if (!spdif_ctx->lavf_ctx) {
|
||||
if (init_filter(da, spdif_ctx->avpkt) < 0)
|
||||
if (init_filter(da, &pkt) < 0)
|
||||
goto done;
|
||||
}
|
||||
spdif_ctx->out_buffer_len = 0;
|
||||
int ret = av_write_frame(spdif_ctx->lavf_ctx, spdif_ctx->avpkt);
|
||||
int ret = av_write_frame(spdif_ctx->lavf_ctx, &pkt);
|
||||
avio_flush(spdif_ctx->lavf_ctx->pb);
|
||||
if (ret < 0) {
|
||||
MP_ERR(da, "spdif mux error: '%s'\n", mp_strerror(AVUNERROR(ret)));
|
||||
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,53 +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;
|
||||
}
|
||||
|
||||
spdif_ctx->avpkt = av_packet_alloc();
|
||||
MP_HANDLE_OOM(spdif_ctx->avpkt);
|
||||
|
||||
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 */
|
||||
Loaded 100 of 738 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user