Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
349e437466 | ||
|
|
f47a055440 | ||
|
|
80c03b49bf | ||
|
|
e7fbc86d58 | ||
|
|
85a4557369 | ||
|
|
7c0a9b8ff7 | ||
|
|
f2bc9e4dd1 | ||
|
|
f8b85dd65d | ||
|
|
bde10d2771 | ||
|
|
22c1468b5a | ||
|
|
01843cf9e7 |
No files matched your search
@@ -8,8 +8,3 @@ charset = utf-8
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
max_line_length = 80
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[.git/COMMIT*]
|
||||
max_line_length = 72
|
||||
@@ -2,7 +2,7 @@
|
||||
name: 'Report a build Problem'
|
||||
about: 'Create a report for a build related Issue'
|
||||
title: ''
|
||||
labels: 'core:meson'
|
||||
labels: 'core:waf'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
@@ -24,7 +24,7 @@ differs from your own.
|
||||
|
||||
### Log file
|
||||
|
||||
Upload meson-logs/meson-log.txt or meson setup build output to https://0x0.st/ or attach
|
||||
Upload build/config.log or ./waf configure output to https://0x0.st/ or attach
|
||||
it to the github issue, and replace this text with a link to it.
|
||||
|
||||
The issue will be closed for ignoring the issue template.
|
||||
+67
-204
@@ -6,75 +6,40 @@ on:
|
||||
- master
|
||||
- ci
|
||||
- 'release/**'
|
||||
paths-ignore:
|
||||
- 'DOCS/**'
|
||||
- 'TOOLS/lua/**'
|
||||
pull_request:
|
||||
branches: [master]
|
||||
paths-ignore:
|
||||
- 'DOCS/**'
|
||||
- 'TOOLS/lua/**'
|
||||
|
||||
jobs:
|
||||
mingw:
|
||||
runs-on: ubuntu-22.04
|
||||
env:
|
||||
CCACHE_BASEDIR: ${{ github.workspace }}
|
||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||
CCACHE_MAXSIZE: 500M
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: [i686-w64-mingw32, x86_64-w64-mingw32]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Get time
|
||||
id: get_time
|
||||
run: echo "timestamp=`date +%s%N`" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v3
|
||||
# Increase -N suffix here to force full rebuild after changes
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ${{ matrix.target }}-${{ steps.get_time.outputs.timestamp }}
|
||||
restore-keys: ${{ matrix.target }}-
|
||||
path: mingw_prefix/
|
||||
key: "${{ matrix.target }}-1"
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo dpkg --add-architecture i386
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y autoconf automake pkg-config g++-mingw-w64 gcc-multilib python3-pip ninja-build nasm wine wine32 wine64 ccache
|
||||
sudo python3 -m pip install meson
|
||||
sudo apt-get install -y autoconf automake pkg-config g++-mingw-w64 gcc-multilib nasm yasm
|
||||
./bootstrap.py
|
||||
|
||||
- name: Build libraries
|
||||
- name: Build
|
||||
run: |
|
||||
./ci/build-mingw64.sh
|
||||
env:
|
||||
TARGET: ${{ matrix.target }}
|
||||
|
||||
- name: Build with meson
|
||||
id: build
|
||||
- name: Print configure log
|
||||
if: ${{ failure() }}
|
||||
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: Functional test
|
||||
id: tests
|
||||
run: |
|
||||
cd artifact && wine64 ./mpv.com -v --no-config
|
||||
env:
|
||||
WINEDEBUG: '+loaddll'
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: mpv-${{ matrix.target }}
|
||||
path: mpv-git-*.zip
|
||||
cat ./build/config.log
|
||||
|
||||
macos:
|
||||
runs-on: ${{ matrix.os }}
|
||||
@@ -82,211 +47,109 @@ jobs:
|
||||
matrix:
|
||||
cc:
|
||||
- "clang"
|
||||
cxx:
|
||||
- "clang++"
|
||||
os:
|
||||
- "macos-12"
|
||||
- "macos-13"
|
||||
- "macos-10.15"
|
||||
- "macos-11"
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Remove stray upstream python binary symlinks under /usr/local
|
||||
- name: Override Xcode 13.0 if it's the default toolchain
|
||||
run: |
|
||||
find /usr/local/bin -lname '*/Library/Frameworks/Python.framework/*' -delete -print
|
||||
brew unlink python && brew link --overwrite python
|
||||
XCODE_PATH="$(xcode-select -p)"
|
||||
case "${XCODE_PATH}" in
|
||||
*Xcode_13.0*)
|
||||
sudo xcode-select -s "/Applications/Xcode_13.1.app"
|
||||
echo "Updated Xcode path ${XCODE_PATH} -> $(xcode-select -p)"
|
||||
;;
|
||||
esac
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
brew update
|
||||
brew install autoconf automake pkg-config libtool python freetype fribidi little-cms2 lua@5.1 libass ffmpeg meson libplacebo
|
||||
brew install autoconf automake pkg-config libtool python freetype fribidi little-cms2 luajit libass ffmpeg
|
||||
|
||||
- name: Build with meson
|
||||
id: build
|
||||
- name: Build
|
||||
run: |
|
||||
./ci/build-macos.sh
|
||||
env:
|
||||
CC: "${{ matrix.cc }}"
|
||||
CXX: "${{ matrix.cxx }}"
|
||||
TRAVIS_OS_NAME: "${{ matrix.os }}"
|
||||
|
||||
- name: Print meson log
|
||||
if: ${{ failure() && steps.build.outcome == 'failure' }}
|
||||
- name: Print configure log
|
||||
if: ${{ 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
|
||||
cat ./build/config.log
|
||||
|
||||
linux:
|
||||
runs-on: "ubuntu-22.04"
|
||||
runs-on: "ubuntu-20.04"
|
||||
container:
|
||||
image: "registry.opensuse.org/home/mia/images/images/mpv-ci:stable-deps"
|
||||
image: "registry.cirno.systems/kiwi/containers/mpv-ci:stable-deps"
|
||||
# Disable seccomp until a container manager in GitHub recognizes
|
||||
# clone3() syscall,
|
||||
# <https://github.com/actions/virtual-environments/issues/3812>.
|
||||
options: --security-opt seccomp=unconfined
|
||||
env:
|
||||
CC: "${{ matrix.config.cc }}"
|
||||
CXX: "${{ matrix.config.cxx }}"
|
||||
CC: "${{ matrix.cc }}"
|
||||
strategy:
|
||||
matrix:
|
||||
config:
|
||||
- {
|
||||
cc: "gcc",
|
||||
cxx: "g++",
|
||||
}
|
||||
- {
|
||||
cc: "clang",
|
||||
cxx: "clang++",
|
||||
}
|
||||
cc:
|
||||
- "gcc"
|
||||
- "clang"
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
# workaround to avoid "fatal: unsafe repository" error
|
||||
git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||
./bootstrap.py
|
||||
|
||||
- name: Build with meson
|
||||
id: build
|
||||
- name: Build
|
||||
run: |
|
||||
./ci/build-tumbleweed.sh
|
||||
|
||||
- name: Print meson log
|
||||
if: ${{ failure() && steps.build.outcome == 'failure' }}
|
||||
- name: Print configure log
|
||||
if: ${{ 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
|
||||
cat ./build/config.log
|
||||
|
||||
freebsd:
|
||||
runs-on: ubuntu-latest # until https://github.com/actions/runner/issues/385
|
||||
timeout-minutes: 30 # avoid any weirdness with the VM
|
||||
runs-on: macos-10.15 # until https://github.com/actions/runner/issues/385
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v2
|
||||
- name: Test in FreeBSD VM
|
||||
uses: cross-platform-actions/action@v0.19.1
|
||||
uses: vmactions/freebsd-vm@v0.1.5 # aka FreeBSD 13.0
|
||||
with:
|
||||
operating_system: freebsd
|
||||
version: '13.2'
|
||||
run: |
|
||||
sudo pkg update
|
||||
sudo pkg install -y \
|
||||
alsa-lib \
|
||||
cmake \
|
||||
usesh: true
|
||||
prepare: |
|
||||
# Requested in ci/build-freebsd.sh
|
||||
pkg install -y \
|
||||
evdev-proto \
|
||||
ffmpeg \
|
||||
git \
|
||||
iconv \
|
||||
libplacebo \
|
||||
libxkbcommon \
|
||||
luajit \
|
||||
openal-soft \
|
||||
pkgconf \
|
||||
python3 \
|
||||
sdl2 \
|
||||
vulkan-headers \
|
||||
wayland-protocols \
|
||||
#
|
||||
# Optionally auto-enabled
|
||||
pkg install -y \
|
||||
alsa-lib \
|
||||
jackit \
|
||||
libXv \
|
||||
libarchive \
|
||||
libbluray \
|
||||
libcaca \
|
||||
libcdio-paranoia \
|
||||
libdvdnav \
|
||||
libplacebo \
|
||||
libXinerama \
|
||||
libxkbcommon \
|
||||
libxpresent \
|
||||
libXv \
|
||||
luajit \
|
||||
meson \
|
||||
mujs \
|
||||
openal-soft \
|
||||
pipewire \
|
||||
pkgconf \
|
||||
pulseaudio \
|
||||
python3 \
|
||||
rubberband \
|
||||
sekrit-twc-zimg \
|
||||
sdl2 \
|
||||
sndio \
|
||||
uchardet \
|
||||
v4l_compat \
|
||||
vulkan-headers \
|
||||
wayland-protocols
|
||||
./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:
|
||||
- name: Disable autocrlf
|
||||
shell: pwsh
|
||||
#
|
||||
run: |
|
||||
git config --global core.autocrlf false
|
||||
git config --global core.eol lf
|
||||
- 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
|
||||
meson:p
|
||||
ninja:p
|
||||
pkgconf:p
|
||||
python: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: |
|
||||
meson test -C build
|
||||
|
||||
- name: Print meson test log
|
||||
if: ${{ failure() && steps.tests.outcome == 'failure' }}
|
||||
run: |
|
||||
cat ./build/meson-logs/testlog.txt
|
||||
./ci/build-freebsd.sh
|
||||
@@ -1,58 +0,0 @@
|
||||
name: comment
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["build"]
|
||||
types: [completed]
|
||||
|
||||
jobs:
|
||||
pr_comment:
|
||||
if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
const { owner, repo } = context.repo;
|
||||
const run_id = ${{ github.event.workflow_run.id }};
|
||||
const pull_head_sha = '${{github.event.workflow_run.head_sha}}';
|
||||
|
||||
const issue_number = await(async () => {
|
||||
const pulls = await github.rest.pulls.list({ owner, repo });
|
||||
for await (const { data } of github.paginate.iterator(pulls)) {
|
||||
for (const pull of data) {
|
||||
if (pull.head.sha === pull_head_sha) {
|
||||
return pull.number;
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
if (issue_number) {
|
||||
core.info(`Using pull request ${issue_number}`);
|
||||
} else {
|
||||
return core.error(`No matching pull request found`);
|
||||
}
|
||||
|
||||
const { data: { artifacts } } = await github.rest.actions.listWorkflowRunArtifacts({ owner, repo, run_id });
|
||||
if (!artifacts.length) {
|
||||
return core.error(`No artifacts found`);
|
||||
}
|
||||
|
||||
let body = `Download the artifacts for this pull request:\n\n<details><summary>Windows</summary>\n`;
|
||||
for (const art of artifacts) {
|
||||
const art_link = `https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip`;
|
||||
if (art.name.includes('w64')) {
|
||||
body += `\n* [${art.name}](${art_link})`;
|
||||
}
|
||||
}
|
||||
body += `\n</details>`;
|
||||
|
||||
const { data: comments } = await github.rest.issues.listComments({ repo, owner, issue_number });
|
||||
const existing_comment = comments.find((c) => c.user.login === 'github-actions[bot]');
|
||||
if (existing_comment) {
|
||||
core.info(`Updating comment ${existing_comment.id}`);
|
||||
await github.rest.issues.updateComment({ repo, owner, comment_id: existing_comment.id, body });
|
||||
} else {
|
||||
core.info(`Creating a comment`);
|
||||
await github.rest.issues.createComment({ repo, owner, issue_number, body });
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
name: docs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- ci
|
||||
- 'release/**'
|
||||
paths:
|
||||
- 'DOCS/**'
|
||||
pull_request:
|
||||
branches: [master]
|
||||
paths:
|
||||
- 'DOCS/**'
|
||||
|
||||
jobs:
|
||||
check-docs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Docs
|
||||
run: |
|
||||
sudo apt-get install python3-docutils
|
||||
rst2man --strip-elements-with-class=contents --halt=2 ./DOCS/man/mpv.rst mpv.1
|
||||
@@ -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
|
||||
+38
-3
@@ -1,3 +1,38 @@
|
||||
/subprojects/*
|
||||
!/subprojects/packagefiles
|
||||
!/subprojects/*.wrap
|
||||
*.o
|
||||
*.a
|
||||
*.d
|
||||
*.exe
|
||||
*.pyc
|
||||
.depend
|
||||
|
||||
/config.h
|
||||
/config.mak
|
||||
/config.log
|
||||
/mpv
|
||||
/mpv.app
|
||||
/version.h
|
||||
/input/input_conf.h
|
||||
/tags
|
||||
/TAGS
|
||||
/video/out/x11_icon.inc
|
||||
/demux/ebml_defs.c
|
||||
/demux/ebml_types.h
|
||||
/sub/osd_font.h
|
||||
/player/lua/*.inc
|
||||
/player/builtin_conf.inc
|
||||
/DOCS/man/mpv.1
|
||||
/DOCS/man/mpv.aux
|
||||
/DOCS/man/mpv.log
|
||||
/DOCS/man/mpv.out
|
||||
/DOCS/man/mpv.pdf
|
||||
/DOCS/man/mpv.toc
|
||||
|
||||
/waf
|
||||
/waf-*
|
||||
/waf3-*
|
||||
/build
|
||||
/.waf*
|
||||
/.lock-waf_*
|
||||
|
||||
/old_build
|
||||
/Makefile
|
||||
@@ -4,7 +4,7 @@ mpv as a whole is licensed under the GNU General Public License GPL version 2
|
||||
or later (called GPLv2+ in this document, see LICENSE.GPL for full license
|
||||
text) by default, or the GNU Lesser General Public License LGPL version 2 or
|
||||
later (LGPLv2.1+ in this document, see LICENSE.LGPL for full license text) if
|
||||
built with the -Dgpl=false configure switch.
|
||||
built with the --enable-lgpl configure switch.
|
||||
|
||||
Most source files are LGPLv2.1+ or GPLv2+, but some files are available under
|
||||
more liberal licenses, such as BSD, MIT, ISC, and possibly others. Look at the
|
||||
@@ -31,11 +31,11 @@ contains the complete SVN and CVS history as well.
|
||||
Some libraries are GPLv2+ or GPLv3+ only. Building mpv with Samba support makes
|
||||
it GPLv3+.
|
||||
|
||||
mpv can be built as LGPLv2.1+ with the -Dgpl=false configure option. To add a
|
||||
LGPL mode to mpv, MPlayer code had to be relicensed from GPLv2+ to LGPLv2.1+ by
|
||||
asking the MPlayer authors for permission. Since permission could not be
|
||||
obtained from everyone, LGPL mode disables the following features, some of them
|
||||
quite central:
|
||||
mpv can be built as LGPLv2.1+ with the --enable-lgpl configure option. To add
|
||||
a LGPL mode to mpv, MPlayer code had to be relicensed from GPLv2+ to LGPLv2.1+
|
||||
by asking the MPlayer authors for permission. Since permission could not be
|
||||
obtained from everyone, LGPL mode disables the following features, some of
|
||||
them quite central:
|
||||
- Linux X11 video output
|
||||
- BSD audio output via OSS
|
||||
- NVIDIA/Linux hardware decoding (vdpau, although nvdec usually works)
|
||||
@@ -45,7 +45,7 @@ Some of these will be fixed in the future. The intended use for LGPL mode is
|
||||
with libmpv, and currently it's not recommended to build mpv CLI in LGPL mode
|
||||
at all.
|
||||
|
||||
The following files are still GPL only (-Dgpl=false disables them):
|
||||
The following files are still GPL only (--enable-lgpl disables them):
|
||||
|
||||
audio/out/ao_jack.c will stay GPL
|
||||
audio/out/ao_oss.c will stay GPL
|
||||
|
||||
@@ -32,17 +32,6 @@ API changes
|
||||
|
||||
::
|
||||
|
||||
--- mpv 0.37.0 ---
|
||||
2.2 - add mpv_time_ns()
|
||||
--- 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)
|
||||
@@ -65,6 +54,7 @@ API changes
|
||||
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
|
||||
@@ -82,6 +72,7 @@ API changes
|
||||
- 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
|
||||
|
||||
+33
-34
@@ -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
|
||||
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
|
||||
@@ -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,30 +132,37 @@ 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}
|
||||
```
|
||||
|
||||
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
|
||||
@@ -182,11 +177,13 @@ 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 +208,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
|
||||
+8
-9
@@ -140,7 +140,7 @@ Touching user-visible parts may require updating the mpv docs
|
||||
Code formatting
|
||||
---------------
|
||||
|
||||
mpv uses C11 with K&R formatting, with some exceptions.
|
||||
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).
|
||||
@@ -153,9 +153,9 @@ mpv uses C11 with K&R formatting, with some exceptions.
|
||||
some_function(a, b, c);
|
||||
}
|
||||
```
|
||||
- Break lines on 80 columns. There is a hard limit of 100 columns. You may ignore
|
||||
- Break lines on 80 columns. There is a hard limit of 85 columns. You may ignore
|
||||
this limit if there's a strong case that not breaking the line will increase
|
||||
readability. Going over 100 columns might provoke endless discussions about
|
||||
readability. Going over 85 columns might provoke endless discussions about
|
||||
whether such a limit is needed or not, so avoid it.
|
||||
- If the body of an if/for/while statement has more than 1 physical lines, then
|
||||
always add braces, even if they're technically redundant.
|
||||
@@ -224,16 +224,15 @@ New code must follow the following conventions:
|
||||
General coding
|
||||
--------------
|
||||
|
||||
- Use C11. Also freely make use of C11 features if it's appropriate, but do not
|
||||
use VLA and complex number types.
|
||||
- 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 C11 must be used.
|
||||
But in general, standard C99 must be used.
|
||||
- The same applies to libc functions. We have to be Windows-compatible too. Use
|
||||
functions guaranteed by C11 or POSIX only, unless your use is guarded by a
|
||||
configure check. Be mindful of MinGW-specifics since C11 support is not always
|
||||
guaranteed.
|
||||
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).
|
||||
- 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.
|
||||
|
||||
+37
-37
@@ -3,26 +3,26 @@ General usage
|
||||
|
||||
::
|
||||
|
||||
mpv infile --o=outfile [--of=outfileformat] [--ofopts=formatoptions] [--orawts] \
|
||||
mpv infile -o outfile [-of outfileformat] [-ofopts formatoptions] [-orawts] \
|
||||
[(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::
|
||||
@@ -48,7 +48,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,10 +62,10 @@ for.
|
||||
|
||||
Typical MPEG-4 Part 2 ("ASP", "DivX") encoding, AVI container::
|
||||
|
||||
mpv infile --o=outfile.avi \
|
||||
mpv infile -o outfile.avi \
|
||||
--vf=fps=25 \
|
||||
--ovc=mpeg4 --ovcopts=qscale=4 \
|
||||
--oac=libmp3lame --oacopts=b=128k
|
||||
-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
|
||||
@@ -73,22 +73,22 @@ The frame rate should ideally match the input (25 for PAL, 24000/1001 or
|
||||
|
||||
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 +111,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.
|
||||
@@ -133,19 +133,19 @@ 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
|
||||
* 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
|
||||
======================
|
||||
|
||||
@@ -26,187 +26,6 @@ Interface changes
|
||||
|
||||
::
|
||||
|
||||
--- mpv 0.37.0 ---
|
||||
- `--save-position-on-quit` and its associated commands now store state files
|
||||
in %LOCALAPPDATA% instead of %APPDATA% directory by default on Windows.
|
||||
- change `--subs-with-matching-audio` default from `no` to `yes`
|
||||
- change `--subs-fallback` default from `no` to `default`
|
||||
- add the `--hdr-peak-percentile` option
|
||||
- include `--hdr-peak-percentile` in the `gpu-hq` profile
|
||||
- change `--audiotrack-pcm-float` default from `no` to `yes`
|
||||
- add video-params/aspect-name
|
||||
- change type of `--sub-pos` to float
|
||||
- The remaining time printed in the terminal is now adjusted for speed by default.
|
||||
You can disable this with `--no-term-remaining-playtime`.
|
||||
- add `playlist-path` and `playlist/N/playlist-path` properties
|
||||
- add `--x11-wid-title` option
|
||||
- add `--libplacebo-opts` option
|
||||
- add `--audio-file-exts`, `--cover-art-auto-exts`, and `--sub-auto-exts`
|
||||
- change `slang` default back to NULL
|
||||
- remove special handling of the `auto` value from `--alang/slang/vlang` options
|
||||
- add `--subs-match-os-language` as a replacement for `--slang=auto`
|
||||
- add `always` option to `--subs-fallback-forced`
|
||||
- remove `auto` choice from `--sub-forced-only`
|
||||
- remove `auto-forced-only` property
|
||||
- rename `--sub-forced-only` to `--sub-forced-events-only`
|
||||
- remove `sub-forced-only-cur` property (`--sub-forced-events-only` is a replacement)
|
||||
- remove deprecated `video-aspect` property
|
||||
- add `--video-crop`
|
||||
- add `video-params/crop-[w,h,x,y]`
|
||||
- remove `--tone-mapping-mode`
|
||||
- change `--subs-fallback-forced` so that it works alongside `--slang`
|
||||
- add `--icc-3dlut-size=auto` and make it the default
|
||||
- add `--scale=ewa_lanczos4sharpest`
|
||||
- remove `--scale-wblur`, `--cscale-wblur`, `--dscale-wblur`, `--tscale-wblur`
|
||||
- remove `bcspline` filter (`bicubic` is now the same as `bcspline`)
|
||||
- rename `--cache-dir` and `--cache-unlink-files` to `--demuxer-cache-dir` and
|
||||
`--demuxer-cache-unlink-files`
|
||||
- enable `--correct-downscaling`, `--linear-downscaling`, `--sigmoid-upscaling`
|
||||
- `--cscale` defaults to `--scale` if not defined
|
||||
- change `--tscale` default to `oversample`
|
||||
- change `--dither-depth` to `auto`
|
||||
- deprecate `--profile=gpu-hq`, add `--profile=<fast|high-quality>`
|
||||
- change `--dscale` default to `hermite`
|
||||
- update defaults to `--hdr-peak-decay-rate=20`, `--hdr-scene-threshold-low=1.0`,
|
||||
`--hdr-scene-threshold-high=3.0`
|
||||
- update defaults to `--deband-threshold=48`, `--deband-grain=32`
|
||||
- add `--directory-mode=auto` and make it the default
|
||||
- remove deprecated `--profile=opengl-hq`
|
||||
- remove several legacy fallbacks for old deprecated options (now they will just
|
||||
error out like normal)
|
||||
- remove deprecated `drop-frame-count` and `vo-drop-frame-count` property aliases
|
||||
- remove the ability to write to the `display-fps` property (use `override-display-fps`
|
||||
instead)
|
||||
- writing the current value to playlist-pos will no longer restart playback (use
|
||||
`playlist-play-index` instead)
|
||||
- remove deprecated `--oaoffset`, `--oafirst`, `--ovoffset`, `--ovfirst`,
|
||||
`--demuxer-force-retry-on-eof`, `--fit-border` options
|
||||
- remove deprecated `--record-file` option
|
||||
- remove deprecated `--vf-defaults` and `--af-defaults` options
|
||||
- `--drm-connector` no longer allows selecting the card number (use `--drm-device`
|
||||
instead)
|
||||
- add `--title-bar` option
|
||||
- add `--window-corners` option
|
||||
- rename `--cdrom-device` to `--cdda-device`
|
||||
- remove `--scale-cutoff`, `--cscale-cutoff`, `--dscale-cutoff`, `--tscale-cutoff`
|
||||
- remove `--scaler-lut-size`
|
||||
- deprecate shared-script-properties (user-data is a replacement)
|
||||
- add `--backdrop-type` option
|
||||
- add `--window-affinity` option
|
||||
- `--config-dir` no longer forces cache and state files to also reside in there
|
||||
- deprecate `--demuxer-cue-codepage` in favor of `--metadata-codepage`
|
||||
- change the default of `metadata-codepage` to `auto`
|
||||
- add `playlist-next-playlist` and `playlist-prev-playlist` commands
|
||||
- change `video-codec` to show description or name, not both
|
||||
- deprecate `--cdda-toc-bias` option, offsets are always checked now
|
||||
- disable `--allow-delayed-peak-detect` by default
|
||||
- rename `--fps` to `--container-fps-override`
|
||||
- rename `--override-display-fps` to `--display-fps-override`
|
||||
- rename `--sub-ass-force-style` to `--sub-ass-style-overrides`
|
||||
- alias `--screenshot-directory` to `--screenshot-dir`
|
||||
- alias `--watch-later-directory` to `--watch-later-dir`
|
||||
- rename `--play-dir` to `--play-direction`
|
||||
- `--js-memory-report` is now used for enabling memory reporting for javascript
|
||||
scripts
|
||||
- drop support for `-del` syntax for list options
|
||||
- `--demuxer-hysteresis-secs` now respects `--cache-secs` and/or
|
||||
`--demuxer-readahead-secs` as well
|
||||
- add hdr metadata to `video-params` property
|
||||
- add `--target-gamut`
|
||||
- change the way display names are retrieved on macOS, usage of options and properties
|
||||
`--fs-screen-name`, `--screen-name` and `display-names` needs to be adjusted
|
||||
- remove OpenGL cocoa backend that was deprecated in 0.29
|
||||
- remove `border`, `fullscreen`, `ontop`, `osd-level` and `pause`
|
||||
from default `--watch-later-options`
|
||||
- add `video-*` and `secondary-sub-visibility` to default `--watch-later-options`
|
||||
--- 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
|
||||
|
||||
+17
-33
@@ -19,8 +19,8 @@ syntax is:
|
||||
The ``--vf`` description describes how libavfilter can be used and how to
|
||||
workaround deprecated mpv filters.
|
||||
|
||||
See ``--vf`` group of options for info on how ``--af-add``, ``--af-pre``,
|
||||
``--af-clr``, and possibly others work.
|
||||
See ``--vf`` group of options for info on how ``--af-defaults``, ``--af-add``,
|
||||
``--af-pre``, ``--af-del``, ``--af-clr``, and possibly others work.
|
||||
|
||||
Available filters are:
|
||||
|
||||
@@ -100,7 +100,7 @@ Available filters are:
|
||||
|
||||
``scaletempo[=option1:option2:...]``
|
||||
Scales audio tempo without altering pitch, optionally synced to playback
|
||||
speed.
|
||||
speed (default).
|
||||
|
||||
This works by playing 'stride' ms of audio at normal speed then consuming
|
||||
'stride*scale' ms of input audio. It pieces the strides together by
|
||||
@@ -116,8 +116,8 @@ Available filters are:
|
||||
cause noticeable skips at high scale amounts and an echo at low scale
|
||||
amounts. Very low values will alter pitch. Increasing improves
|
||||
performance. (default: 60)
|
||||
``overlap=<factor>``
|
||||
Factor of stride to overlap. Decreasing improves performance.
|
||||
``overlap=<percent>``
|
||||
Percentage of stride to overlap. Decreasing improves performance.
|
||||
(default: .20)
|
||||
``search=<amount>``
|
||||
Length in milliseconds to search for best overlap position. Decreasing
|
||||
@@ -161,17 +161,14 @@ Available filters are:
|
||||
Would play media at 1.2x normal speed, with audio at normal pitch.
|
||||
Changing playback speed would change pitch, leaving audio tempo at
|
||||
1.2x.
|
||||
|
||||
|
||||
``scaletempo2[=option1:option2:...]``
|
||||
Scales audio tempo without altering pitch.
|
||||
The algorithm is ported from chromium and uses the
|
||||
The algorithm is ported from chromium and uses the
|
||||
Waveform Similarity Overlap-and-add (WSOLA) method.
|
||||
It seems to achieves higher audio quality than scaletempo, and rubberband R2
|
||||
engine, or ``engine=faster``. This filter is inserted automatically if
|
||||
``audio-pitch-correction`` option is used (on by default) when the playback
|
||||
speed is changed.
|
||||
It seems to achieve a higher audio quality than scaletempo and rubberband.
|
||||
|
||||
By default, the ``search-interval`` and ``window-size`` parameters
|
||||
By default, the ``search-interval`` and ``window-size`` parameters
|
||||
have the same values as in chromium.
|
||||
|
||||
``min-speed=<speed>``
|
||||
@@ -179,33 +176,22 @@ Available filters are:
|
||||
|
||||
``max-speed=<speed>``
|
||||
Mute audio if the playback speed is above ``<speed>``
|
||||
and ``<speed> != 0``. (default: 8.0)
|
||||
and ``<speed> != 0``. (default: 4.0)
|
||||
|
||||
``search-interval=<amount>``
|
||||
Length in milliseconds to search for best overlap position. (default: 40)
|
||||
|
||||
Length in milliseconds to search for best overlap position. (default: 30)
|
||||
|
||||
``window-size=<amount>``
|
||||
Length in milliseconds of the overlap-and-add window. (default: 12)
|
||||
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 ``scaletempo2``, and will be used to adjust audio pitch
|
||||
when playing at speed different from normal. It can also be used to adjust
|
||||
audio pitch without changing playback speed.
|
||||
of ``scaletempo``, and will be used to adjust audio pitch when playing
|
||||
at speed different from normal. It can also be used to adjust audio pitch
|
||||
without changing playback speed.
|
||||
|
||||
``pitch-scale=<amount>``
|
||||
``<pitch-scale>``
|
||||
Sets the pitch scaling factor. Frequencies are multiplied by this value.
|
||||
(default: 1.0)
|
||||
|
||||
``engine=<faster|finer>``
|
||||
Select the core Rubberband engine to be used. There are two available:
|
||||
|
||||
:Faster: This is the Rubberband R2 engine. It uses significantly less
|
||||
CPU than the Finer (R3) engine.
|
||||
:Finer: This is the Rubberband R3 engine. This engine is only available
|
||||
with librubberband version 3 or newer. This produces significantly
|
||||
higher quality output, at the cost of higher CPU usage. (Default
|
||||
if available)
|
||||
|
||||
This filter has a number of additional sub-options. You can list them with
|
||||
``mpv --af=rubberband=help``. This will also show the default values
|
||||
@@ -213,8 +199,6 @@ Available filters 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
|
||||
Do note that certain options are only applicable to one of R2 (faster) and
|
||||
R3 (finer) engines.
|
||||
(The mapping of the mpv rubberband filter sub-option names and values to
|
||||
those of librubberband follows a simple pattern: ``"Option" + Name + Value``.)
|
||||
|
||||
|
||||
+6
-32
@@ -30,8 +30,7 @@ Available audio output drivers are:
|
||||
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.
|
||||
@@ -101,7 +100,7 @@ Available audio output drivers are:
|
||||
exclusive mode (bypasses the sound server).
|
||||
|
||||
``openal``
|
||||
OpenAL audio output driver.
|
||||
OpenAL audio output driver. This is broken and does not work.
|
||||
|
||||
``--openal-num-buffers=<2-128>``
|
||||
Specify the number of audio buffers to use. Lower values are better for
|
||||
@@ -113,7 +112,9 @@ Available audio output drivers are:
|
||||
|
||||
``--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.
|
||||
tinting the sound with ambisonics or HRTF.
|
||||
Channels are dropped when when they are not available as downmixing
|
||||
will be disabled. Default: no.
|
||||
|
||||
``pulse``
|
||||
PulseAudio audio output driver
|
||||
@@ -128,7 +129,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.
|
||||
|
||||
``--pulse-latency-hacks=<yes|no>``
|
||||
Enable hacks to workaround PulseAudio timing bugs (default: no). If
|
||||
@@ -145,27 +146,6 @@ Available audio output drivers are:
|
||||
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
|
||||
@@ -239,11 +219,5 @@ 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.
|
||||
|
||||
``sndio``
|
||||
Audio output to the OpenBSD sndio sound system
|
||||
|
||||
(Note: only supports mono, stereo, 4.0, 5.1 and 7.1 channel
|
||||
layouts.)
|
||||
|
||||
``wasapi``
|
||||
Audio output to the Windows Audio Session API.
|
||||
+24
-46
@@ -11,63 +11,63 @@ Keybindings
|
||||
\`
|
||||
Show the console.
|
||||
|
||||
ESC and Ctrl+[
|
||||
ESC
|
||||
Hide the console.
|
||||
|
||||
ENTER, Ctrl+j and Ctrl+m
|
||||
ENTER, Ctrl+J and Ctrl+M
|
||||
Run the typed command.
|
||||
|
||||
Shift+ENTER
|
||||
Type a literal newline character.
|
||||
|
||||
LEFT and Ctrl+b
|
||||
LEFT and Ctrl+B
|
||||
Move the cursor to the previous character.
|
||||
|
||||
RIGHT and Ctrl+f
|
||||
RIGHT and Ctrl+F
|
||||
Move the cursor to the next character.
|
||||
|
||||
Ctrl+LEFT and Alt+b
|
||||
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
|
||||
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
|
||||
HOME and Ctrl+A
|
||||
Move the cursor to the start of the current line.
|
||||
|
||||
END and Ctrl+e
|
||||
END and Ctrl+E
|
||||
Move the cursor to the end of the current line.
|
||||
|
||||
BACKSPACE and Ctrl+h
|
||||
BACKSPACE and Ctrl+H
|
||||
Delete the previous character.
|
||||
|
||||
Ctrl+d
|
||||
Ctrl+D
|
||||
Hide the console if the current line is empty, otherwise delete the next
|
||||
character.
|
||||
|
||||
Ctrl+BACKSPACE and Ctrl+w
|
||||
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
|
||||
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
|
||||
Ctrl+U
|
||||
Delete text from the cursor to the beginning of the current line.
|
||||
|
||||
Ctrl+k
|
||||
Ctrl+K
|
||||
Delete text from the cursor to the end of the current line.
|
||||
|
||||
Ctrl+c
|
||||
Ctrl+C
|
||||
Clear the current line.
|
||||
|
||||
UP and Ctrl+p
|
||||
UP and Ctrl+P
|
||||
Move back in the command history.
|
||||
|
||||
DOWN and Ctrl+n
|
||||
DOWN and Ctrl+N
|
||||
Move forward in the command history.
|
||||
|
||||
PGUP
|
||||
@@ -79,16 +79,16 @@ PGDN
|
||||
INSERT
|
||||
Toggle insert mode.
|
||||
|
||||
Ctrl+v
|
||||
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
|
||||
TAB and Ctrl+I
|
||||
Complete the command or property name at the cursor.
|
||||
|
||||
Ctrl+l
|
||||
Ctrl+L
|
||||
Clear all log messages from the console.
|
||||
|
||||
Commands
|
||||
@@ -99,13 +99,9 @@ Commands
|
||||
specifying the initial cursor position as a positive integer starting from
|
||||
1.
|
||||
|
||||
.. admonition:: Examples for input.conf
|
||||
.. admonition:: Example for input.conf
|
||||
|
||||
``% script-message-to console type "seek absolute-percent; keypress ESC" 6``
|
||||
Enter a percent position to seek to and close the console.
|
||||
|
||||
``Ctrl+o script-message-to console type "loadfile ''; keypress ESC" 11``
|
||||
Enter a file or URL to play. Tab completes paths in the filesystem.
|
||||
``% script-message-to console type "seek absolute-percent" 6``
|
||||
|
||||
Known issues
|
||||
------------
|
||||
@@ -139,29 +135,11 @@ Configurable Options
|
||||
``font``
|
||||
Default: unset (picks a hardcoded font depending on detected platform)
|
||||
|
||||
Set the font used for the REPL and the console.
|
||||
This has to be a monospaced font for the completion suggestions to be
|
||||
aligned correctly.
|
||||
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.
|
||||
multiplied by "scale."
|
||||
|
||||
``font_hw_ratio``
|
||||
Default: 2.0
|
||||
|
||||
The ratio of font height to font width.
|
||||
Adjusts table width of completion suggestions.
|
||||
@@ -28,6 +28,10 @@ You can encode files from one format/codec to another using this facility.
|
||||
Specifies the output audio codec. 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.
|
||||
|
||||
``--oacopts=<options>``
|
||||
Specifies the output audio codec options for libavcodec.
|
||||
See ``--oacopts=help`` for a full list of supported options.
|
||||
@@ -46,10 +50,18 @@ You can encode files from one format/codec to another using this facility.
|
||||
``--oacopts=""``
|
||||
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.
|
||||
|
||||
``--ovc=<codec>``
|
||||
Specifies the output video codec. 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.
|
||||
|
||||
``--ovcopts=<options>``
|
||||
Specifies the output video codec options for libavcodec.
|
||||
See --ovcopts=help for a full list of supported options.
|
||||
@@ -71,6 +83,10 @@ You can encode files from one format/codec to another using this facility.
|
||||
``--ovcopts=""``
|
||||
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.
|
||||
|
||||
``--orawts``
|
||||
Copies input pts to the output video (not supported by some output
|
||||
container formats, e.g. AVI). In this mode, discontinuities are not fixed
|
||||
|
||||
+83
-216
@@ -186,7 +186,7 @@ character at the value.
|
||||
|
||||
Custom quotes also take the content literally, but are more flexible than single
|
||||
quotes. They start with ````` (back-quote) followed by any ASCII character,
|
||||
and end at the first occurrence of the same pair in reverse order, e.g.
|
||||
and end at the first occurance of the same pair in reverse order, e.g.
|
||||
```-foo-``` or ````bar````. The final pair sequence is not allowed at the
|
||||
value - in these examples ``-``` and `````` respectively. In the second
|
||||
example the last character of the value also can't be a back-quote.
|
||||
@@ -334,9 +334,6 @@ Remember to quote string arguments in input.conf (see `Flat command syntax`_).
|
||||
``set <name> <value>``
|
||||
Set the given property or option to the given value.
|
||||
|
||||
``del <name>``
|
||||
Delete the given property. Most properties cannot be deleted.
|
||||
|
||||
``add <name> [<value>]``
|
||||
Add the given value to the property or option. On overflow or underflow,
|
||||
clamp the property to the maximum. If ``<value>`` is omitted, assume ``1``.
|
||||
@@ -369,7 +366,8 @@ Remember to quote string arguments in input.conf (see `Flat command syntax`_).
|
||||
behavior depends on the selected video output.
|
||||
<window>
|
||||
Save the contents of the mpv window. Typically scaled, with OSD and
|
||||
subtitles. The exact behavior depends on the selected video output.
|
||||
subtitles. The exact behavior depends on the selected video output, and
|
||||
if no support is available, this will act like ``video``.
|
||||
<each-frame>
|
||||
Take a screenshot each frame. Issue this command again to stop taking
|
||||
screenshots. Note that you should disable frame-dropping when using
|
||||
@@ -386,9 +384,6 @@ Remember to quote string arguments in input.conf (see `Flat command syntax`_).
|
||||
normal standalone commands, this is always asynchronous, and the flag has
|
||||
no effect. (This behavior changed with mpv 0.29.0.)
|
||||
|
||||
On success, returns a ``mpv_node`` with a ``filename`` field set to the
|
||||
saved screenshot location.
|
||||
|
||||
``screenshot-to-file <filename> <flags>``
|
||||
Take a screenshot and save it to a given file. The format of the file will
|
||||
be guessed by the extension (and ``--screenshot-format`` is ignored - the
|
||||
@@ -422,13 +417,6 @@ Remember to quote string arguments in input.conf (see `Flat command syntax`_).
|
||||
force
|
||||
Terminate playback if the first file is being played.
|
||||
|
||||
``playlist-next-playlist``
|
||||
Go to the next entry on the playlist with a different ``playlist-path``.
|
||||
|
||||
``playlist-prev-playlist``
|
||||
Go to the first of the previous entries on the playlist with a different
|
||||
``playlist-path``.
|
||||
|
||||
``playlist-play-index <integer|current|none>``
|
||||
Start (or restart) playback of the given playlist index. In addition to the
|
||||
0-based playlist entry index, it supports the following values:
|
||||
@@ -443,7 +431,7 @@ Remember to quote string arguments in input.conf (see `Flat command syntax`_).
|
||||
Playback is stopped. If idle mode (``--idle``) is enabled, the player
|
||||
will enter idle mode, otherwise it will exit.
|
||||
|
||||
This command is similar to ``loadfile`` in that it only manipulates the
|
||||
This comm and is similar to ``loadfile`` in that it only manipulates the
|
||||
state of what to play next, without waiting until the current file is
|
||||
unloaded, and the next one is loaded.
|
||||
|
||||
@@ -563,9 +551,9 @@ Remember to quote string arguments in input.conf (see `Flat command syntax`_).
|
||||
|
||||
``playback_only`` (``MPV_FORMAT_FLAG``)
|
||||
Boolean indicating whether the process should be killed when playback
|
||||
of the current playlist entry terminates (optional, default: true). If
|
||||
enabled, stopping playback will automatically kill the process, and you
|
||||
can't start it outside of playback.
|
||||
terminates (optional, default: true). If enabled, stopping playback
|
||||
will automatically kill the process, and you can't start it outside of
|
||||
playback.
|
||||
|
||||
``capture_size`` (``MPV_FORMAT_INT64``)
|
||||
Integer setting the maximum number of stdout plus stderr bytes that can
|
||||
@@ -593,7 +581,7 @@ Remember to quote string arguments in input.conf (see `Flat command syntax`_).
|
||||
instead. (Unlike the underlying OS mechanisms, the mpv command cannot
|
||||
start a process with empty environment. Fortunately, that is completely
|
||||
useless.) The format of the list is as in the ``execle()`` syscall. Each
|
||||
string item defines an environment variable as in ``NAME=VALUE``.
|
||||
string item defines an environment variable as in ``NANME=VALUE``.
|
||||
|
||||
On Lua, you may use ``utils.get_env_list()`` to retrieve the current
|
||||
environment if you e.g. simply want to add a new variable.
|
||||
@@ -612,14 +600,12 @@ Remember to quote string arguments in input.conf (see `Flat command syntax`_).
|
||||
The command returns the following result (as ``MPV_FORMAT_NODE_MAP``):
|
||||
|
||||
``status`` (``MPV_FORMAT_INT64``)
|
||||
Typically this is the process exit code (0 or positive) if the process
|
||||
terminates normally, or negative for other errors (failed to start,
|
||||
terminated by mpv, and others). The meaning of negative values is
|
||||
undefined, other than meaning error (and does not correspond to OS low
|
||||
level exit status values).
|
||||
The raw exit status of the process. It will be negative on error. The
|
||||
meaning of negative values is undefined, other than meaning error (and
|
||||
does not correspond to OS low level exit status values).
|
||||
|
||||
On Windows, it can happen that a negative return value is returned even
|
||||
if the process terminates normally, because the win32 ``UINT`` exit
|
||||
On Windows, it can happen that a negative return value is returned
|
||||
even if the process exits gracefully, because the win32 ``UINT`` exit
|
||||
code is assigned to an ``int`` variable before being set as ``int64_t``
|
||||
field in the result map. This might be fixed later.
|
||||
|
||||
@@ -630,9 +616,9 @@ Remember to quote string arguments in input.conf (see `Flat command syntax`_).
|
||||
Same as ``stdout``, but for stderr.
|
||||
|
||||
``error_string`` (``MPV_FORMAT_STRING``)
|
||||
Empty string if the process terminated normally. The string ``killed``
|
||||
if the process was terminated in an unusual way. The string ``init`` if
|
||||
the process could not be started.
|
||||
Empty string if the process exited gracefully. 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 ``playback_only`` being set to true.
|
||||
@@ -647,19 +633,17 @@ Remember to quote string arguments in input.conf (see `Flat command syntax`_).
|
||||
it was somehow killed or returned an error status has to be queried from
|
||||
the result value.
|
||||
|
||||
This command can be asynchronously aborted via API. Also see `Asynchronous
|
||||
command details`_. Only the ``run`` command can start processes in a truly
|
||||
detached way.
|
||||
This command can be asynchronously aborted via API.
|
||||
|
||||
.. note:: The subprocess will always be terminated on player exit if it
|
||||
wasn't started in detached mode, even if ``playback_only`` is
|
||||
false.
|
||||
In all cases, the subprocess will be terminated on player exit. Also see
|
||||
`Asynchronous command details`_. Only the ``run`` command can start
|
||||
processes in a truly detached way.
|
||||
|
||||
.. admonition:: Warning
|
||||
|
||||
Don't forget to set the ``playback_only`` field to false if you want
|
||||
the command to run while the player is in idle mode, or if you don't
|
||||
want the end of playback to kill the command.
|
||||
Don't forget to set the ``playback_only`` field if you want the command
|
||||
run while the player is in idle mode, or if you don't want that end of
|
||||
playback kills the command.
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
@@ -684,7 +668,7 @@ Remember to quote string arguments in input.conf (see `Flat command syntax`_).
|
||||
``quit-watch-later [<code>]``
|
||||
Exit player, and store current playback position. Playing that file later
|
||||
will seek to the previous position on start. The (optional) argument is
|
||||
exactly as in the ``quit`` command. See `RESUMING PLAYBACK`_.
|
||||
exactly as in the ``quit`` command.
|
||||
|
||||
``sub-add <url> [<flags> [<title> [<lang>]]]``
|
||||
Load the given subtitle file or stream. By default, it is selected as
|
||||
@@ -758,7 +742,7 @@ Remember to quote string arguments in input.conf (see `Flat command syntax`_).
|
||||
``show-text <text> [<duration>|-1 [<level>]]``
|
||||
Show text on the OSD. The string can contain properties, which are expanded
|
||||
as described in `Property Expansion`_. This can be used to show playback
|
||||
time, filename, and so on. ``no-osd`` has no effect on this command.
|
||||
time, filename, and so on.
|
||||
|
||||
<duration>
|
||||
The time in ms to show the message for. By default, it uses the same
|
||||
@@ -786,7 +770,7 @@ Remember to quote string arguments in input.conf (see `Flat command syntax`_).
|
||||
|
||||
``show-progress``
|
||||
Show the progress bar, the elapsed time and the total duration of the file
|
||||
on the OSD. ``no-osd`` has no effect on this command.
|
||||
on the OSD.
|
||||
|
||||
``write-watch-later-config``
|
||||
Write the resume config file that the ``quit-watch-later`` command writes,
|
||||
@@ -921,6 +905,13 @@ Input Commands that are Possibly Subject to Change
|
||||
<remove>
|
||||
Like ``toggle``, but always remove the given filter from the chain.
|
||||
|
||||
<del>
|
||||
Remove the given filters from the video chain. Unlike in the other
|
||||
cases, the second parameter is a comma separated list of filter names
|
||||
or integer indexes. ``0`` would denote the first filter. Negative
|
||||
indexes start from the last filter, and ``-1`` denotes the last
|
||||
filter. Deprecated, use ``remove``.
|
||||
|
||||
<clr>
|
||||
Remove all filters. Note that like the other sub-commands, this does
|
||||
not control automatically inserted filters.
|
||||
@@ -1096,7 +1087,7 @@ Input Commands that are Possibly Subject to Change
|
||||
Before mpv 0.18.1, you had to do manual "double buffering" when updating
|
||||
an overlay by replacing it with a different memory buffer. Since mpv
|
||||
0.18.1, the memory is simply copied and doesn't reference any of the
|
||||
memory indicated by the command's arguments after the command returns.
|
||||
memory indicated by the command's arguments after the commend returns.
|
||||
If you want to use this command before mpv 0.18.1, reads the old docs
|
||||
to see how to handle this correctly.
|
||||
|
||||
@@ -1296,21 +1287,16 @@ Input Commands that are Possibly Subject to Change
|
||||
The ``flags`` argument is like the first argument to ``screenshot`` and
|
||||
supports ``subtitles``, ``video``, ``window``.
|
||||
|
||||
``vf-command <label> <command> <argument> [<target>]``
|
||||
Send a command to the filter. Note that currently, this only works with
|
||||
the ``lavfi`` filter. Refer to the libavfilter documentation for the list
|
||||
of supported commands for each filter.
|
||||
``vf-command <label> <command> <argument>``
|
||||
Send a command to the filter with the given ``<label>``. Use ``all`` to send
|
||||
it to all filters at once. The command and argument string is filter
|
||||
specific. Currently, this only works with the ``lavfi`` filter - see
|
||||
the libavfilter documentation for which commands a filter supports.
|
||||
|
||||
``<label>`` is a mpv filter label, use ``all`` to send it to all filters
|
||||
at once.
|
||||
Note that the ``<label>`` is a mpv filter label, not a libavfilter filter
|
||||
name.
|
||||
|
||||
``<command>`` and ``<argument>`` are filter-specific strings.
|
||||
|
||||
``<target>`` is a filter or filter instance name and defaults to ``all``.
|
||||
Note that the target is an additional specifier for filters that
|
||||
support them, such as complex ``lavfi`` filter chains.
|
||||
|
||||
``af-command <label> <command> <argument> [<target>]``
|
||||
``af-command <label> <command> <argument>``
|
||||
Same as ``vf-command``, but for audio filters.
|
||||
|
||||
``apply-profile <name> [<mode>]``
|
||||
@@ -1393,7 +1379,7 @@ Input Commands that are Possibly Subject to Change
|
||||
|
||||
This was mostly created for network streams. For local files, there may
|
||||
be much better methods to create excerpts and such. There are tons of
|
||||
much more user-friendly Lua scripts, that will re-encode parts of a file
|
||||
much more user-friendly Lua scripts, that will reencode parts of a file
|
||||
by spawning a separate instance of ``ffmpeg``. With network streams,
|
||||
this is not that easily possible, as the stream would have to be
|
||||
downloaded again. Even if ``--stream-record`` is used to record the
|
||||
@@ -1433,8 +1419,8 @@ IPC sees. Note that the C API has separate C-level declarations with
|
||||
``mpv_event``, which may be slightly different.
|
||||
|
||||
Note that events are asynchronous: the player core continues running while
|
||||
events are delivered to scripts and other clients. In some cases, you can use
|
||||
hooks to enforce synchronous execution.
|
||||
events are delivered to scripts and other clients. In some cases, you can hooks
|
||||
to enforce synchronous execution.
|
||||
|
||||
All events can have the following fields:
|
||||
|
||||
@@ -1614,8 +1600,10 @@ This list uses the event name field value, and the C API symbol in brackets:
|
||||
``data``
|
||||
The new value of the property.
|
||||
|
||||
The following events also happen, but are deprecated: ``idle``, ``tick``
|
||||
Use ``mpv_observe_property()`` (Lua: ``mp.observe_property()``) instead.
|
||||
The following events also happen, but are deprecated: ``tracks-changed``,
|
||||
``track-switched``, ``pause``, ``unpause``, ``metadata-update``, ``idle``,
|
||||
``tick``, ``chapter-change``. Use ``mpv_observe_property()``
|
||||
(Lua: ``mp.observe_property()``) instead.
|
||||
|
||||
Hooks
|
||||
~~~~~
|
||||
@@ -1829,7 +1817,7 @@ Property list
|
||||
|
||||
.. note::
|
||||
|
||||
Most options can be set at runtime via properties as well. Just remove the
|
||||
Most options can be set as runtime via properties as well. Just remove the
|
||||
leading ``--`` from the option name. These are not documented below, see
|
||||
`OPTIONS`_ instead. Only properties which do not exist as option with the
|
||||
same name, or which have very different behavior from the options are
|
||||
@@ -1930,11 +1918,6 @@ Property list
|
||||
This replaces the ``length`` property, which was deprecated after the
|
||||
mpv 0.9 release. (The semantics are the same.)
|
||||
|
||||
This has a sub-property:
|
||||
|
||||
``duration/full``
|
||||
``duration`` with milliseconds.
|
||||
|
||||
``avsync``
|
||||
Last A/V synchronization difference. Unavailable if audio or video is
|
||||
disabled.
|
||||
@@ -1949,9 +1932,13 @@ Property list
|
||||
situations, e.g. when video packets are damaged, or the decoder doesn't
|
||||
follow the usual rules. Unavailable if video is disabled.
|
||||
|
||||
``drop-frame-count`` is a deprecated alias.
|
||||
|
||||
``frame-drop-count``
|
||||
Frames dropped by VO (when using ``--framedrop=vo``).
|
||||
|
||||
``vo-drop-frame-count`` is a deprecated alias.
|
||||
|
||||
``mistimed-frame-count``
|
||||
Number of video frames that were not timed correctly in display-sync mode
|
||||
for the sake of keeping A/V sync. This does not include external
|
||||
@@ -1981,11 +1968,6 @@ Property list
|
||||
``time-pos`` (RW)
|
||||
Position in current file in seconds.
|
||||
|
||||
This has a sub-property:
|
||||
|
||||
``time-pos/full``
|
||||
``time-pos`` with milliseconds.
|
||||
|
||||
``time-start``
|
||||
Deprecated. Always returns 0. Before mpv 0.14, this used to return the start
|
||||
time of the file (could affect e.g. transport streams). See
|
||||
@@ -1995,41 +1977,21 @@ Property list
|
||||
Remaining length of the file in seconds. Note that the file duration is not
|
||||
always exactly known, so this is an estimate.
|
||||
|
||||
This has a sub-property:
|
||||
|
||||
``time-remaining/full``
|
||||
``time-remaining`` with milliseconds.
|
||||
|
||||
``audio-pts``
|
||||
Current audio playback position in current file in seconds. Unlike time-pos,
|
||||
this updates more often than once per frame. For audio-only files, it is
|
||||
mostly equivalent to time-pos, while for video-only files this property is
|
||||
not available.
|
||||
|
||||
This has a sub-property:
|
||||
|
||||
``audio-pts/full``
|
||||
``audio-pts`` with milliseconds.
|
||||
|
||||
``playtime-remaining``
|
||||
``time-remaining`` scaled by the current ``speed``.
|
||||
|
||||
This has a sub-property:
|
||||
|
||||
``playtime-remaining/full``
|
||||
``playtime-remaining`` with milliseconds.
|
||||
|
||||
``playback-time`` (RW)
|
||||
Position in current file in seconds. Unlike ``time-pos``, the time is
|
||||
clamped to the range of the file. (Inaccurate file durations etc. could
|
||||
make it go out of range. Useful on attempts to seek outside of the file,
|
||||
as the seek target time is considered the current position during seeking.)
|
||||
|
||||
This has a sub-property:
|
||||
|
||||
``playback-time/full``
|
||||
``playback-time`` with milliseconds.
|
||||
|
||||
``chapter`` (RW)
|
||||
Current chapter number. The number of the first chapter is 0.
|
||||
|
||||
@@ -2441,28 +2403,12 @@ Property list
|
||||
``video-params/dw``, ``video-params/dh``
|
||||
Video size as integers, scaled for correct aspect ratio.
|
||||
|
||||
``video-params/crop-x``, ``video-params/crop-y``
|
||||
Crop offset of the source video frame.
|
||||
|
||||
``video-params/crop-w``, ``video-params/crop-h``
|
||||
Video size after cropping.
|
||||
|
||||
``video-params/aspect``
|
||||
Display aspect ratio as float.
|
||||
|
||||
``video-params/aspect-name``
|
||||
Display aspect ratio name as string. The name coresponds to motion
|
||||
picture film format that introduced given aspect ratio in film.
|
||||
|
||||
``video-params/par``
|
||||
Pixel aspect ratio.
|
||||
|
||||
``video-params/sar``
|
||||
Storage aspect ratio.
|
||||
|
||||
``video-params/sar-name``
|
||||
Storage aspect ratio name as string.
|
||||
|
||||
``video-params/colormatrix``
|
||||
The colormatrix in use as string. (Exact values subject to change.)
|
||||
|
||||
@@ -2475,7 +2421,7 @@ Property list
|
||||
``video-params/gamma``
|
||||
The gamma function in use as string. (Exact values subject to change.)
|
||||
|
||||
``video-params/sig-peak`` (deprecated)
|
||||
``video-params/sig-peak``
|
||||
The video file's tagged signal peak as float.
|
||||
|
||||
``video-params/light``
|
||||
@@ -2496,33 +2442,6 @@ Property list
|
||||
(but in future releases, it could change to ``no``). If alpha is
|
||||
present, this is set to ``straight`` or ``premul``.
|
||||
|
||||
``video-params/min-luma``
|
||||
Minimum luminance, as reported by HDR10 metadata (in cd/m²)
|
||||
|
||||
``video-params/max-luma``
|
||||
Maximum luminance, as reported by HDR10 metadata (in cd/m²)
|
||||
|
||||
``video-params/max-cll``
|
||||
Maximum content light level, as reported by HDR10 metadata (in cd/m²)
|
||||
|
||||
``video-params/max-fall``
|
||||
Maximum frame average light level, as reported by HDR10 metadata (in cd/m²)
|
||||
|
||||
``video-params/scene-max-r``
|
||||
MaxRGB of a scene for R component, as reported by HDR10+ metadata (in cd/m²)
|
||||
|
||||
``video-params/scene-max-g``
|
||||
MaxRGB of a scene for G component, as reported by HDR10+ metadata (in cd/m²)
|
||||
|
||||
``video-params/scene-max-b``
|
||||
MaxRGB of a scene for B component, as reported by HDR10+ metadata (in cd/m²)
|
||||
|
||||
``video-params/max-pq-y``
|
||||
Maximum PQ luminance of a frame, as reported by peak detection (in PQ, 0-1)
|
||||
|
||||
``video-params/avg-pq-y``
|
||||
Average PQ luminance of a frame, as reported by peak detection (in PQ, 0-1)
|
||||
|
||||
When querying the property with the client API using ``MPV_FORMAT_NODE``,
|
||||
or with Lua ``mp.get_property_native``, this will return a mpv_node with
|
||||
the following contents:
|
||||
@@ -2549,15 +2468,6 @@ Property list
|
||||
"stereo-in" MPV_FORMAT_STRING
|
||||
"average-bpp" MPV_FORMAT_INT64
|
||||
"alpha" MPV_FORMAT_STRING
|
||||
"min-luma" MPV_FORMAT_DOUBLE
|
||||
"max-luma" MPV_FORMAT_DOUBLE
|
||||
"max-cll" MPV_FORMAT_DOUBLE
|
||||
"max-fall" MPV_FORMAT_DOUBLE
|
||||
"scene-max-r" MPV_FORMAT_DOUBLE
|
||||
"scene-max-g" MPV_FORMAT_DOUBLE
|
||||
"scene-max-b" MPV_FORMAT_DOUBLE
|
||||
"max-pq-y" MPV_FORMAT_DOUBLE
|
||||
"avg-pq-y" MPV_FORMAT_DOUBLE
|
||||
|
||||
``dwidth``, ``dheight``
|
||||
Video display size. This is the video size after filters and aspect scaling
|
||||
@@ -2647,7 +2557,7 @@ Property list
|
||||
is unavailable if no video is active.
|
||||
|
||||
When setting this property in the fullscreen or maximized state, the behavior
|
||||
is the same as window-scale. In all other cases, setting the value of this
|
||||
is the same as window-scale. In all ther cases, setting the value of this
|
||||
property will always resize the window. This does not affect the value of
|
||||
``window-scale``.
|
||||
|
||||
@@ -2670,6 +2580,11 @@ Property list
|
||||
available on all platforms. Note that any of the listed facts may change
|
||||
any time without a warning.
|
||||
|
||||
Writing to this property is deprecated. It has the same effect as writing to
|
||||
``override-display-fps``. Since mpv 0.31.0, this property is unavailable
|
||||
if no display FPS was reported (e.g. if no video is active), while in older
|
||||
versions, it returned the ``--display-fps`` option value.
|
||||
|
||||
``estimated-display-fps``
|
||||
The actual rate at which display refreshes seem to occur, measured by
|
||||
system time. Only available if display-sync mode (as selected by
|
||||
@@ -2689,6 +2604,13 @@ Property list
|
||||
It may be saner to report an absolute DPI, however, this is the way HiDPI
|
||||
support is implemented on most OS APIs. See also ``--hidpi-window-scale``.
|
||||
|
||||
``video-aspect`` (RW)
|
||||
Deprecated. This is tied to ``--video-aspect-override``, but always
|
||||
reports the current video aspect if video is active.
|
||||
|
||||
The read and write components of this option can be split up into
|
||||
``video-params/aspect`` and ``video-aspect-override`` respectively.
|
||||
|
||||
``osd-width``, ``osd-height``
|
||||
Last known OSD width (can be 0). This is needed if you want to use the
|
||||
``overlay-add`` command. It gives you the actual OSD/window size (not
|
||||
@@ -2728,10 +2650,6 @@ Property list
|
||||
Any of these properties may be unavailable or set to dummy values if the
|
||||
VO window is not created or visible.
|
||||
|
||||
``window-id``
|
||||
Read-only - mpv's window id. May not always be available, i.e due to window
|
||||
not being opened yet or not being supported by the VO.
|
||||
|
||||
``mouse-pos``
|
||||
Read-only - last known mouse position, normalizd to OSD dimensions.
|
||||
|
||||
@@ -2746,16 +2664,13 @@ Property list
|
||||
coordinates should be ignored when this value is false, because the
|
||||
video backends update them only when the pointer hovers the window.
|
||||
|
||||
``sub-ass-extradata``
|
||||
The current ASS subtitle track's extradata. There is no formatting done.
|
||||
The extradata is returned as a string as-is. This property is not
|
||||
available for non-ASS subtitle tracks.
|
||||
|
||||
``sub-text``
|
||||
The current subtitle text regardless of sub visibility. Formatting is
|
||||
stripped. If the subtitle is not text-based (i.e. DVD/BD subtitles), an
|
||||
empty string is returned.
|
||||
|
||||
This property is experimental and might be removed in the future.
|
||||
|
||||
``sub-text-ass``
|
||||
Like ``sub-text``, but return the text in ASS format. Text subtitles in
|
||||
other formats are converted. For native ASS subtitles, events that do
|
||||
@@ -2767,6 +2682,8 @@ Property list
|
||||
header and per-event metadata are not returned. You likely need to do
|
||||
further filtering on the returned string to make it useful.
|
||||
|
||||
This property is experimental and might be removed in the future.
|
||||
|
||||
``secondary-sub-text``
|
||||
Same as ``sub-text``, but for the secondary subtitles.
|
||||
|
||||
@@ -2801,9 +2718,10 @@ Property list
|
||||
(Before mpv 0.33.0, instead of returning -1, this property was unavailable
|
||||
if no playlist entry was current.)
|
||||
|
||||
Writing the current value back to the property will have no effect.
|
||||
Use ``playlist-play-index`` to restart the playback of the current entry if
|
||||
desired.
|
||||
Writing the current value back to the property is subject to change.
|
||||
Currently, it will restart playback of the playlist entry. But in the
|
||||
future, writing the current value will be ignored. Use the
|
||||
``playlist-play-index`` command to get guaranteed behavior.
|
||||
|
||||
``playlist-pos-1`` (RW)
|
||||
Same as ``playlist-pos``, but 1-based.
|
||||
@@ -2844,11 +2762,6 @@ Property list
|
||||
``playlist-count``
|
||||
Number of total playlist entries.
|
||||
|
||||
``playlist-path``
|
||||
The original path of the playlist for the current entry before mpv expanded
|
||||
the entries. Unavailable if the file was not originally associated with a
|
||||
playlist in some way.
|
||||
|
||||
``playlist``
|
||||
Playlist, current entry marked. Currently, the raw property value is
|
||||
useless.
|
||||
@@ -2871,10 +2784,9 @@ Property list
|
||||
entry, ``no``/false or unavailable otherwise.
|
||||
|
||||
``playlist/N/title``
|
||||
Name of the Nth entry. Available if the playlist file contains
|
||||
such fields and mpv's parser supports it for the given
|
||||
playlist format, or if the playlist entry has been opened before and a
|
||||
media-title other then then filename has been acquired.
|
||||
Name of the Nth entry. Only available if the playlist file contains
|
||||
such fields, and only if mpv's parser supports it for the given
|
||||
playlist format.
|
||||
|
||||
``playlist/N/id``
|
||||
Unique ID for this entry. This is an automatically assigned integer ID
|
||||
@@ -2882,11 +2794,6 @@ Property list
|
||||
instance. Other commands, events, etc. use this as ``playlist_entry_id``
|
||||
fields.
|
||||
|
||||
``playlist/N/playlist-path``
|
||||
The original path of the playlist for this entry before mpv expanded
|
||||
it. Unavailable if the file was not originally associated with a playlist
|
||||
in some way.
|
||||
|
||||
When querying the property with the client API using ``MPV_FORMAT_NODE``,
|
||||
or with Lua ``mp.get_property_native``, this will return a mpv_node with
|
||||
the following contents:
|
||||
@@ -2984,12 +2891,6 @@ Property list
|
||||
``track-list/N/demux-w``, ``track-list/N/demux-h``
|
||||
Video size hint as indicated by the container. (Not always accurate.)
|
||||
|
||||
``track-list/N/demux-crop-x``, ``track-list/N/demux-crop-y``
|
||||
Crop offset of the source video frame.
|
||||
|
||||
``track-list/N/demux-crop-w``, ``track-list/N/demux-crop-h``
|
||||
Video size after cropping.
|
||||
|
||||
``track-list/N/demux-channel-count``
|
||||
Number of audio channels as indicated by the container. (Not always
|
||||
accurate - in particular, the track could be decoded as a different
|
||||
@@ -3052,10 +2953,6 @@ Property list
|
||||
"decoder-desc" MPV_FORMAT_STRING
|
||||
"demux-w" MPV_FORMAT_INT64
|
||||
"demux-h" MPV_FORMAT_INT64
|
||||
"demux-crop-x" MPV_FORMAT_INT64
|
||||
"demux-crop-y" MPV_FORMAT_INT64
|
||||
"demux-crop-w" MPV_FORMAT_INT64
|
||||
"demux-crop-h" MPV_FORMAT_INT64
|
||||
"demux-channel-count" MPV_FORMAT_INT64
|
||||
"demux-channels" MPV_FORMAT_STRING
|
||||
"demux-samplerate" MPV_FORMAT_INT64
|
||||
@@ -3082,7 +2979,7 @@ Property list
|
||||
If tracks of the requested type are selected via ``--lavfi-complex``, the
|
||||
first one is returned.
|
||||
|
||||
``chapter-list`` (RW)
|
||||
``chapter-list``
|
||||
List of chapters, current entry marked. Currently, the raw property value
|
||||
is useless.
|
||||
|
||||
@@ -3173,8 +3070,7 @@ Property list
|
||||
processed for C escape sequences before passing it to the OSD code. See
|
||||
`Flat command syntax`_ for details.
|
||||
|
||||
A list of tags can be found here:
|
||||
https://aegisub.org/docs/latest/ass_tags/
|
||||
A list of tags can be found here: http://docs.aegisub.org/latest/ASS_Tags/
|
||||
|
||||
``vo-configured``
|
||||
Whether the VO is configured right now. Usually this corresponds to whether
|
||||
@@ -3350,24 +3246,6 @@ Property list
|
||||
(There is no way to ensure synchronization if two scripts try to update the
|
||||
same key at the same time.)
|
||||
|
||||
``user-data`` (RW)
|
||||
This is a recursive key/value map of arbitrary nodes shared between clients for
|
||||
general use (i.e. scripts, IPC clients, host applications, etc).
|
||||
The player itself does not use any data in it (although some builtin scripts may).
|
||||
The property is not preserved across player restarts.
|
||||
|
||||
This is a more powerful replacement for ``shared-script-properties``.
|
||||
|
||||
Sub-paths can be accessed directly; e.g. ``user-data/my-script/state/a`` can be
|
||||
read, written, or observed.
|
||||
|
||||
The top-level object itself cannot be written directly; write to sub-paths instead.
|
||||
|
||||
Converting this property or its sub-properties to strings will give a JSON
|
||||
representation. If converting a leaf-level object (i.e. not a map or array)
|
||||
and not using raw mode, the underlying content will be given (e.g. strings will be
|
||||
printed directly, rather than quoted and JSON-escaped).
|
||||
|
||||
``working-directory``
|
||||
The working directory of the mpv process. Can be useful for JSON IPC users,
|
||||
because the command line player usually works with relative paths.
|
||||
@@ -3423,9 +3301,8 @@ Property list
|
||||
might contain either a release version, or just a git hash.
|
||||
|
||||
``mpv-configuration``
|
||||
The configuration arguments that were passed to the build system. If the
|
||||
meson version used to compile mpv is older than 1.1.0, then a hardcoded
|
||||
string of a few, arbitrary options is displayed instead.
|
||||
The configuration arguments which were passed to the build system
|
||||
(typically the way ``./waf configure ...`` was invoked).
|
||||
|
||||
``ffmpeg-version``
|
||||
The contents of the ``av_version_info()`` API call. This is a string which
|
||||
@@ -3439,12 +3316,6 @@ Property list
|
||||
somewhat weird form (apparently "hex BCD"), indicating the release version
|
||||
of the libass library linked to mpv.
|
||||
|
||||
``platform``
|
||||
Returns a string describing what target platform mpv was built for. The value
|
||||
of this is dependent on what the underlying build system detects. Some of the
|
||||
most common values are: ``windows``, ``darwin`` (macos or ios), ``linux``,
|
||||
``android``, and ``freebsd``. Note that this is not a complete listing.
|
||||
|
||||
``options/<name>`` (RW)
|
||||
The value of option ``--<name>``. Most options can be changed at runtime by
|
||||
writing to this property. Note that many options require reloading the file
|
||||
@@ -3516,10 +3387,6 @@ Property list
|
||||
not a map, as order matters and duplicate entries are possible. Recursive
|
||||
profiles are not expanded, and show up as special ``profile`` options.
|
||||
|
||||
The ``profile-restore`` field is currently missing if it holds the default
|
||||
value (either because it was not set, or set explicitly to ``default``),
|
||||
but in the future it might hold the value ``default``.
|
||||
|
||||
``command-list``
|
||||
The list of input commands. This returns an array of maps, where each map
|
||||
node represents a command. This map currently only has a single entry:
|
||||
|
||||
+2
-2
@@ -193,8 +193,8 @@ 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.
|
||||
command is long running, sending the message will lead to any reply until much
|
||||
later when the command finishes.
|
||||
|
||||
Some commands execute synchronously, but these will behave like asynchronous
|
||||
commands that finished execution immediately.
|
||||
|
||||
+10
-6
@@ -73,6 +73,12 @@ Unsupported Lua APIs and their JS alternatives
|
||||
|
||||
``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.
|
||||
@@ -91,12 +97,10 @@ 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.
|
||||
success, ``fn`` is called always a-sync, ``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)
|
||||
@@ -217,9 +221,9 @@ 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``, ``append_file`` and ``require`` already use
|
||||
this internally.
|
||||
|
||||
``mp.utils.read_file(fname [,max])``
|
||||
Returns the content of file ``fname`` as string. If ``max`` is provided and
|
||||
|
||||
+5
-9
@@ -21,15 +21,15 @@ 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 enabled by default if compiler supports linking with the ``-rdynamic``
|
||||
flag on Linux/BSD platforms. On Windows the are always enabled.
|
||||
They are available on Linux/BSD platforms only and enabled by default if the
|
||||
compiler supports linking with the ``-rdynamic`` flag.
|
||||
|
||||
C plugins location
|
||||
------------------
|
||||
|
||||
C plugins are put into the mpv scripts directory in its config directory
|
||||
(see the `FILES`_ section for details). They must have a ``.so`` or ``.dll``
|
||||
file extension. They can also be explicitly loaded with the ``--script`` option.
|
||||
(see the `FILES`_ section for details). They must have a ``.so`` file extension.
|
||||
They can also be explicitly loaded with the ``--script`` option.
|
||||
|
||||
API
|
||||
---
|
||||
@@ -64,13 +64,9 @@ 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.
|
||||
|
||||
On Windows to make symbols from the host binary available, you have to define
|
||||
MPV_CPLUGIN_DYNAMIC_SYM when compiling cplugin. This will load symbols
|
||||
dynamically, before calling ``mpv_open_cplugin()``.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
|
||||
+19
-19
@@ -62,15 +62,15 @@ 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 directories in the Lua package path. This was silently
|
||||
changed in mpv 0.32.0.)
|
||||
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
|
||||
``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.
|
||||
|
||||
@@ -191,20 +191,12 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
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)``
|
||||
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
|
||||
@@ -438,21 +430,17 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
that are equal to the ``fn`` parameter. This uses normal Lua ``==``
|
||||
comparison, so be careful when dealing with closures.
|
||||
|
||||
``mp.add_timeout(seconds, fn [, disabled])``
|
||||
``mp.add_timeout(seconds, fn)``
|
||||
Call the given function fn when the given number of seconds has elapsed.
|
||||
Note that the number of seconds can be fractional. For now, the timer's
|
||||
resolution may be as low as 50 ms, although this will be improved in the
|
||||
future.
|
||||
|
||||
If the ``disabled`` argument is set to ``true`` or a truthy value, the
|
||||
timer will wait to be manually started with a call to its ``resume()``
|
||||
method.
|
||||
|
||||
This is a one-shot timer: it will be removed when it's fired.
|
||||
|
||||
Returns a timer object. See ``mp.add_periodic_timer`` for details.
|
||||
|
||||
``mp.add_periodic_timer(seconds, fn [, disabled])``
|
||||
``mp.add_periodic_timer(seconds, fn)``
|
||||
Call the given function periodically. This is like ``mp.add_timeout``, but
|
||||
the timer is re-added after the function fn is run.
|
||||
|
||||
@@ -539,6 +527,18 @@ 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.)
|
||||
@@ -684,13 +684,13 @@ with values found in the config-file and the command-line (in that order).
|
||||
|
||||
Example implementation::
|
||||
|
||||
require 'mp.options'
|
||||
local options = {
|
||||
optionA = "defaultvalueA",
|
||||
optionB = -0.5,
|
||||
optionC = true,
|
||||
}
|
||||
|
||||
require "mp.options".read_options(options, "myscript")
|
||||
read_options(options, "myscript")
|
||||
print(options.optionA)
|
||||
|
||||
|
||||
|
||||
+102
-160
@@ -39,12 +39,9 @@ LIRC support - configure remotes as input devices instead).
|
||||
|
||||
See the ``--input-`` options for ways to customize it.
|
||||
|
||||
The following listings are not necessarily complete. See ``etc/input.conf``
|
||||
in the mpv source files for a list of default bindings. User ``input.conf``
|
||||
files and Lua scripts can define additional key bindings.
|
||||
|
||||
See `COMMAND INTERFACE`_ and `Key names`_ sections for more details on
|
||||
configuring keybindings.
|
||||
The following listings are not necessarily complete. See ``etc/input.conf`` for
|
||||
a list of default bindings. User ``input.conf`` files and Lua scripts can
|
||||
define additional key bindings.
|
||||
|
||||
See also ``--input-test`` for interactive binding details by key, and the
|
||||
`stats`_ built-in script for key bindings list (including print to terminal).
|
||||
@@ -64,7 +61,7 @@ Ctrl+LEFT and Ctrl+RIGHT
|
||||
Seek to the previous/next subtitle. Subject to some restrictions and
|
||||
might not always work; see ``sub-seek`` command.
|
||||
|
||||
Ctrl+Shift+LEFT and Ctrl+Shift+RIGHT
|
||||
Ctrl+Shift+Left and Ctrl+Shift+Right
|
||||
Adjust subtitle delay so that the next or previous subtitle is displayed
|
||||
now. This is especially useful to sync subtitles to audio.
|
||||
|
||||
@@ -94,7 +91,7 @@ Shift+Ctrl+BACKSPACE
|
||||
ENTER
|
||||
Go forward in the playlist.
|
||||
|
||||
p and SPACE
|
||||
p / SPACE
|
||||
Pause (pressing again unpauses).
|
||||
|
||||
\.
|
||||
@@ -110,8 +107,7 @@ q
|
||||
|
||||
Q
|
||||
Like ``q``, but store the current playback position. Playing the same file
|
||||
later will resume at the old playback position if possible. See
|
||||
`RESUMING PLAYBACK`_.
|
||||
later will resume at the old playback position if possible.
|
||||
|
||||
/ and *
|
||||
Decrease/increase volume.
|
||||
@@ -128,9 +124,6 @@ m
|
||||
\#
|
||||
Cycle through the available audio tracks.
|
||||
|
||||
E
|
||||
Cycle through the available Editions.
|
||||
|
||||
f
|
||||
Toggle fullscreen (see also ``--fs``).
|
||||
|
||||
@@ -144,7 +137,7 @@ w and W
|
||||
Decrease/increase pan-and-scan range. The ``e`` key does the same as
|
||||
``W`` currently, but use is discouraged.
|
||||
|
||||
o and P
|
||||
o (also P)
|
||||
Show progression bar, elapsed time and total duration on the OSD.
|
||||
|
||||
O
|
||||
@@ -166,16 +159,16 @@ l
|
||||
L
|
||||
Toggle infinite looping.
|
||||
|
||||
Ctrl++ and Ctrl+-
|
||||
Ctrl + and Ctrl -
|
||||
Adjust audio delay (A/V sync) by +/- 0.1 seconds.
|
||||
|
||||
Shift+g and Shift+f
|
||||
Adjust subtitle font size by +/- 10%.
|
||||
|
||||
u
|
||||
Switch between applying only ``--sub-ass-*`` overrides (default) to SSA/ASS
|
||||
subtitles, and overriding them almost completely with the normal subtitle
|
||||
style. See ``--sub-ass-override`` for more info.
|
||||
Switch between applying no style overrides to SSA/ASS subtitles, and
|
||||
overriding them almost completely with the normal subtitle style. See
|
||||
``--sub-ass-override`` for more info.
|
||||
|
||||
V
|
||||
Toggle subtitle VSFilter aspect compatibility mode. See
|
||||
@@ -192,7 +185,7 @@ S
|
||||
Take a screenshot, without subtitles. (Whether this works depends on VO
|
||||
driver support.)
|
||||
|
||||
Ctrl+s
|
||||
Ctrl s
|
||||
Take a screenshot, as the window shows it (with subtitles, OSD, and scaled
|
||||
video).
|
||||
|
||||
@@ -211,13 +204,13 @@ d
|
||||
A
|
||||
Cycle aspect ratio override.
|
||||
|
||||
Ctrl+h
|
||||
Ctrl h
|
||||
Toggle hardware video decoding on/off.
|
||||
|
||||
Alt+LEFT, Alt+RIGHT, Alt+UP, Alt+DOWN
|
||||
Move the video rectangle (panning).
|
||||
|
||||
Alt++ and Alt+-
|
||||
Alt + and Alt -
|
||||
Combining ``Alt`` with the ``+`` or ``-`` keys changes video zoom.
|
||||
|
||||
Alt+BACKSPACE
|
||||
@@ -236,7 +229,7 @@ i and I
|
||||
file such as codec, framerate, number of dropped frames and so on. See
|
||||
`STATS`_ for more information.
|
||||
|
||||
DEL
|
||||
del
|
||||
Cycle OSC visibility between never / auto (mouse-move) / always
|
||||
|
||||
\`
|
||||
@@ -257,16 +250,16 @@ corresponding adjustment.)
|
||||
7 and 8
|
||||
Adjust saturation.
|
||||
|
||||
Alt+0 (and Command+0 on macOS)
|
||||
Alt+0 (and command+0 on macOS)
|
||||
Resize video window to half its original size.
|
||||
|
||||
Alt+1 (and Command+1 on macOS)
|
||||
Alt+1 (and command+1 on macOS)
|
||||
Resize video window to its original size.
|
||||
|
||||
Alt+2 (and Command+2 on macOS)
|
||||
Alt+2 (and command+2 on macOS)
|
||||
Resize video window to double its original size.
|
||||
|
||||
Command + f (macOS only)
|
||||
command + f (macOS only)
|
||||
Toggle fullscreen (see also ``--fs``).
|
||||
|
||||
(The following keys are valid if you have a keyboard with multimedia keys.)
|
||||
@@ -280,8 +273,6 @@ STOP
|
||||
PREVIOUS and NEXT
|
||||
Seek backward/forward 1 minute.
|
||||
|
||||
ZOOMIN and ZOOMOUT
|
||||
Changes video zoom.
|
||||
|
||||
If you miss some older key bindings, look at ``etc/restore-old-bindings.conf``
|
||||
in the mpv git repository.
|
||||
@@ -299,10 +290,10 @@ Forward/Back button
|
||||
Skip to next/previous entry in playlist.
|
||||
|
||||
Wheel up/down
|
||||
Decrease/increase volume.
|
||||
Seek forward/backward 10 seconds.
|
||||
|
||||
Wheel left/right
|
||||
Seek forward/backward 10 seconds.
|
||||
Decrease/increase volume.
|
||||
|
||||
|
||||
USAGE
|
||||
@@ -463,10 +454,6 @@ Name Meaning
|
||||
``~~desktop/`` the path to the desktop (win32, macOS)
|
||||
``~~exe_dir/`` win32 only: the path to the directory containing the exe (for
|
||||
config file purposes; ``$MPV_HOME`` overrides it)
|
||||
``~~cache/`` the path to application cache data (``~/.cache/mpv/``)
|
||||
On some platforms, this will be the same as ``~~home/``.
|
||||
``~~state/`` the path to application state data (``~/.local/state/mpv/``)
|
||||
On some platforms, this will be the same as ``~~home/``.
|
||||
``~~old_home/`` do not use
|
||||
================ ===============================================================
|
||||
|
||||
@@ -525,8 +512,10 @@ String list and path list options
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
String lists are separated by ``,``. The strings are not parsed or interpreted
|
||||
by the option system itself. However, most path or file list options use ``:``
|
||||
(Unix) or ``;`` (Windows) as separator, instead of ``,``.
|
||||
by the option system itself. However, most
|
||||
|
||||
Path or file list options use ``:`` (Unix) or ``;`` (Windows) as separator,
|
||||
instead of ``,``.
|
||||
|
||||
They support the following operations:
|
||||
|
||||
@@ -539,6 +528,7 @@ Suffix Meaning
|
||||
-pre Prepend 1 or more items (same syntax as -set)
|
||||
-clr Clear the option (remove all items)
|
||||
-remove Delete item if present (does not interpret escapes)
|
||||
-del Delete 1 or more items by integer index (deprecated)
|
||||
-toggle Append an item, or remove if if it already exists (no escapes)
|
||||
============= ===============================================
|
||||
|
||||
@@ -567,7 +557,7 @@ Keys are unique within the list. If an already present key is set, the existing
|
||||
key is removed before the new value is appended.
|
||||
|
||||
If you want to pass a value without interpreting it for escapes or ``,``, it is
|
||||
recommended to use the ``-append`` variant. When using libmpv, prefer using
|
||||
recommended to use the ``-add`` variant. When using libmpv, prefer using
|
||||
``MPV_FORMAT_NODE_MAP``; when using a scripting backend or the JSON IPC, use an
|
||||
appropriate structured data type.
|
||||
|
||||
@@ -589,6 +579,7 @@ Suffix Meaning
|
||||
-pre Prepend 1 or more filters (same syntax as -set)
|
||||
-clr Clear the option (remove all filters)
|
||||
-remove Delete filter if present
|
||||
-del Delete 1 or more filters by integer index or filter label (deprecated)
|
||||
-toggle Append a filter, or remove if if it already exists
|
||||
-help Pseudo operation that prints a help text to the terminal
|
||||
============= ===============================================
|
||||
@@ -625,34 +616,30 @@ user-specific one is ``~/.config/mpv/mpv.conf``. For details and platform
|
||||
specifics (in particular Windows paths) see the `FILES`_ section.
|
||||
|
||||
User-specific options override system-wide options and options given on the
|
||||
command line override both. The syntax of the configuration files is
|
||||
``option=value``. Everything after a *#* is considered a comment. Options that
|
||||
work without values can be enabled by setting them to *yes* and disabled by
|
||||
setting them to *no*, and if the value is omitted, *yes* is implied. Even
|
||||
suboptions can be specified in this way.
|
||||
command line override either. The syntax of the configuration files is
|
||||
``option=value``. Everything after a *#* is considered a comment. Options
|
||||
that work without values can be enabled by setting them to *yes* and disabled by
|
||||
setting them to *no*. Even suboptions can be specified in this way.
|
||||
|
||||
.. admonition:: Example configuration file
|
||||
|
||||
::
|
||||
|
||||
# Don't allow new windows to be larger than the screen.
|
||||
autofit-larger=100%x100%
|
||||
# Enable hardware decoding if available, =yes is implied.
|
||||
hwdec
|
||||
# Spaces don't have to be escaped.
|
||||
osd-playing-msg=File: ${filename}
|
||||
# Use GPU-accelerated video output by default.
|
||||
vo=gpu
|
||||
# Use quotes for text that can contain spaces:
|
||||
term-status-msg="Time: ${time-pos}"
|
||||
|
||||
Escaping special characters
|
||||
Escaping spaces and special characters
|
||||
--------------------------------------
|
||||
|
||||
This is done like with command line options. A config entry can be quoted with
|
||||
``"``, ``'``, as well as with the fixed-length syntax (``%n%``) mentioned
|
||||
before. This is like passing the exact contents of the quoted string as a
|
||||
command line option. C-style escapes are currently _not_ interpreted on this
|
||||
level, although some options do this manually (this is a mess and should
|
||||
probably be changed at some point). The shell is not involved here, so option
|
||||
values only need to be quoted to escape ``#`` and ``\``, ``"``, ``'`` or ``%``
|
||||
at the beginning of the value, and leading and trailing whitespace.
|
||||
This is done like with command line options. The shell is not involved here,
|
||||
but option values still need to be quoted as a whole if it contains certain
|
||||
characters like spaces. A config entry can be quoted with ``"``,
|
||||
as well as with the fixed-length syntax (``%n%``) mentioned before. This is like
|
||||
passing the exact contents of the quoted string as command line option. C-style
|
||||
escapes are currently _not_ interpreted on this level, although some options do
|
||||
this manually. (This is a mess and should probably be changed at some point.)
|
||||
|
||||
Putting Command Line Options into the Configuration File
|
||||
--------------------------------------------------------
|
||||
@@ -666,7 +653,7 @@ Option Configuration file entry
|
||||
``--flag`` ``flag``
|
||||
``-opt val`` ``opt=val``
|
||||
``--opt=val`` ``opt=val``
|
||||
``-opt "has spaces"`` ``opt=has spaces``
|
||||
``-opt "has spaces"`` ``opt="has spaces"``
|
||||
======================= ========================
|
||||
|
||||
File-specific Configuration Files
|
||||
@@ -716,7 +703,7 @@ or at runtime with the ``apply-profile <name>`` command.
|
||||
[slow]
|
||||
profile-desc="some profile name"
|
||||
# reference a builtin profile
|
||||
profile=high-quality
|
||||
profile=gpu-hq
|
||||
|
||||
[fast]
|
||||
vo=vdpau
|
||||
@@ -780,7 +767,7 @@ the following options:
|
||||
|
||||
[something]
|
||||
profile-restore=copy-equal
|
||||
vf-add=rotate=PI/2 # rotate by 90 degrees
|
||||
vf-add=rotate=90
|
||||
|
||||
Then running these commands will result in behavior as commented:
|
||||
|
||||
@@ -788,9 +775,9 @@ the following options:
|
||||
|
||||
set vf vflip
|
||||
apply-profile something
|
||||
vf add hflip
|
||||
vf-add=hflip
|
||||
apply-profile something
|
||||
# vf == vflip,rotate=PI/2,hflip,rotate=PI/2
|
||||
# vf == vflip,rotate=90,hflip,rotate=90
|
||||
apply-profile something restore
|
||||
# vf == vflip
|
||||
|
||||
@@ -799,10 +786,9 @@ Conditional auto profiles
|
||||
|
||||
Profiles which have the ``profile-cond`` option set are applied automatically
|
||||
if the associated condition matches (unless auto profiles are disabled). The
|
||||
option takes a string, which is interpreted as Lua expression. If the
|
||||
expression evaluates as truthy, the profile is applied. If the expression
|
||||
errors or evaluates as falsy, the profile is not applied. This Lua code
|
||||
execution is not sandboxed.
|
||||
option takes a string, which is interpreted as Lua condition. If evaluating the
|
||||
expression returns true, the profile is applied, if it returns false, it is
|
||||
ignored. This Lua code execution is not sandboxed.
|
||||
|
||||
Any variables in condition expressions can reference properties. If an
|
||||
identifier is not already defined by Lua or mpv, it is interpreted as property.
|
||||
@@ -823,13 +809,13 @@ cause errors if used in expressions. These are logged in verbose mode, and the
|
||||
expression is considered to be false.
|
||||
|
||||
Whenever a property referenced by a profile condition changes, the condition
|
||||
is re-evaluated. If the return value of the condition changes from falsy or
|
||||
error to truthy, the profile is applied.
|
||||
is re-evaluated. If the return value of the condition changes from false or
|
||||
error to true, the profile is applied.
|
||||
|
||||
This mechanism tries to "unapply" profiles once the condition changes from
|
||||
truthy to falsy or error. If you want to use this, you need to set
|
||||
``profile-restore`` for the profile. Another possibility it to create another
|
||||
profile with an inverse condition to undo the other profile.
|
||||
This mechanism tries to "unapply" profiles once the condition changes from true
|
||||
to false. If you want to use this, you need to set ``profile-restore`` for the
|
||||
profile. Another possibility it to create another profile with an inverse
|
||||
condition to undo the other profile.
|
||||
|
||||
Recursive profiles can be used. But it is discouraged to reference other
|
||||
conditional profiles in a conditional profile, since this can lead to tricky
|
||||
@@ -846,14 +832,6 @@ and unintuitive behavior.
|
||||
profile-cond=width >= 1280
|
||||
hue=-50
|
||||
|
||||
Make only videos containing "youtube" or "youtu.be" in their path brighter:
|
||||
|
||||
::
|
||||
|
||||
[youtube]
|
||||
profile-cond=path:find('youtu%.?be')
|
||||
gamma=20
|
||||
|
||||
If you want the profile to be reverted if the condition goes to false again,
|
||||
you can set ``profile-restore``:
|
||||
|
||||
@@ -863,7 +841,7 @@ and unintuitive behavior.
|
||||
profile-desc=Mess up video when entering fullscreen
|
||||
profile-cond=fullscreen
|
||||
profile-restore=copy
|
||||
vf-add=rotate=PI/2 # rotate by 90 degrees
|
||||
vf-add=rotate=90
|
||||
|
||||
This appends the ``rotate`` filter to the video filter chain when entering
|
||||
fullscreen. When leaving fullscreen, the ``vf`` option is set to the value
|
||||
@@ -876,7 +854,7 @@ and unintuitive behavior.
|
||||
|
||||
[something]
|
||||
profile-cond=fullscreen
|
||||
vf-add=@rot:rotate=PI/2
|
||||
vf-add=@rot:rotate=90
|
||||
|
||||
[something-inv]
|
||||
profile-cond=not fullscreen
|
||||
@@ -1092,8 +1070,7 @@ this with ``--untimed``, but it will likely break, unless the stream has no
|
||||
audio, and the input feeds data to the player at a constant rate.
|
||||
|
||||
Another common problem is with MJPEG streams. These do not signal the correct
|
||||
framerate. Using ``--untimed`` or ``--no-correct-pts --container-fps-override=60``
|
||||
might help.
|
||||
framerate. Using ``--untimed`` or ``--no-correct-pts --fps=60`` might help.
|
||||
|
||||
For livestreams, data can build up due to pausing the stream, due to slightly
|
||||
lower playback rate, or "buffering" pauses. If the demuxer cache is enabled,
|
||||
@@ -1111,33 +1088,6 @@ Additional options that can be tried:
|
||||
- without audio ``--framedrop=no --speed=1.01`` may help for live sources
|
||||
(results can be mixed)
|
||||
|
||||
RESUMING PLAYBACK
|
||||
=================
|
||||
|
||||
mpv is capable of storing the playback position of the currently playing file
|
||||
and resume from there the next time that file is played. This is done with the
|
||||
commands ``quit-watch-later`` (bound to Shift+Q by default) and
|
||||
``write-watch-later-config``, and with the ``--save-position-on-quit`` option.
|
||||
|
||||
The difference between always quitting with a key bound to ``quit-watch-later``
|
||||
and using ``--save-position-on-quit`` is that the latter will save the playback
|
||||
position even when mpv is closed with a method other than a keybinding, for
|
||||
example if you shutdown your system without closing mpv beforehand, unless of
|
||||
course mpv is terminated abruptly and doesn't have the time to save (e.g. with
|
||||
the KILL Unix signal).
|
||||
|
||||
mpv also stores options other than the playback position when they have been
|
||||
modified after playback began, for example the volume and selected audio/subtitles,
|
||||
and restores their values the next time the file is played. Which options are
|
||||
saved can be configured with the ``--watch-later-options`` option.
|
||||
|
||||
When playing multiple playlist entries, mpv checks if one them has a resume
|
||||
config file associated, and if it finds one it restarts playback from it. For
|
||||
example, if you use ``quit-watch-later`` on the 5th episode of a show, and
|
||||
later play all the episodes, mpv will automatically resume playback from
|
||||
episode 5.
|
||||
|
||||
More options to configure this functionality are listed in `Watch Later`_.
|
||||
|
||||
PROTOCOLS
|
||||
=========
|
||||
@@ -1371,13 +1321,36 @@ a VO using the OS GUI API is active). ``--stop-screensaver=no`` disables this.
|
||||
|
||||
A common problem is that Linux desktop environments ignore the standard
|
||||
screensaver APIs on which mpv relies. In particular, mpv uses the Screen Saver
|
||||
extension (XSS) on X11, and the idle-inhibit protocol on Wayland.
|
||||
extension (XSS) on X11, and the idle-inhibit on Wayland.
|
||||
|
||||
GNOME is one of the worst offenders, and ignores even the now widely supported
|
||||
idle-inhibit protocol. (This is either due to a combination of malice and
|
||||
incompetence, but since implementing this protocol would only take a few lines
|
||||
of code, it is most likely the former. You will also notice how GNOME advocates
|
||||
react offended whenever their sabotage is pointed out, which indicates either
|
||||
hypocrisy, or even worse ignorance.)
|
||||
|
||||
Such incompatible desktop environments (i.e. which ignore standards) typically
|
||||
require using a DBus API. This is ridiculous in several ways. The immediate
|
||||
practical problem is that it would require adding a quite unwieldy dependency
|
||||
for a DBus library, somehow integrating its mainloop into mpv, and other
|
||||
generally unacceptable things.
|
||||
|
||||
However, since mpv does not officially support GNOME, this is not much of a
|
||||
problem. If you are one of those miserable users who want to use mpv on GNOME,
|
||||
report a bug on the GNOME issue tracker:
|
||||
https://gitlab.gnome.org/groups/GNOME/-/issues
|
||||
|
||||
Alternatively, you may be able to write a Lua script that calls the
|
||||
``xdg-screensaver`` command line program. (By the way, this a command line
|
||||
program is an utterly horrible kludge that tries to identify your DE, and then
|
||||
tries to send the correct DBus command via a DBus CLI tool.) If you find the
|
||||
idea of having to write a script just so your screensaver doesn't kick in
|
||||
ridiculous, do not use GNOME, or use GNOME video software instead of mpv (good
|
||||
luck).
|
||||
|
||||
Before mpv 0.33.0, the X11 backend ran ``xdg-screensaver reset`` in 10 second
|
||||
intervals when not paused in order to support screensaver inhibition in these
|
||||
environments. This functionality was removed in 0.33.0, but it is possible to
|
||||
call the ``xdg-screensaver`` command line program from a user script instead.
|
||||
|
||||
intervals when not paused. This hack was removed in 0.33.0.
|
||||
|
||||
.. include:: options.rst
|
||||
|
||||
@@ -1539,7 +1512,6 @@ input command can take an exit code: in this case, that exit code is returned.
|
||||
FILES
|
||||
=====
|
||||
|
||||
Note that this section assumes Linux/BSD. On other platforms the paths may be different.
|
||||
For Windows-specifics, see `FILES ON WINDOWS`_ section.
|
||||
|
||||
``/usr/local/etc/mpv/mpv.conf``
|
||||
@@ -1547,18 +1519,6 @@ For Windows-specifics, see `FILES ON WINDOWS`_ section.
|
||||
in default configuration will use ``/usr/local/etc/mpv/`` as config
|
||||
directory, while most Linux distributions will set it to ``/etc/mpv/``).
|
||||
|
||||
``~/.cache/mpv``
|
||||
The standard cache directory. Certain options within mpv may cause it to write
|
||||
cache files to disk. This can be overridden by environment variables, in
|
||||
ascending order:
|
||||
|
||||
:1: If ``$XDG_CACHE_HOME`` is set, then the derived cache directory
|
||||
will be ``$XDG_CACHE_HOME/mpv``.
|
||||
:2: If ``$MPV_HOME`` is set, then the derived cache directory will be
|
||||
``$MPV_HOME``.
|
||||
|
||||
If the directory does not exist, mpv will try to create it automatically.
|
||||
|
||||
``~/.config/mpv``
|
||||
The standard configuration directory. This can be overridden by environment
|
||||
variables, in ascending order:
|
||||
@@ -1573,9 +1533,7 @@ For Windows-specifics, see `FILES ON WINDOWS`_ section.
|
||||
|
||||
``~/.mpv/``
|
||||
The original (pre 0.5.0) configuration directory. It will continue to be
|
||||
read if present. If this directory is present and the standard configuration
|
||||
directory is not present, then cache files and watch later config files will
|
||||
also be written to this directory.
|
||||
read if present.
|
||||
|
||||
If both this directory and the standard configuration directory are
|
||||
present, configuration will be read from both with the standard
|
||||
@@ -1600,8 +1558,11 @@ For Windows-specifics, see `FILES ON WINDOWS`_ section.
|
||||
fallback subtitle font
|
||||
|
||||
``~/.config/mpv/fonts/``
|
||||
Default location for ``--sub-fonts-dir`` (see `Subtitles`_) and
|
||||
``--osd-fonts-dir`` (see `OSD`_).
|
||||
Font files in this directory are used by mpv/libass for subtitles. Useful
|
||||
if you do not want to install fonts to your system. Note that files in this
|
||||
directory are loaded into memory before being used by mpv. If you have a
|
||||
lot of fonts, consider using fonts.conf (see above) to include additional
|
||||
fonts, which is more memory-efficient.
|
||||
|
||||
``~/.config/mpv/scripts/``
|
||||
All files in this directory are loaded as if they were passed to the
|
||||
@@ -1611,18 +1572,11 @@ For Windows-specifics, see `FILES ON WINDOWS`_ section.
|
||||
|
||||
See `Script location`_ for details.
|
||||
|
||||
``~/.local/state/mpv/watch_later/``
|
||||
``~/.config/mpv/watch_later/``
|
||||
Contains temporary config files needed for resuming playback of files with
|
||||
the watch later feature. See for example the ``Q`` key binding, or the
|
||||
``quit-watch-later`` input command.
|
||||
|
||||
This can be overridden by environment variables, in ascending order:
|
||||
|
||||
:1: If ``$XDG_STATE_HOME`` is set, then the derived watch later directory
|
||||
will be ``$XDG_STATE_HOME/mpv/watch_later``.
|
||||
:2: If ``$MPV_HOME`` is set, then the derived watch later directory will be
|
||||
``$MPV_HOME/watch_later``.
|
||||
|
||||
Each file is a small config file which is loaded if the corresponding media
|
||||
file is loaded. It contains the playback position and some (not necessarily
|
||||
all) settings that were changed during playback. The filenames are hashed
|
||||
@@ -1653,20 +1607,16 @@ You can find the exact path by running ``echo %APPDATA%\mpv\mpv.conf`` in cmd.ex
|
||||
Other config files (such as ``input.conf``) are in the same directory. See the
|
||||
`FILES`_ section above.
|
||||
|
||||
The cache directory is located at ``%LOCALAPPDATA%/mpv/cache``.
|
||||
|
||||
The watch_later directory is located at ``%LOCALAPPDATA%/mpv/watch_later``.
|
||||
|
||||
The environment variable ``$MPV_HOME`` completely overrides these, like on
|
||||
UNIX.
|
||||
|
||||
If a directory named ``portable_config`` next to the mpv.exe exists, all
|
||||
config will be loaded from this directory only. Watch later config files and
|
||||
cache files are written to this directory as well. (This exists on Windows
|
||||
only and is redundant with ``$MPV_HOME``. However, since Windows is very
|
||||
scripting unfriendly, a wrapper script just setting ``$MPV_HOME``, like you
|
||||
could do it on other systems, won't work. ``portable_config`` is provided for
|
||||
convenience to get around this restriction.)
|
||||
config will be loaded from this directory only. Watch later config files are
|
||||
written to this directory as well. (This exists on Windows only and is redundant
|
||||
with ``$MPV_HOME``. However, since Windows is very scripting unfriendly, a
|
||||
wrapper script just setting ``$MPV_HOME``, like you could do it on other
|
||||
systems, won't work. ``portable_config`` is provided for convenience to get
|
||||
around this restriction.)
|
||||
|
||||
Config files located in the same directory as ``mpv.exe`` are loaded with
|
||||
lower priority. Some config files are loaded only once, which means that
|
||||
@@ -1680,11 +1630,3 @@ future.
|
||||
|
||||
Note that mpv likes to mix ``/`` and ``\`` path separators for simplicity.
|
||||
kernel32.dll accepts this, but cmd.exe does not.
|
||||
|
||||
FILES ON MACOS
|
||||
==============
|
||||
|
||||
On macOS the watch later directory is located at ``~/.config/mpv/watch_later/``
|
||||
and the cache directory is set to ``~/Library/Caches/io.mpv/``. These directories
|
||||
can't be overwritten by enviroment variables.
|
||||
Everything else is the same as `FILES`_.
|
||||
+498
-941
File diff suppressed because it is too large.
Load diff
+1
-47
@@ -85,7 +85,6 @@ seekbar
|
||||
|
||||
============= ================================================
|
||||
left-click seek to position
|
||||
mouse wheel seek forward/backward
|
||||
============= ================================================
|
||||
|
||||
time left
|
||||
@@ -102,7 +101,6 @@ audio and sub
|
||||
left-click cycle audio/sub tracks forward
|
||||
right-click cycle audio/sub tracks backwards
|
||||
shift+L-click show available audio/sub tracks
|
||||
mouse wheel cycle audio/sub tracks forward/backwards
|
||||
============= ================================================
|
||||
|
||||
vol
|
||||
@@ -176,7 +174,7 @@ Configurable Options
|
||||
``seekbarhandlesize``
|
||||
Default: 0.6
|
||||
|
||||
Size ratio of the seek handle if ``seekbarstyle`` is set to ``diamond``
|
||||
Size ratio of the seek handle if ``seekbarstyle`` is set to ``dimaond``
|
||||
or ``knob``. This is relative to the full height of the seekbar.
|
||||
|
||||
``seekbarkeyframes``
|
||||
@@ -238,11 +236,6 @@ Configurable Options
|
||||
|
||||
Enable the OSC when fullscreen
|
||||
|
||||
``idlescreen``
|
||||
Default: yes
|
||||
|
||||
Show the mpv logo and message when idle
|
||||
|
||||
``scalewindowed``
|
||||
Default: 1.0
|
||||
|
||||
@@ -312,26 +305,11 @@ Configurable Options
|
||||
|
||||
Show total time instead of time remaining
|
||||
|
||||
``remaining_playtime``
|
||||
Default: yes
|
||||
|
||||
Whether the time-remaining display takes speed into account.
|
||||
``yes`` - how much playback time remains at the current speed.
|
||||
``no`` - how much video-time remains.
|
||||
|
||||
``timems``
|
||||
Default: no
|
||||
|
||||
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)
|
||||
|
||||
@@ -404,26 +382,6 @@ Configurable Options
|
||||
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
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -446,10 +404,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.
|
||||
|
||||
+11
-9
@@ -35,6 +35,13 @@ UP Scroll one line up
|
||||
DOWN Scroll one line down
|
||||
==== ==================
|
||||
|
||||
Font
|
||||
~~~~
|
||||
|
||||
For optimal visual experience, a font with support for many font weights and
|
||||
monospaced digits is recommended. By default, the open source font
|
||||
`Source Sans Pro <https://github.com/adobe-fonts/source-sans-pro>`_ is used.
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
@@ -97,24 +104,19 @@ Configurable Options
|
||||
|
||||
Show graphs for vsync and jitter values (page 1). Only when toggled.
|
||||
|
||||
``plot_tonemapping_lut``
|
||||
Default: no
|
||||
|
||||
Enable tone-mapping LUT visualization automatically. Only when toggled.
|
||||
|
||||
``flush_graph_data``
|
||||
Default: yes
|
||||
|
||||
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.
|
||||
@@ -225,8 +227,8 @@ 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 and will increase memory usage. It can be
|
||||
enabled by setting the ``--js-memory-report`` option before starting mpv.
|
||||
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
|
||||
|
||||
+15
-30
@@ -44,17 +44,19 @@ The exact syntax is:
|
||||
the ``lavfi`` filter, which uses a very similar syntax as mpv (MPlayer
|
||||
historically) to specify filters and their parameters.
|
||||
|
||||
.. note::
|
||||
|
||||
``--vf`` can only take a single track as input, even if the filter supports
|
||||
dynamic input. Filters that require multiple inputs can't be used.
|
||||
Use ``--lavfi-complex`` for such a use case. This also applies for ``--af``.
|
||||
|
||||
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.
|
||||
|
||||
``--vf-defaults=<filter1[=parameter1:parameter2:...],filter2,...>``
|
||||
Set defaults for each filter. (Deprecated. ``--af-defaults`` is deprecated
|
||||
as well.)
|
||||
|
||||
.. note::
|
||||
|
||||
To get a full list of available video filters, see ``--vf=help`` and
|
||||
@@ -108,6 +110,11 @@ filter list.
|
||||
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-clr``
|
||||
Completely empties the filter list.
|
||||
|
||||
@@ -134,11 +141,6 @@ Available mpv-only filters are:
|
||||
|
||||
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).
|
||||
|
||||
@@ -162,9 +164,6 @@ Available mpv-only filters are:
|
||||
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``
|
||||
@@ -309,14 +308,6 @@ 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
|
||||
@@ -466,7 +457,7 @@ Available mpv-only filters are:
|
||||
::
|
||||
|
||||
import vapoursynth as vs
|
||||
from vapoursynth import core
|
||||
core = vs.get_core()
|
||||
core.std.AddBorders(video_in, 10, 10, 20, 20).set_output()
|
||||
|
||||
.. warning::
|
||||
@@ -569,19 +560,13 @@ Available mpv-only filters are:
|
||||
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
|
||||
the ``--container-fps-override`` command line option overrides this value.
|
||||
the ``--fps`` command line option overrides this value.
|
||||
|
||||
Useful for some filters which insist on having a FPS.
|
||||
|
||||
``display_fps``
|
||||
Refresh rate of the current display. Note that this value can be 0.
|
||||
|
||||
``display_res``
|
||||
Resolution of the current display. This is an integer array with the
|
||||
first entry corresponding to the width and the second entry coresponding
|
||||
to the height. These values can be 0. Note that this will not respond to
|
||||
monitor changes and may not work on all platforms.
|
||||
|
||||
``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.
|
||||
|
||||
+93
-145
@@ -21,47 +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, mpv utilizes settings that balance quality and performance.
|
||||
Additionally, two predefined profiles are available: ``fast`` for maximum
|
||||
performance and ``high-quality`` for superior rendering quality. You can
|
||||
apply a specific profile using the ``--profile=<name>`` option and inspect
|
||||
its contents using ``--show-profile=<name>``.
|
||||
|
||||
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
|
||||
@@ -119,9 +78,7 @@ Available video output drivers are:
|
||||
|
||||
``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::
|
||||
|
||||
@@ -271,6 +228,35 @@ 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 ``--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.
|
||||
|
||||
``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.
|
||||
@@ -287,23 +273,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=gpu`` too.
|
||||
|
||||
The following global options are supported by this video output:
|
||||
|
||||
@@ -317,6 +293,25 @@ Available video output drivers are:
|
||||
nla
|
||||
``non-linear anamorphic scaling``
|
||||
|
||||
``--vo-vaapi-deint-mode=<mode>``
|
||||
Select deinterlacing algorithm. Note that by default deinterlacing is
|
||||
initially always off, and needs to be enabled with the ``d`` key
|
||||
(default key binding for ``cycle deinterlace``).
|
||||
|
||||
This option doesn't apply if libva supports video post processing (vpp).
|
||||
In this case, the default for ``deint-mode`` is ``no``, and enabling
|
||||
deinterlacing via user interaction using the methods mentioned above
|
||||
actually inserts the ``vavpp`` video filter. If vpp is not actually
|
||||
supported with the libva backend in use, you can use this option to
|
||||
forcibly enable VO based deinterlacing.
|
||||
|
||||
no
|
||||
Don't allow deinterlacing (default for newer libva).
|
||||
first-field
|
||||
Show only first field.
|
||||
bob
|
||||
bob deinterlacing (default for older libva).
|
||||
|
||||
``--vo-vaapi-scaled-osd=<yes|no>``
|
||||
If enabled, then the OSD is rendered at video resolution and scaled to
|
||||
display resolution. By default, this is disabled, and the OSD is
|
||||
@@ -368,54 +363,13 @@ 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.
|
||||
Note: the Sixel image output is not synchronized with other terminal output
|
||||
from mpv, which can lead to broken images. The option ``--really-quiet``
|
||||
can help with that, and is recommended.
|
||||
|
||||
You may need to use ``--profile=sw-fast`` to get decent performance.
|
||||
|
||||
@@ -459,25 +413,10 @@ Available video output drivers are:
|
||||
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
|
||||
``--vo-sixel-exit-clear=<yes|no>`` (default: yes)
|
||||
Whether or not to clear the terminal on quit. 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>``
|
||||
@@ -570,7 +509,14 @@ Available video output drivers are:
|
||||
``rpi`` (Raspberry Pi)
|
||||
Native video output on the Raspberry Pi using the MMAL API.
|
||||
|
||||
The following global options are supported by this video output:
|
||||
This is deprecated. Use ``--vo=gpu`` 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
|
||||
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).
|
||||
|
||||
The following deprecated global options are supported by this video output:
|
||||
|
||||
``--rpi-display=<number>``
|
||||
Select the display number on which the video overlay should be shown
|
||||
@@ -602,15 +548,18 @@ Available video output drivers are:
|
||||
|
||||
The following global options are supported by this video output:
|
||||
|
||||
``--drm-connector=<name>``
|
||||
``--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. (default: empty)
|
||||
connectors. The ``<gpu_number>`` argument can be used to disambiguate
|
||||
multiple graphic cards, but is deprecated in favor of ``--drm-device``.
|
||||
(default: empty)
|
||||
|
||||
``--drm-device=<path>``
|
||||
Select the DRM device file to use. If specified this overrides automatic
|
||||
card selection. (default: empty)
|
||||
card selection and any card number specified ``--drm-connector``.
|
||||
(default: empty)
|
||||
|
||||
``--drm-mode=<preferred|highest|N|WxH[@R]>``
|
||||
Mode to use (resolution and frame rate).
|
||||
@@ -628,6 +577,16 @@ Available video output drivers are:
|
||||
Use ``--drm-mode=help`` to get a list of available modes for all active
|
||||
connectors.
|
||||
|
||||
``--drm-atomic=<no|auto>``
|
||||
Toggle use of atomic modesetting. Mostly useful for debugging.
|
||||
|
||||
:no: Use legacy modesetting.
|
||||
:auto: Use atomic modesetting, falling back to legacy modesetting if
|
||||
not available. (default)
|
||||
|
||||
Note: Only affects ``gpu-context=drm``. ``vo=drm`` supports legacy
|
||||
modesetting only.
|
||||
|
||||
``--drm-draw-plane=<primary|overlay|N>``
|
||||
Select the DRM plane to which video and OSD is drawn to, under normal
|
||||
circumstances. The plane can be specified as ``primary``, which will
|
||||
@@ -636,11 +595,11 @@ Available video output drivers are:
|
||||
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.
|
||||
When using this option with the drmprime-drm hwdec interop, only the OSD
|
||||
is rendered to this plane.
|
||||
|
||||
``--drm-drmprime-video-plane=<primary|overlay|N>``
|
||||
Select the DRM plane to use for video with the drmprime-overlay hwdec
|
||||
Select the DRM plane to use for video with the drmprime-drm hwdec
|
||||
interop (used by e.g. the rkmpp hwdec on RockChip SoCs, and v4l2 hwdec:s
|
||||
on various other SoC:s). The plane is unused otherwise. This option
|
||||
accepts the same values as ``--drm-draw-plane``. (default: overlay)
|
||||
@@ -666,28 +625,17 @@ Available video output drivers are:
|
||||
``--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.
|
||||
useful when used together with the drmprime-drm hwdec interop at high
|
||||
resolutions, as it allows scaling the draw plane (which in this case
|
||||
only handles the OSD) down to a size the GPU can handle.
|
||||
|
||||
When used without the drmprime-overlay hwdec interop this option will
|
||||
just cause the video to get rendered at a different resolution and then
|
||||
When used without the drmprime-drm hwdec interop this option will just
|
||||
cause the video to get rendered at a different resolution and then
|
||||
scaled to screen size.
|
||||
|
||||
Note: this option is only available with DRM atomic support.
|
||||
(default: display resolution)
|
||||
|
||||
``--drm-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.
|
||||
|
||||
``mediacodec_embed`` (Android)
|
||||
Renders ``IMGFMT_MEDIACODEC`` frames directly to an ``android.view.Surface``.
|
||||
Requires ``--hwdec=mediacodec`` for hardware decoding, along with
|
||||
@@ -697,8 +645,8 @@ 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``.
|
||||
To use hardware decoding with ``--vo=gpu`` instead, use
|
||||
``--hwdec=mediacodec-copy`` along with ``--gpu-context=android``.
|
||||
|
||||
``wlshm`` (Wayland only)
|
||||
Shared memory video output driver without hardware acceleration that works
|
||||
|
||||
@@ -214,7 +214,7 @@ Command Line Switches
|
||||
``-af volnorm`` (removed; use acompressor ffmpeg filter instead)
|
||||
``-afm hwac3`` ``--ad=spdif:ac3,spdif:dts``
|
||||
``-ao alsa:device=hw=0.3`` ``--ao=alsa:device=[hw:0,3]``
|
||||
``-aspect`` ``--video-aspect-override``
|
||||
``-aspect`` ``--video-aspect``
|
||||
``-ass-bottom-margin`` ``--vf=sub=bottom:top``
|
||||
``-ass`` ``--sub-ass``
|
||||
``-audiofile-cache`` (removed; the main cache settings are used)
|
||||
@@ -229,7 +229,7 @@ Command Line Switches
|
||||
``-endpos`` ``--length``
|
||||
``-fixed-vo`` (removed; always the default)
|
||||
``-font`` ``--osd-font``
|
||||
``-forcedsubsonly`` ``--sub-forced-events-only``
|
||||
``-forcedsubsonly`` ``--sub-forced-only``
|
||||
``-forceidx`` ``--index``
|
||||
``-format`` ``--audio-format``
|
||||
``-fsmode-dontuse`` (removed)
|
||||
@@ -321,9 +321,9 @@ input.conf and Slave Commands
|
||||
+--------------------------------+----------------------------------------+
|
||||
| ``pt_step -1 [0|1]`` | ``playlist-prev [weak|force] (same)`` |
|
||||
+--------------------------------+----------------------------------------+
|
||||
| ``switch_ratio [<ratio>]`` | ``set video-aspect-override <ratio>`` |
|
||||
| ``switch_ratio [<ratio>]`` | ``set video-aspect <ratio>`` |
|
||||
| | |
|
||||
| | ``set video-aspect-override 0`` (reset)|
|
||||
| | ``set video-aspect 0`` (reset aspect) |
|
||||
+--------------------------------+----------------------------------------+
|
||||
| ``step_property_osd <prop>`` | ``cycle <prop> <step>`` (wraps), |
|
||||
| ``<step> <dir>`` | ``add <prop> <step>`` (clamps). |
|
||||
|
||||
+24
-41
@@ -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.
|
||||
@@ -33,31 +31,26 @@ While on master:
|
||||
|
||||
- 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 release branch (`release/0.X`) 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 +59,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 +82,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
|
||||
+28
-27
@@ -377,7 +377,7 @@ 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
|
||||
Libraries are often not library safe, because they they use global mutable state
|
||||
or other "global" resources. Typical examples include use of signals, simple
|
||||
global variables (like hsearch() in libc), or internal caches not protected by
|
||||
locks.
|
||||
@@ -430,11 +430,12 @@ like a log file or the internal console.lua script.
|
||||
Locking
|
||||
-------
|
||||
|
||||
See generally available literature. In mpv, we use mp_thread for this.
|
||||
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, but most likely you will only hurt yourself and others.
|
||||
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
|
||||
@@ -480,7 +481,7 @@ 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,
|
||||
"Callback hell" is when when components call both up and down the hierarchy,
|
||||
which for example leads to accidentally recursion, reentrancy problems, or
|
||||
locking nightmares. This is avoided by (mostly) calling only down the hierarchy.
|
||||
Basically the call graph forms a DAG. The other direction is handled by event
|
||||
@@ -555,13 +556,13 @@ Condition variables
|
||||
-------------------
|
||||
|
||||
They're used whenever a thread needs to wait for something, without nonsense
|
||||
like sleep calls or busy waiting. mpv uses the mp_thread API for this.
|
||||
There's a lot of literature on condition variables, threading in general. Read it.
|
||||
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. mp_cond does not have any
|
||||
state per-se. Maybe mp_cond would better be named mp_interrupt,
|
||||
because its sole purpose is to interrupt a thread waiting via mp_cond_wait()
|
||||
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.
|
||||
@@ -570,24 +571,24 @@ The very short version is...
|
||||
|
||||
Shared declarations:
|
||||
|
||||
mp_mutex lock;
|
||||
mp_cond cond_var;
|
||||
pthread_mutex_t lock;
|
||||
pthread_cond_t cond_var;
|
||||
struct something state_var; // protected by lock, changes signaled by cond_var
|
||||
|
||||
Waiter thread:
|
||||
|
||||
mp_mutex_lock(&lock);
|
||||
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. mp_cond_wait() can have sporadic wakeups
|
||||
// 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.
|
||||
mp_cond_wait(&cond_var, &lock);
|
||||
pthread_cond_wait(&cond_var, &lock);
|
||||
}
|
||||
|
||||
// Here you may react to the state change. The state cannot change
|
||||
@@ -595,43 +596,43 @@ Waiter thread:
|
||||
// and reacquire it).
|
||||
// ...
|
||||
|
||||
mp_mutex_unlock(&lock);
|
||||
pthread_mutex_unlock(&lock);
|
||||
|
||||
Signaler thread:
|
||||
|
||||
mp_mutex_lock(&lock);
|
||||
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 mp_cond_wait(). If not, nothing happens.
|
||||
mp_cond_broadcast(&cond_var);
|
||||
// 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.
|
||||
mp_mutex_unlock(&lock);
|
||||
pthread_mutex_unlock(&lock);
|
||||
|
||||
Some basic rules:
|
||||
1. Always access your state under proper locking
|
||||
2. Always check your predicate before every call to mp_cond_wait()
|
||||
(And don't call mp_cond_wait() if the predicate is fulfilled.)
|
||||
3. Always call mp_cond_wait() in a loop
|
||||
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 mp_cond_broadcast()/_signal() inside of its associated
|
||||
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 mp_cond is some kind of semaphore, or holds any
|
||||
- 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 mp_cond_wait() and friends,
|
||||
that are at the same time blocking on pthread_cond_wait() and friends,
|
||||
nothing else)
|
||||
- Changing the predicate, but not updating all mp_cond_broadcast()/
|
||||
- Changing the predicate, but not updating all pthread_cond_broadcast()/
|
||||
_signal() calls correctly
|
||||
- Forgetting that mp_cond_wait() unlocks the lock (other threads can
|
||||
- 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)
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
waf build system overview
|
||||
=========================
|
||||
|
||||
mpv's new build system is based on waf and it should completely replace the
|
||||
custom ./configure + Makefile based system inherited from MPlayer.
|
||||
|
||||
Goals and the choice of waf
|
||||
===========================
|
||||
|
||||
The new system comes with some goals, which can be summed up as: be as good as
|
||||
the old one at what it did well (customizability) and fix some of it's major
|
||||
shortcomings:
|
||||
|
||||
1) The build system must be uniform in how it handles any single feature check.
|
||||
Repetition and boilerplate have to be avoided.
|
||||
|
||||
When adding a new feature using the old configure, one had to add a fair
|
||||
amount of code to the shell script to do option parsing, detection of the
|
||||
feature and declaration of variables for the Makefile to pickup. The worst
|
||||
part is this pieces are spread apart in the configure and copy pasted for
|
||||
any single case. That brings us to..
|
||||
|
||||
2) --enable-feature has to be overridden by the user and helps them understand that
|
||||
they have libraries missing and should install them for the feature to be
|
||||
enabled.
|
||||
|
||||
3) Must be customizable, hackable, pleasant to the developer eyes and to work
|
||||
with in general.
|
||||
|
||||
4) Must have separate configuration and build steps.
|
||||
|
||||
Goal 2 comes as a given on pretty much any build system, since autotools made
|
||||
this behaviour very popular among users (and rightly so).
|
||||
|
||||
Goal 1+3 were somewhat harder to accomplish as it looks like all of the build
|
||||
systems we evaluated (waf included!) had problems with them. For reference we
|
||||
had proof of concept build systems with waf, CMake and autotools.
|
||||
|
||||
What puts waf apart from CMake and autotools, is that projects using it use
|
||||
Python to program their build system. Also while the Waf Book shows really
|
||||
simple API usages, you can write your own build system on top of waf that is
|
||||
tailored to the project's specific needs.
|
||||
|
||||
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/>`_.
|
||||
|
||||
All of the configuration process is handled with a declarative approach. Lists
|
||||
of dictionaries define the checks, and some custom Python code traverses these
|
||||
lists and depending on the check definition it calls into the actual waf API.
|
||||
|
||||
A simple example using pkg-config would be::
|
||||
|
||||
{
|
||||
'name': '--vdpau',
|
||||
'desc': 'VDPAU acceleration',
|
||||
'deps': [ 'x11' ],
|
||||
'func': check_pkg_config('vdpau', '>= 0.2'),
|
||||
}
|
||||
|
||||
This defines a feature called ``vdpau`` which can be enabled or disabled by
|
||||
the users with configure flags (that's the meaning of ``--``). This feature
|
||||
depends on another feature whose name is ``x11``, and the autodetection check
|
||||
consists of running ``pkg-config`` and looking for ``vdpau`` with version
|
||||
``>= 0.2``. If the check succeeds a ``#define HAVE_VDPAU 1`` will be added to
|
||||
``config.h``, if not ``#define HAVE_VDPAU 0`` will be added.
|
||||
|
||||
The defines names are automatically prepended with ``HAVE_``, capitalized and
|
||||
special characters are replaced with underscores. This happens in
|
||||
``waftools/inflectors.py``.
|
||||
|
||||
Mandatory fields:
|
||||
-----------------
|
||||
|
||||
``name``: indicates the unique identifier used by the custom dependency code
|
||||
to refer to a feature. If the unique identifier is prepended with ``--``
|
||||
the build system will also generate options for ``./waf configure`` so that
|
||||
the feature can be enabled and disabled.
|
||||
|
||||
``desc``: this is the textual representation of the feature used in the
|
||||
interactions with the users.
|
||||
|
||||
``func``: function that will perform the check. These functions are defined in
|
||||
``waftools/checks``. The reusable checks are all functions that return
|
||||
functions. The return functions will then be applied using waf's configuration
|
||||
context.
|
||||
|
||||
The source code for the reusable checks is a bit convoluted, but it should be
|
||||
easy to pick up their usage from the ``wscript``. Their signature mirrors
|
||||
the semantics of some of the shell functions used in mplayer.
|
||||
|
||||
If someone expresses some interest, I will extend this document with official
|
||||
documentation for each check function.
|
||||
|
||||
Optional fields
|
||||
---------------
|
||||
|
||||
``deps``: list of dependencies of this feature. It is a list of names of
|
||||
other features as defined in the ``name`` field (minus the eventual leading
|
||||
``--``). All of the dependencies must be satisfied. If they are not the check
|
||||
will be skipped without even running ``func``.
|
||||
|
||||
``deps_any``: like deps but it is satisfied even if only one of the dependencies
|
||||
is satisfied. You can think of ``deps`` as a 'and' condition and ``deps_any``
|
||||
as a 'or' condition.
|
||||
|
||||
``deps_neg``: like deps but it is satisfied when none of the dependencies is
|
||||
satisfied.
|
||||
|
||||
``req``: defaults to False. If set to True makes this feature a hard
|
||||
dependency of mpv (configuration will fail if autodetection fails). If set to
|
||||
True you must also provide ``fmsg``.
|
||||
|
||||
``fmsg``: string with the failure message in case a required dependency is not
|
||||
satisfied.
|
||||
|
||||
``os_specific_checks``: this takes a dictionary that has ``os-`` dependencies
|
||||
as keys (such as ``os-win32``), and by values has another dictionary that is
|
||||
merged on top of the current feature definition only for that specific OS.
|
||||
For example::
|
||||
|
||||
{
|
||||
'name': '--pthreads',
|
||||
'desc': 'POSIX threads',
|
||||
'func': check_pthreads,
|
||||
'os_specific_checks': {
|
||||
'os-win32': {
|
||||
'func': check_pthreads_w32_static.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
will override the value of ``func`` with ``check_pthreads_w32_static`` only
|
||||
if the target OS of the build is Windows.
|
||||
|
||||
``groups``: groups a dependency with another one. This can be used to disabled
|
||||
all the grouped dependencies with one ``--disable-``. At the moment this is
|
||||
only used for OpenGL backends, where you want to disable them when
|
||||
``--disable-gl`` is passed to the configure.
|
||||
|
||||
mpv's custom build step on top of waf
|
||||
=====================================
|
||||
|
||||
Build step is pretty much vanilla waf. The only difference being that the list
|
||||
of source files can contain both strings or tuples. If a tuple is found,
|
||||
the second element in the tuple will be used to match the features detected
|
||||
in the configure step (the ``name`` field described above). If this feature
|
||||
was not enabled during configure, the source file will not be compiled in.
|
||||
|
||||
All of the custom Python for this is inside the function ``filtered_sources``
|
||||
contained in the file ``waftools/dependencies.py``.
|
||||
|
||||
Also ``dependencies_use`` and ``dependencies_includes`` collect cflags and
|
||||
ldflags that were generated from the features checks in the configure step.
|
||||
@@ -36,7 +36,7 @@ Releases can be found on the [release list][releases].
|
||||
|
||||
## System requirements
|
||||
|
||||
- A not too ancient Linux, Windows 10 or later, or macOS 10.15 or later.
|
||||
- A not too ancient Linux, Windows 7 or later, or OSX 10.8 or later.
|
||||
- A somewhat capable CPU. Hardware decoding might help if the CPU is too slow to
|
||||
decode video in realtime, but must be explicitly enabled with the `--hwdec`
|
||||
option.
|
||||
@@ -49,11 +49,6 @@ Releases can be found on the [release list][releases].
|
||||
output methods can help (such as `--vo=xv` on Linux), but this use is not
|
||||
recommended or supported.
|
||||
|
||||
mpv does not go out of its way to break on older hardware or old, unsupported
|
||||
operating systems, but development is not done with them in mind. Keeping
|
||||
compatibility with such setups is not guaranteed. If things work, consider it
|
||||
a happy accident.
|
||||
|
||||
## Downloads
|
||||
|
||||
|
||||
@@ -78,29 +73,42 @@ Changes to the default key bindings are indicated in
|
||||
|
||||
|
||||
Compiling with full features requires development files for several
|
||||
external libraries. Mpv requires [meson](https://mesonbuild.com/index.html)
|
||||
to build. Meson can be obtained from your distro or PyPI.
|
||||
external libraries. Below is a list of some important requirements.
|
||||
|
||||
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.
|
||||
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
|
||||
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`.
|
||||
|
||||
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
|
||||
to the *prefix* after it is compiled.
|
||||
|
||||
Example:
|
||||
|
||||
meson setup build
|
||||
meson compile -C build
|
||||
meson install -C build
|
||||
./bootstrap.py
|
||||
./waf configure
|
||||
./waf
|
||||
./waf install
|
||||
|
||||
Essential dependencies (incomplete list):
|
||||
|
||||
- gcc or clang
|
||||
- X development headers (xlib, xrandr, xext, xscrnsaver, xpresent, libvdpau,
|
||||
- X development headers (xlib, xrandr, xext, xscrnsaver, xinerama, libvdpau,
|
||||
libGL, GLX, EGL, xv, ...)
|
||||
- Audio output development headers (libasound/ALSA, pulseaudio)
|
||||
- FFmpeg libraries (libavutil libavcodec libavformat libswscale libavfilter
|
||||
and either libswresample or libavresample)
|
||||
- libplacebo
|
||||
- zlib
|
||||
- iconv (normally provided by the system libc)
|
||||
- libass (OSD, OSC, text subtitles)
|
||||
@@ -142,12 +150,12 @@ or cross-compile from Linux with MinGW. See
|
||||
|
||||
## Release cycle
|
||||
|
||||
Once or twice a year, a release is cut off from the current development state
|
||||
and is assigned a 0.X.0 version number. No further maintenance is done, except
|
||||
in the event of security issues.
|
||||
Every other month, an arbitrary git snapshot is made, and is assigned
|
||||
a 0.X.0 version number. No further maintenance is done.
|
||||
|
||||
The goal of releases is to make Linux distributions happy. Linux distributions
|
||||
are also expected to apply their own patches in case of bugs.
|
||||
are also expected to apply their own patches in case of bugs and security
|
||||
issues.
|
||||
|
||||
Releases other than the latest release are unsupported and unmaintained.
|
||||
|
||||
@@ -178,7 +186,7 @@ for ideas on what you could contribute with.
|
||||
|
||||
## License
|
||||
|
||||
GPLv2 "or later" by default, LGPLv2.1 "or later" with `-Dgpl=false`.
|
||||
GPLv2 "or later" by default, LGPLv2.1 "or later" with `--enable-lgpl`.
|
||||
See [details.](https://github.com/mpv-player/mpv/blob/master/Copyright)
|
||||
|
||||
## History
|
||||
|
||||
+112
-160
@@ -1,43 +1,67 @@
|
||||
Release 0.37.0
|
||||
Release 0.34.1
|
||||
==============
|
||||
|
||||
This release requires FFmpeg 4.4 or newer and libplacebo 6.338.0 or newer.
|
||||
Bug fix release for the 0.34 branch.
|
||||
|
||||
This is the first release to unconditionally require libplacebo, but note that
|
||||
the new improved renderer (vo_gpu_next) is not yet the default.
|
||||
Fixes and Minor Enhancements
|
||||
----------------------------
|
||||
|
||||
- player: fix autofit/geometry related segfault
|
||||
- ao_oss: add missing PATH_DEV_MIXER define
|
||||
- egl_helpers: fix context creation if GLES 3 is not available
|
||||
- audio: stop corrupting audio on underreads
|
||||
- wayland: fix a potential segfault on surface enter
|
||||
- vo_gpu: opengl: fix wrong glMemoryBarrier call
|
||||
- vo_gpu: opengl: create context at version 4.4 again
|
||||
- af_lavcac3enc: fix memory leak on no-op
|
||||
- subprocess: unblock/reset signals before running child process
|
||||
- wayland: avoid doing unneccesary window resizes
|
||||
|
||||
|
||||
Release 0.34.0
|
||||
==============
|
||||
|
||||
This release requires FFmpeg 4.0 or newer.
|
||||
|
||||
For packagers: Note that mpv's build system is only supported on Python 3.
|
||||
If you use the `bootstrap.py` script it will take care of this, otherwise you
|
||||
need to explicitly invoke the build system using `python3 waf`.
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
New
|
||||
~~~
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- ao_oss: add SPDIF passthrough support
|
||||
- hwtransfer: implement support for HW->HW format conversions
|
||||
- stream/dvbin: add support for delivery system ISDB-T
|
||||
- audio/chmap: support up to 64 channels (including 22.2 layout)
|
||||
- libmpv: add mpv_time_ns()
|
||||
- vo_gpu, vo_gpu_next: add Vulkan support for macOS
|
||||
- meson: make libplacebo a required dependency
|
||||
- hwdec: support videotoolbox hwdec with libplacebo
|
||||
- player: allow vo to be switched at runtime
|
||||
- input.conf syntax: support custom quotes in `XstringX` form & single quotes
|
||||
- ao_ass: readd OSSv4 audio output, default on *BSD
|
||||
- player: load cover art with the media filename
|
||||
- vo_gpu: vulkan: implement a VkDisplayKHR backed context
|
||||
- osc: seekbar hover/drag: display target chapter at the OSC title
|
||||
- stats.lua: add page 4 with a list of active key bindings
|
||||
- sub: add --sub-filter-jsre (JS regex)
|
||||
- vo_rpi: restore fullscreen handling
|
||||
- vo_tct: add resize capability
|
||||
- ytdl_hook.lua: search for yt-dlp by default (preferred over youtube-dl)
|
||||
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- msg: print warning and error messages to stderr
|
||||
- options: restore old default subtitle selection behavior
|
||||
- input.conf: swap wheel up/down with wheel left/right
|
||||
- vo_sixel: many fixes and improvements
|
||||
- filters: switch from scaletempo to scaletempo2, used for `speed` option
|
||||
- audio/vo_pulse: fix various edge cases and issues
|
||||
- stats.lua: move internal performance info from page 4 to page 0
|
||||
- command: don't hardcode command lists to be repeatable
|
||||
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- waf: remove waf as a build system
|
||||
- osc.lua: remove toggle for forced only subpictures (appeared as [F])
|
||||
- mac: remove runtime checks and compatibility for macOS older than 10.15
|
||||
- cocoa: remove deprecated OpenGL cocoa backend
|
||||
- mac: drop build support for swift versions earlier than 4.1
|
||||
- vo_gpu: drop support for libplacebo older than v3.104.0
|
||||
|
||||
|
||||
Options and Commands
|
||||
@@ -46,169 +70,97 @@ Options and Commands
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- vo_gpu_next: add --hdr-peak-percentile
|
||||
- player: add --term-remaining-playtime option
|
||||
- x11: add --x11-wid-title option
|
||||
- vo_gpu_next: add --libplacebo-opts
|
||||
- player: add --subs-match-os-language option (replaces 'auto' option)
|
||||
- vo: add --video-crop
|
||||
- win32: add --window-corners, --window-affinity, --title-bar, --backdrop-type
|
||||
- sub: add --sub-stretch-durations option
|
||||
- vo_sixel: add --vo-sixel-exit-clear
|
||||
- player: add --screen-name and --fs-screen-name
|
||||
- player/command: add albumart argument to video-add
|
||||
- command: add pid (process id), display-width and display-height properties
|
||||
- sub: add secondary-sub-text, -start, and -end properties; secondary-sub-visibility option
|
||||
- vo_gpu: add --icc-force-contrast
|
||||
- player: add append-play flag to loadlist
|
||||
- command: add support for secondary subs to sub-seek and sub-step
|
||||
- options: add watch-later-options to configure which options are saved
|
||||
- player: add track-list/N/image sub-property
|
||||
- input: add --no-input-builtin-bindings option
|
||||
- drm_common: add --drm-device option
|
||||
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- builtin.conf: add --hdr-peak-percentile=99.995 to gpu-hq profile
|
||||
- player: add 'always' option to --subs-fallback-forced
|
||||
- demux_playlist: default to --directory-mode=lazy
|
||||
- builtin.conf: add --allow-delayed-peak-detect=no to gpu-hq profile
|
||||
- vo_gpu, vo_gpu_next: support --icc-3dlut-size=auto
|
||||
- demux: prepend some cache options with --demuxer-
|
||||
- builtin.conf: modernize internal profiles for higher quality rendering by default,
|
||||
rename 'gpu-hq' profile to 'high-quality', add 'fast' profile
|
||||
- vo_gpu, vo_gpu_next: default to dscale=hermite
|
||||
- builtin.conf: remove deprecated 'opengl-hq' profile
|
||||
- options: remove a bunch of old option fallbacks/deprecated ones
|
||||
- vo_gpu: allow --deband-iterations to be 0
|
||||
- stream_cdda: deprecate --cdda-toc-bias and always check for offsets
|
||||
- options: disable --allow-delayed-peak-detect by default
|
||||
- options: adjust default of --watch-later-options
|
||||
- vo_sixel: change default dither to "auto"
|
||||
- vd_lavc: add VP8 to the default allowed hwdec codec list
|
||||
- vo_gpu: lower default deband threshold to preserve more detail
|
||||
- vo_gpu: adjust interpolation-threshold's default
|
||||
- demux: undeprecate --cache-secs
|
||||
- options: add `Uploader`, `Channel_URL` to --display-tags defaults
|
||||
- options: --audio-display determines cover priority
|
||||
- command: make current-window-scale writeable
|
||||
|
||||
|
||||
Deprecated
|
||||
~~~~~~~~~~
|
||||
|
||||
- command: deprecate shared-script-properties
|
||||
- demux_cue: deprecate --demuxer-cue-codepage for --metadata-codepage
|
||||
- win32: ignore and deprecate --fit-border
|
||||
- drm_common: deprecate selecting by card number in --drm-connector
|
||||
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- player: remove special 'auto' option from alang/slang/vlang (previous default)
|
||||
- vo_gpu: remove --tone-mapping-mode
|
||||
- vo_gpu: remove --scale-wblur, --scale-cutoff etc.
|
||||
- vo_gpu: remove --scaler-lut-size
|
||||
- m_option: drop support for -del for list options
|
||||
- vo_gpu: remove --icc-contrast
|
||||
- stats.lua: remove script-opts for the main keys (i/I)
|
||||
- vo_gpu: opengl: remove --opengl-restrict
|
||||
|
||||
|
||||
Fixes and Minor Enhancements
|
||||
----------------------------
|
||||
|
||||
- build: remove unneeded libdl requirement for vaapi
|
||||
- zimg: fix abort on subsampled input with odd heights
|
||||
- video_writer: fix gamma for YUV screenshots
|
||||
- player/video: fix possible crash when changing lavfi-complex
|
||||
- ad_spdif: fix segfault due to early deallocation
|
||||
- ao_pipewire: fix race conditon with setting the media role
|
||||
- draw_bmp: fix overflowing coordinates in mark_rcs
|
||||
- ao_sndio: use sio_flush() to improve controls responsiveness
|
||||
- vo_vdpau: fix hwdec for this vo
|
||||
- vo_gpu, vo_gpu_next: fix setting an empty dscale
|
||||
- vd_lavc: repeatedly attempt to fallback if hwdec fails in reinit
|
||||
- options: fix relative time parsing on negative input
|
||||
- win32: signal DPI changes correctly
|
||||
- mp_image: properly infer color levels for some pixfmts
|
||||
- vo_gpu_next: add ability to use named hook params
|
||||
- vo_gpu_next: take into account PAR when taking screenshots
|
||||
- ao_audiotrack: support more channel layouts
|
||||
- osc.lua: support speed-independent time-remaining display
|
||||
- sub: fix switching tracks while paused
|
||||
- audio: fix clipping with gapless audio enabled
|
||||
- player/video: avoid spamming logs with EOF
|
||||
- player/command: detect aspect ratio name and show it in stats.lua
|
||||
- wayland: keyboard input fixes
|
||||
- demux_playlist: remove len restriction on headerless m3u
|
||||
- win32: fix display resolution calculation on mulitple monitors
|
||||
- vo_gpu_next: multiple adjustments and fixes to interpolation
|
||||
- loadfile: avoid infinite playlist loading loops
|
||||
- context_drm_egl: don't free EGL properties if they are null
|
||||
- x11: require xrandr 1.4, remove xinerama support
|
||||
- drm_common: skip cards that don't have connected outputs
|
||||
- win32_common: fixes minimized window being focused on launch
|
||||
- ao/jack: set device_buffer to JACK buffer size
|
||||
- meson: rename all features with underscores
|
||||
- input: add new keys: Back, Tools, ZoomIn, ZoomOut
|
||||
- win32: don't ignore --screen and --fs-screen
|
||||
- input: add missing keypad key defines and Windows multimedia keys
|
||||
- player: use audio pts corresponding to playing audio on EOF
|
||||
- command: add sub-ass-extradata property
|
||||
- vo_dmabuf_wayland: unmap osd surface when not needed
|
||||
- player: always write redirect entries for resuming playback
|
||||
- stats.lua: reorganize how video params are displayed
|
||||
- stats.lua: display HDR metadata
|
||||
- osc.lua: add scrolling to the seekbar
|
||||
- demux_lavf: prefer track durations over container durations to determine total
|
||||
- vo: vulkan: allow picking devices by UUID
|
||||
- video: allow overriding container crop if it is present
|
||||
- vo_gpu, vo_gpu_next, screenshot: support applying crop for screenshots
|
||||
- sd_lavc: account for floating point inaccuracy, fix sub PTS
|
||||
- stream: accept dav:// and davs:// urls
|
||||
- filter_kernels: refine some constants
|
||||
- filter_kernels: add ewa_lanczos4sharpest
|
||||
- osc.lua: add scrolling to audio/sub buttons
|
||||
- demux_mkv: support cropping and rotation
|
||||
- vo_dmabuf_wayland: support 90 degree rotations
|
||||
- filter_kernels: add hermite filter
|
||||
- vo: avoid unnecessary redraws when the OSD shows
|
||||
- scripting: support DLL cplugins
|
||||
- af_scaletempo2: various bug fixes
|
||||
- sdl_gamepad: fix button detection on modern controllers
|
||||
- vo_dmabuf_wayland: support osd rendering when there's no video
|
||||
- demux_playlist: add --directory-mode=auto
|
||||
- vo_gpu_next: use proper color for subtitles
|
||||
- win32: add an option to change window affinity and control title bar state
|
||||
- win32: reduce top border thickness to imitate DWM invisible borders
|
||||
- wayland: remove gnome-specific idle-inhibit warning
|
||||
- win32: pass window handle to the window-id property
|
||||
- osc.lua: fix calculation for slider's min-max average
|
||||
- recorder: fix a couple of memory leaks
|
||||
- af_scaletempo2: raise max playback rate to 8.0
|
||||
- osc.lua: move the idle logo behind other overlays
|
||||
- hwdec_drmprime: add nv16 support
|
||||
- various: change internal timing code to work in nanoseconds instead of microseconds
|
||||
- vo: increase display refresh rate estimation limit from 99 Hz to 400 Hz
|
||||
- external_files: base cover-art-whitelist on cover-art-auto-exts
|
||||
- path: don't override cache and state paths with --config-dir
|
||||
- codec_tags: map some more image mimetypes
|
||||
- af/vf-command: add ability to target a specific lavfi filter
|
||||
- win32: prevent white window flash on startup
|
||||
- demux_playlist: use --metacode-codepage when parsing playlist files
|
||||
- video: revert racey change that led to stutter and deadlocking
|
||||
- console.lua: various improvements
|
||||
- command: add playlist-next-playlist and playlist-prev-playlist
|
||||
- ytdl_hook.lua: set metadata with single tracks
|
||||
- defaults.lua: add a disabled parameter to timer constructors
|
||||
- terminal-unix: race condition fixes
|
||||
- af_scaletempo2: better defaults
|
||||
- hwtransfer: handle hwcontexts that don't implement frame constraints
|
||||
- stream_cdda: remove fallback for ancient libcdio versions
|
||||
- osdep: drop support for C11 without atomics
|
||||
- dvbin: do a big cleanup on code style and functions
|
||||
- ytdl_hook.lua: parse the separate cookies field
|
||||
- sub: update subtitles if current track is an image
|
||||
- javascript: use --js-memory-report option instead of MPV_LEAK_REPORT
|
||||
- ao_coreaudio: signal buffer underruns
|
||||
- ytdl_hook.lua: support thumbnails
|
||||
- demux: make hysteresis-secs respect cache-secs
|
||||
- mp_image: pass rotation correctly to/from AVFrame correctly
|
||||
- various: add new internal mp_thread abstraction and use it
|
||||
- drm: use present_sync mechanism for presentation feedback
|
||||
- vo_gpu: apply ICC profile and dithering only to window screenshots
|
||||
- audio: introduce ao_read_data_nonblocking() and use it in relevant backends
|
||||
- wayland: obey initial size hints set by the compositor
|
||||
- command: export storage aspect ratio (sar) properties
|
||||
- vo: delay vsync samples by at least 10 refreshes to improve convergence time
|
||||
- vo_sdl: fix broken mouse wheel multiplier
|
||||
- vo_gpu_next: simplify cache code and don't re-save when unmodified
|
||||
- terminal: correctly process input when foregrounded
|
||||
- vo_wlshm: support presentation time
|
||||
- mac: fix a window positioning bug when exiting fullscreen
|
||||
- csputils: add mappings for DCI-P3 (ST.431-2) and P3-D65 (ST.432-1)
|
||||
- player: make resetting of track selection to "auto" work
|
||||
- stream_lavf: support rtsps
|
||||
- vo_wlshm: support big endian systems
|
||||
- demux_mf: add support for more image codecs
|
||||
- msg: fix really-quiet option to only affect terminal output
|
||||
- mac: fix traditional fullscreen on macOS 11
|
||||
- ao_pulse: fix misbehavior with PipeWire when setting volume or mute
|
||||
- stats.lua: include a filter's @label when displaying filters on page 1
|
||||
- wayland, win32: support the display-hidpi-scale property
|
||||
- win32: keep the window title-bar inside the screen
|
||||
- filter_kernels: fix incorrect constant for quadric window
|
||||
- command: add display-width/display-height property
|
||||
- edl: add a way to add tags
|
||||
- stream_file: disable readahead for remote files on macOS to fix stutter
|
||||
- osxbundle: fix slow and wasteful memory allocation that caused stutter
|
||||
- vo_gpu: fix extreme clipping with --gamut-clipping for HDR outputs
|
||||
- af_scaletempo2: speed up using vector calculations
|
||||
- recorder: fix muxing of certain codecs
|
||||
- win32: support the focused property
|
||||
- js: add mp.utils.append_file
|
||||
- subs: fix missing subtitles on last frame
|
||||
- stats.lua: show scaled resolution
|
||||
- osc: expose osc-visibility via shared-script-properties
|
||||
- terminal-unix: fix ^Z identification and ignore unknown CSI sequences
|
||||
- context_drm_egl: allow autoprobe selection
|
||||
- wayland: improve behavior with touch events
|
||||
- command: make current-window-scale writeable
|
||||
- vo_vdpau: don't treat preemption as an error to fix VT switching
|
||||
- wayland: read XCURSOR_THEME to get cursor theme
|
||||
- demux_playlist: extend maximum line size (again) to 2M
|
||||
- win32: call timeBeginPeriod on demand on Windows 10
|
||||
- build: allow easier selection of lua version/package
|
||||
- vo_gpu: fix distortion with certain rotated videos
|
||||
- drm_common: skip non-primary devices during automatic choosing
|
||||
- drm_common: support USB, SPI, Writeback and unknown connector types (future proofing)
|
||||
|
||||
|
||||
This listing is not complete. Check DOCS/client-api-changes.rst for a history
|
||||
of changes to the client API, and DOCS/interface-changes.rst for a history
|
||||
of changes to other user-visible interfaces.
|
||||
|
||||
A complete changelog can be seen by running `git log v0.36.0..v0.37.0`
|
||||
A complete changelog can be seen by running `git log v0.33.0..v0.34.1`
|
||||
in the git repository or by visiting either
|
||||
https://github.com/mpv-player/mpv/compare/v0.36.0...v0.37.0 or
|
||||
https://git.srsfckn.biz/mpv/log/?qt=range&q=v0.36.0..v0.37.0
|
||||
https://github.com/mpv-player/mpv/compare/v0.33.0...v0.34.1 or
|
||||
https://git.srsfckn.biz/mpv/log/?qt=range&q=v0.33.0..v0.34.1
|
||||
Whitespace-only changes.
Executable
+24
@@ -0,0 +1,24 @@
|
||||
#!/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 \
|
||||
--enable-spirv-cross \
|
||||
--enable-d3d-hwaccel \
|
||||
--enable-d3d11 \
|
||||
--enable-jpeg \
|
||||
--enable-lcms2 \
|
||||
--enable-libarchive \
|
||||
--enable-lua \
|
||||
--enable-rubberband \
|
||||
--enable-shaderc \
|
||||
--enable-uchardet \
|
||||
--enable-vulkan
|
||||
"$PYTHON" waf build
|
||||
Executable
+54
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/bash
|
||||
set -e
|
||||
|
||||
export PYTHON=/usr/bin/python3
|
||||
|
||||
# Write an empty fonts.conf to speed up fc-cache
|
||||
export FONTCONFIG_FILE=/dummy-fonts.conf
|
||||
cat >"$FONTCONFIG_FILE" <<EOF
|
||||
<?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-cmake \
|
||||
$MINGW_PACKAGE_PREFIX-lcms2 \
|
||||
$MINGW_PACKAGE_PREFIX-libarchive \
|
||||
$MINGW_PACKAGE_PREFIX-libass \
|
||||
$MINGW_PACKAGE_PREFIX-libjpeg-turbo \
|
||||
$MINGW_PACKAGE_PREFIX-libplacebo \
|
||||
$MINGW_PACKAGE_PREFIX-lua51 \
|
||||
$MINGW_PACKAGE_PREFIX-ninja \
|
||||
$MINGW_PACKAGE_PREFIX-rubberband \
|
||||
$MINGW_PACKAGE_PREFIX-shaderc \
|
||||
$MINGW_PACKAGE_PREFIX-spirv-cross \
|
||||
$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
|
||||
)
|
||||
@@ -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)
|
||||
+13
-64
@@ -23,35 +23,10 @@ def is_user_lib(objfile, libname):
|
||||
not os.path.basename(objfile) in libname and \
|
||||
not "libswift" in libname
|
||||
|
||||
def otool(objfile, rapths):
|
||||
def otool(objfile):
|
||||
command = "otool -L '%s' | grep -e '\t' | awk '{ print $1 }'" % objfile
|
||||
output = subprocess.check_output(command, shell = True, universal_newlines=True)
|
||||
libs = set(filter(partial(is_user_lib, objfile), output.split()))
|
||||
|
||||
libs_resolved = set()
|
||||
libs_relative = set()
|
||||
for lib in libs:
|
||||
lib_path = resolve_lib_path(objfile, lib, rapths)
|
||||
libs_resolved.add(lib_path)
|
||||
if lib_path != lib:
|
||||
libs_relative.add(lib)
|
||||
|
||||
return libs_resolved, libs_relative
|
||||
|
||||
def get_rapths(objfile):
|
||||
rpaths = []
|
||||
command = "otool -l '%s' | grep -A2 LC_RPATH | grep path" % objfile
|
||||
pathRe = re.compile("^\s*path (.*) \(offset \d*\)$")
|
||||
|
||||
try:
|
||||
result = subprocess.check_output(command, shell = True, universal_newlines=True)
|
||||
except:
|
||||
return rpaths
|
||||
|
||||
for line in result.splitlines():
|
||||
rpaths.append(pathRe.search(line).group(1).strip())
|
||||
|
||||
return rpaths
|
||||
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
|
||||
@@ -64,29 +39,11 @@ def get_rpaths_dev_tools(binary):
|
||||
|
||||
return output
|
||||
|
||||
def resolve_lib_path(objfile, lib, rapths):
|
||||
if os.path.exists(lib):
|
||||
return lib
|
||||
|
||||
if lib.startswith('@rpath/'):
|
||||
lib = lib[len('@rpath/'):]
|
||||
for rpath in rapths:
|
||||
lib_path = os.path.join(rpath, lib)
|
||||
if os.path.exists(lib_path):
|
||||
return lib_path
|
||||
elif lib.startswith('@loader_path/'):
|
||||
lib = lib[len('@loader_path/'):]
|
||||
lib_path = os.path.normpath(os.path.join(objfile, lib))
|
||||
if os.path.exists(lib_path):
|
||||
return lib_path
|
||||
|
||||
raise Exception('Could not resolve library: ' + lib)
|
||||
|
||||
def install_name_tool_change(old, new, objfile):
|
||||
subprocess.call(["install_name_tool", "-change", old, new, objfile], stderr=subprocess.DEVNULL)
|
||||
subprocess.call(["install_name_tool", "-change", old, new, objfile])
|
||||
|
||||
def install_name_tool_id(name, objfile):
|
||||
subprocess.call(["install_name_tool", "-id", name, objfile], stderr=subprocess.DEVNULL)
|
||||
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])
|
||||
@@ -94,17 +51,15 @@ def 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(), result_relative = set(), rapths = []):
|
||||
rapths = get_rapths(objfile) + rapths
|
||||
libs_list, libs_relative = otool(objfile, rapths)
|
||||
def libraries(objfile, result = dict()):
|
||||
libs_list = otool(objfile)
|
||||
result[objfile] = libs_list
|
||||
result_relative |= libs_relative
|
||||
|
||||
for lib in libs_list:
|
||||
if lib not in result:
|
||||
libraries(lib, result, result_relative, rapths)
|
||||
libraries(lib, result)
|
||||
|
||||
return result, result_relative
|
||||
return result
|
||||
|
||||
def lib_path(binary):
|
||||
return os.path.join(os.path.dirname(binary), 'lib')
|
||||
@@ -112,15 +67,15 @@ def lib_path(binary):
|
||||
def lib_name(lib):
|
||||
return os.path.join("@executable_path", "lib", os.path.basename(lib))
|
||||
|
||||
def process_libraries(libs_dict, libs_dyn, binary):
|
||||
def process_libraries(libs_dict, binary):
|
||||
libs_set = set(libs_dict)
|
||||
# Remove binary from libs_set to prevent a duplicate of the binary being
|
||||
# Remove binary from libs_set to prevent a duplicate of the binary being
|
||||
# added to the libs directory.
|
||||
libs_set.remove(binary)
|
||||
|
||||
for src in libs_set:
|
||||
name = lib_name(src)
|
||||
dst = os.path.join(lib_path(binary), os.path.basename(src))
|
||||
dst = os.path.join(lib_path(binary), os.path.basename(src))
|
||||
|
||||
shutil.copy(src, dst)
|
||||
os.chmod(dst, 0o755)
|
||||
@@ -133,12 +88,6 @@ def process_libraries(libs_dict, libs_dyn, binary):
|
||||
if p in libs_dict[src]:
|
||||
install_name_tool_change(p, lib_name(p), dst)
|
||||
|
||||
for lib in libs_dyn:
|
||||
install_name_tool_change(lib, lib_name(lib), dst)
|
||||
|
||||
for lib in libs_dyn:
|
||||
install_name_tool_change(lib, lib_name(lib), binary)
|
||||
|
||||
def process_swift_libraries(binary):
|
||||
command = ['xcrun', '--find', 'swift-stdlib-tool']
|
||||
swiftStdlibTool = subprocess.check_output(command, universal_newlines=True).strip()
|
||||
@@ -166,10 +115,10 @@ def main():
|
||||
if not os.path.exists(lib_path(binary)):
|
||||
os.makedirs(lib_path(binary))
|
||||
print(">> gathering all linked libraries")
|
||||
libs, libs_rel = libraries(binary)
|
||||
libs = libraries(binary)
|
||||
|
||||
print(">> copying and processing all linked libraries")
|
||||
process_libraries(libs, libs_rel, binary)
|
||||
process_libraries(libs, binary)
|
||||
|
||||
print(">> removing rpath definitions towards dev tools")
|
||||
remove_dev_tools_rapths(binary)
|
||||
|
||||
@@ -39,6 +39,5 @@ def file2string(infilename, infile, outfile):
|
||||
outfile.write('"' + ''.join(conv[c] for c in line) + '"\n')
|
||||
|
||||
if __name__ == "__main__":
|
||||
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'
|
||||
|
||||
+134
-103
@@ -1,70 +1,103 @@
|
||||
--[[
|
||||
This script uses the lavfi cropdetect filter and the video-crop property to
|
||||
automatically crop the currently playing video with appropriate parameters.
|
||||
This script uses the lavfi cropdetect filter to automatically
|
||||
insert a crop filter with appropriate parameters for the
|
||||
currently playing video.
|
||||
|
||||
It automatically crops the video when playback starts.
|
||||
It will automatically crop the video, when playback starts.
|
||||
|
||||
You can also manually crop the video by pressing the "C" (shift+c) key.
|
||||
Pressing it again undoes the crop.
|
||||
Also It registers the key-binding "C" (shift+c). You can manually
|
||||
crop the video by pressing the "C" (shift+c) key.
|
||||
|
||||
The workflow is as follows: First, it inserts the cropdetect filter. After
|
||||
<detect_seconds> (default is 1) seconds, it then sets video-crop based on the
|
||||
vf-metadata values gathered by cropdetect. The cropdetect filter is removed
|
||||
after video-crop is set 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 filter and setting video-crop, 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 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.
|
||||
|
||||
If non-copy-back hardware decoding is in use, hwdec is temporarily disabled for
|
||||
the duration of cropdetect as the filter would fail otherwise.
|
||||
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).
|
||||
|
||||
These are the default options. They can be overridden by adding
|
||||
script-opts-append=autocrop-<parameter>=<value> to mpv.conf.
|
||||
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.
|
||||
--]]
|
||||
|
||||
require "mp.msg"
|
||||
require 'mp.options'
|
||||
|
||||
local options = {
|
||||
-- Whether to automatically apply crop at the start of playback. If you
|
||||
-- don't want to crop automatically, add
|
||||
-- script-opts-append=autocrop-auto=no to mpv.conf.
|
||||
auto = true,
|
||||
-- Delay before starting crop in auto mode. You can try to increase this
|
||||
-- value to avoid dark scenes or fade ins at beginning. Automatic cropping
|
||||
-- will not occur if the value is larger than the remaining playback time.
|
||||
auto_delay = 4,
|
||||
-- Black threshold for cropdetect. Smaller values will generally result in
|
||||
-- less cropping. See limit of
|
||||
-- https://ffmpeg.org/ffmpeg-filters.html#cropdetect
|
||||
detect_limit = "24/255",
|
||||
-- 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_round = 2,
|
||||
-- The ratio of the minimum clip size to the original. A number from 0 to
|
||||
-- 1. If the picture is over cropped, try adjusting this value.
|
||||
detect_min_ratio = 0.5,
|
||||
-- How long to gather cropdetect data. Increasing this may be desirable to
|
||||
-- allow cropdetect more time to collect data.
|
||||
detect_seconds = 1,
|
||||
-- Whether the OSD shouldn't be used when cropdetect and video-crop are
|
||||
-- applied and removed.
|
||||
suppress_osd = false,
|
||||
}
|
||||
read_options(options)
|
||||
|
||||
require "mp.options".read_options(options)
|
||||
|
||||
local cropdetect_label = mp.get_script_name() .. "-cropdetect"
|
||||
local label_prefix = mp.get_script_name()
|
||||
local labels = {
|
||||
crop = string.format("%s-crop", label_prefix),
|
||||
cropdetect = string.format("%s-cropdetect", label_prefix)
|
||||
}
|
||||
|
||||
timers = {
|
||||
auto_delay = nil,
|
||||
detect_crop = nil
|
||||
}
|
||||
|
||||
local hwdec_backup
|
||||
|
||||
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
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function is_enough_time(seconds)
|
||||
|
||||
-- Plus 1 second for deviation.
|
||||
@@ -74,64 +107,54 @@ function is_enough_time(seconds)
|
||||
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_cropdetect()
|
||||
for _, filter in pairs(mp.get_property_native("vf")) do
|
||||
if filter.label == cropdetect_label then
|
||||
mp.command(
|
||||
string.format("%s vf remove @%s", command_prefix, filter.label))
|
||||
|
||||
return
|
||||
function is_cropable()
|
||||
for _, track in pairs(mp.get_property_native('track-list')) do
|
||||
if track.type == 'video' and track.selected then
|
||||
return not track.albumart
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function restore_hwdec()
|
||||
if hwdec_backup then
|
||||
mp.set_property("hwdec", hwdec_backup)
|
||||
hwdec_backup = nil
|
||||
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_cropdetect()
|
||||
|
||||
-- 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()
|
||||
timers[index] = nil
|
||||
timer = nil
|
||||
end
|
||||
end
|
||||
|
||||
restore_hwdec()
|
||||
end
|
||||
|
||||
function detect_crop()
|
||||
local time_needed = options.detect_seconds
|
||||
|
||||
if not is_cropable(time_needed) then
|
||||
-- If it's not cropable, exit.
|
||||
if not is_cropable() then
|
||||
mp.msg.warn("autocrop only works for videos.")
|
||||
return
|
||||
end
|
||||
|
||||
local hwdec_current = mp.get_property("hwdec-current")
|
||||
if hwdec_current:find("-copy$") == nil and hwdec_current ~= "no" and
|
||||
hwdec_current ~= "crystalhd" and hwdec_current ~= "rkmpp" then
|
||||
hwdec_backup = mp.get_property("hwdec")
|
||||
mp.set_property("hwdec", "no")
|
||||
-- Verify if there is enough time to detect crop.
|
||||
local time_needed = options.detect_seconds
|
||||
|
||||
if not is_enough_time(time_needed) then
|
||||
mp.msg.warn("Not enough time to detect crop.")
|
||||
return
|
||||
end
|
||||
|
||||
-- Insert the cropdetect filter.
|
||||
@@ -141,7 +164,7 @@ function detect_crop()
|
||||
mp.command(
|
||||
string.format(
|
||||
'%s vf pre @%s:cropdetect=limit=%s:round=%d:reset=0',
|
||||
command_prefix, cropdetect_label, limit, round
|
||||
command_prefix, labels.cropdetect, limit, round
|
||||
)
|
||||
)
|
||||
|
||||
@@ -152,9 +175,13 @@ end
|
||||
function detect_end()
|
||||
|
||||
-- Get the metadata and remove the cropdetect filter.
|
||||
local cropdetect_metadata = mp.get_property_native(
|
||||
"vf-metadata/" .. cropdetect_label)
|
||||
remove_cropdetect()
|
||||
local cropdetect_metadata =
|
||||
mp.get_property_native(
|
||||
string.format("vf-metadata/%s",
|
||||
labels.cropdetect
|
||||
)
|
||||
)
|
||||
remove_filter(labels.cropdetect)
|
||||
|
||||
-- Remove the timer of detect crop.
|
||||
if timers.detect_crop then
|
||||
@@ -162,8 +189,6 @@ function detect_end()
|
||||
timers.detect_crop = nil
|
||||
end
|
||||
|
||||
restore_hwdec()
|
||||
|
||||
local meta = {}
|
||||
|
||||
-- Verify the existence of metadata.
|
||||
@@ -199,6 +224,7 @@ function detect_end()
|
||||
else
|
||||
mp.msg.error("Got empty crop data.")
|
||||
mp.msg.info("You might need to increase detect_seconds.")
|
||||
return
|
||||
end
|
||||
|
||||
apply_crop(meta)
|
||||
@@ -207,27 +233,32 @@ end
|
||||
function apply_crop(meta)
|
||||
|
||||
-- Verify if it is necessary to crop.
|
||||
local is_effective = meta.w and meta.h and meta.x and meta.y and
|
||||
(meta.x > 0 or meta.y > 0
|
||||
or meta.w < meta.max_w or meta.h < meta.max_h)
|
||||
local is_effective = meta.x > 0 or meta.y > 0
|
||||
or meta.w < meta.max_w or meta.h < meta.max_h
|
||||
|
||||
-- Verify it is not over cropped.
|
||||
local is_excessive = false
|
||||
if is_effective and (meta.w < meta.min_w or meta.h < meta.min_h) then
|
||||
mp.msg.info("The area to be cropped is too large.")
|
||||
mp.msg.info("You might need to decrease detect_min_ratio.")
|
||||
is_excessive = true
|
||||
end
|
||||
|
||||
if not is_effective or is_excessive then
|
||||
-- Clear any existing crop.
|
||||
mp.command(string.format("%s set file-local-options/video-crop ''", command_prefix))
|
||||
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 set file-local-options/video-crop %sx%s+%s+%s",
|
||||
command_prefix, meta.w, meta.h, meta.x, meta.y))
|
||||
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()
|
||||
@@ -251,7 +282,8 @@ function on_start()
|
||||
-- Verify if there is enough time for autocrop.
|
||||
local time_needed = options.auto_delay + options.detect_seconds
|
||||
|
||||
if not is_cropable(time_needed) then
|
||||
if not is_enough_time(time_needed) then
|
||||
mp.msg.warn("Not enough time for autocrop.")
|
||||
return
|
||||
end
|
||||
|
||||
@@ -278,8 +310,7 @@ function on_toggle()
|
||||
end
|
||||
|
||||
-- Cropped => Remove it.
|
||||
if mp.get_property("video-crop") ~= "" then
|
||||
mp.command(string.format("%s set file-local-options/video-crop ''", command_prefix))
|
||||
if remove_filter(labels.crop) then
|
||||
return
|
||||
end
|
||||
|
||||
@@ -289,7 +320,7 @@ function on_toggle()
|
||||
return
|
||||
end
|
||||
|
||||
-- Neither => Detect crop.
|
||||
-- Neither => Do delectcrop.
|
||||
detect_crop()
|
||||
end
|
||||
|
||||
|
||||
+86
-190
@@ -16,17 +16,11 @@ 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
|
||||
same_type=yes
|
||||
directory_mode=recursive
|
||||
|
||||
--]]
|
||||
|
||||
MAXENTRIES = 5000
|
||||
MAXDIRSTACK = 20
|
||||
|
||||
local msg = require 'mp.msg'
|
||||
local options = require 'mp.options'
|
||||
@@ -37,24 +31,9 @@ o = {
|
||||
images = true,
|
||||
videos = true,
|
||||
audio = true,
|
||||
additional_image_exts = "",
|
||||
additional_video_exts = "",
|
||||
additional_audio_exts = "",
|
||||
ignore_hidden = true,
|
||||
same_type = false,
|
||||
directory_mode = "auto"
|
||||
ignore_hidden = true
|
||||
}
|
||||
options.read_options(o, nil, function(list)
|
||||
split_option_exts(list.additional_video_exts, list.additional_audio_exts, list.additional_image_exts)
|
||||
if list.videos or list.additional_video_exts or
|
||||
list.audio or list.additional_audio_exts or
|
||||
list.images or list.additional_image_exts then
|
||||
create_extensions()
|
||||
end
|
||||
if list.directory_mode then
|
||||
validate_directory_mode()
|
||||
end
|
||||
end)
|
||||
options.read_options(o)
|
||||
|
||||
function Set (t)
|
||||
local set = {}
|
||||
@@ -63,58 +42,35 @@ function Set (t)
|
||||
end
|
||||
|
||||
function SetUnion (a,b)
|
||||
for k in pairs(b) do a[k] = true end
|
||||
return a
|
||||
end
|
||||
|
||||
function Split (s)
|
||||
local set = {}
|
||||
for v in string.gmatch(s, '([^,]+)') do set[v] = true end
|
||||
return set
|
||||
local res = {}
|
||||
for k in pairs(a) do res[k] = true end
|
||||
for k in pairs(b) do res[k] = true end
|
||||
return res
|
||||
end
|
||||
|
||||
EXTENSIONS_VIDEO = Set {
|
||||
'3g2', '3gp', 'avi', 'flv', 'm2ts', 'm4v', 'mj2', 'mkv', 'mov',
|
||||
'mp4', 'mpeg', 'mpg', 'ogv', 'rmvb', 'webm', 'wmv', 'y4m'
|
||||
'mkv', 'avi', 'mp4', 'ogv', 'webm', 'rmvb', 'flv', 'wmv', 'mpeg', 'mpg', 'm4v', '3gp'
|
||||
}
|
||||
|
||||
EXTENSIONS_AUDIO = Set {
|
||||
'aiff', 'ape', 'au', 'flac', 'm4a', 'mka', 'mp3', 'oga', 'ogg',
|
||||
'ogm', 'opus', 'wav', 'wma'
|
||||
'mp3', 'wav', 'ogm', 'flac', 'm4a', 'wma', 'ogg', 'opus'
|
||||
}
|
||||
|
||||
EXTENSIONS_IMAGES = Set {
|
||||
'avif', 'bmp', 'gif', 'j2k', 'jp2', 'jpeg', 'jpg', 'jxl', 'png',
|
||||
'svg', 'tga', 'tif', 'tiff', 'webp'
|
||||
'jpg', 'jpeg', 'png', 'tif', 'tiff', 'gif', 'webp', 'svg', 'bmp'
|
||||
}
|
||||
|
||||
function split_option_exts(video, audio, image)
|
||||
if video then o.additional_video_exts = Split(o.additional_video_exts) end
|
||||
if audio then o.additional_audio_exts = Split(o.additional_audio_exts) end
|
||||
if image then o.additional_image_exts = Split(o.additional_image_exts) end
|
||||
end
|
||||
split_option_exts(true, true, true)
|
||||
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 create_extensions()
|
||||
EXTENSIONS = {}
|
||||
if o.videos then SetUnion(SetUnion(EXTENSIONS, EXTENSIONS_VIDEO), o.additional_video_exts) end
|
||||
if o.audio then SetUnion(SetUnion(EXTENSIONS, EXTENSIONS_AUDIO), o.additional_audio_exts) end
|
||||
if o.images then SetUnion(SetUnion(EXTENSIONS, EXTENSIONS_IMAGES), o.additional_image_exts) end
|
||||
end
|
||||
create_extensions()
|
||||
|
||||
function validate_directory_mode()
|
||||
if o.directory_mode ~= "recursive" and o.directory_mode ~= "lazy" and o.directory_mode ~= "ignore" then
|
||||
o.directory_mode = nil
|
||||
end
|
||||
end
|
||||
validate_directory_mode()
|
||||
|
||||
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
|
||||
|
||||
@@ -135,119 +91,59 @@ table.filter = function(t, iter)
|
||||
end
|
||||
end
|
||||
|
||||
table.append = function(t1, t2)
|
||||
local t1_size = #t1
|
||||
for i = 1, #t2 do
|
||||
t1[t1_size + i] = t2[i]
|
||||
-- splitbynum and alnumcomp from alphanum.lua (C) Andre Bogus
|
||||
-- Released under the MIT License
|
||||
-- http://www.davekoelle.com/files/alphanum.lua
|
||||
|
||||
-- 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
|
||||
return result
|
||||
end
|
||||
|
||||
-- alphanum sorting for humans in Lua
|
||||
-- http://notebook.kulchenko.com/algorithms/alphanumeric-natural-sorting-for-humans-in-lua
|
||||
function clean_key(k)
|
||||
k = (' '..k..' '):gsub("%s+", " "):sub(2, -2):lower()
|
||||
return splitbynum(k)
|
||||
end
|
||||
|
||||
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)
|
||||
-- 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
|
||||
|
||||
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 #xt < #yt
|
||||
end
|
||||
|
||||
local autoloaded = nil
|
||||
local added_entries = {}
|
||||
local autoloaded_dir = nil
|
||||
|
||||
function scan_dir(path, current_file, dir_mode, separator, dir_depth, total_files, extensions)
|
||||
if dir_depth == MAXDIRSTACK then
|
||||
return
|
||||
end
|
||||
msg.trace("scanning: " .. path)
|
||||
local files = utils.readdir(path, "files") or {}
|
||||
local dirs = dir_mode ~= "ignore" and utils.readdir(path, "dirs") or {}
|
||||
local prefix = path == "." and "" or path
|
||||
table.filter(files, function (v)
|
||||
-- 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 (prefix .. v == current_file) and string.match(v, "^%.")) then
|
||||
return false
|
||||
end
|
||||
local ext = get_extension(v)
|
||||
if ext == nil then
|
||||
return false
|
||||
end
|
||||
return extensions[string.lower(ext)]
|
||||
end)
|
||||
table.filter(dirs, function(d)
|
||||
return not ((o.ignore_hidden and string.match(d, "^%.")))
|
||||
end)
|
||||
alphanumsort(files)
|
||||
alphanumsort(dirs)
|
||||
|
||||
for i, file in ipairs(files) do
|
||||
files[i] = prefix .. file
|
||||
end
|
||||
|
||||
table.append(total_files, files)
|
||||
if dir_mode == "recursive" then
|
||||
for _, dir in ipairs(dirs) do
|
||||
scan_dir(prefix .. dir .. separator, current_file, dir_mode,
|
||||
separator, dir_depth + 1, total_files, extensions)
|
||||
end
|
||||
else
|
||||
for i, dir in ipairs(dirs) do
|
||||
dirs[i] = prefix .. dir
|
||||
end
|
||||
table.append(total_files, dirs)
|
||||
end
|
||||
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.debug("stopping: autoload disabled")
|
||||
msg.verbose("stopping: autoload disabled")
|
||||
return
|
||||
elseif #dir == 0 then
|
||||
msg.debug("stopping: not a local path")
|
||||
msg.verbose("stopping: not a local path")
|
||||
return
|
||||
end
|
||||
|
||||
local pl_count = mp.get_property_number("playlist-count", 1)
|
||||
this_ext = get_extension(filename)
|
||||
-- check if this is a manually made playlist
|
||||
if (pl_count > 1 and autoloaded == nil) or
|
||||
(pl_count == 1 and EXTENSIONS[string.lower(this_ext)] == nil) then
|
||||
msg.debug("stopping: manually made playlist")
|
||||
(pl_count == 1 and EXTENSIONS[string.lower(get_extension(filename))] == nil) then
|
||||
msg.verbose("stopping: manually made playlist")
|
||||
return
|
||||
else
|
||||
if pl_count == 1 then
|
||||
autoloaded = true
|
||||
autoloaded_dir = dir
|
||||
added_entries = {}
|
||||
end
|
||||
end
|
||||
|
||||
local extensions = {}
|
||||
if o.same_type then
|
||||
if EXTENSIONS_VIDEO[string.lower(this_ext)] ~= nil then
|
||||
extensions = EXTENSIONS_VIDEO
|
||||
elseif EXTENSIONS_AUDIO[string.lower(this_ext)] ~= nil then
|
||||
extensions = EXTENSIONS_AUDIO
|
||||
else
|
||||
extensions = EXTENSIONS_IMAGES
|
||||
end
|
||||
else
|
||||
extensions = EXTENSIONS
|
||||
autoloaded = true
|
||||
end
|
||||
|
||||
local pl = mp.get_property_native("playlist", {})
|
||||
@@ -255,22 +151,33 @@ function find_and_add_entries()
|
||||
msg.trace(("playlist-pos-1: %s, playlist: %s"):format(pl_current,
|
||||
utils.to_string(pl)))
|
||||
|
||||
local files = {}
|
||||
do
|
||||
local dir_mode = o.directory_mode or mp.get_property("directory-mode", "lazy")
|
||||
local separator = mp.get_property_native("platform") == "windows" and "\\" or "/"
|
||||
scan_dir(autoloaded_dir, path, dir_mode, separator, 0, files, extensions)
|
||||
end
|
||||
|
||||
if next(files) == nil then
|
||||
msg.debug("no other files or directories in directory")
|
||||
local files = utils.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
|
||||
return false
|
||||
end
|
||||
local ext = get_extension(v)
|
||||
if ext == nil then
|
||||
return false
|
||||
end
|
||||
return EXTENSIONS[string.lower(ext)]
|
||||
end)
|
||||
table.sort(files, alnumcomp)
|
||||
|
||||
if dir == "." then
|
||||
dir = ""
|
||||
end
|
||||
|
||||
-- Find the current pl entry (dir+"/"+filename) in the sorted dir list
|
||||
local current
|
||||
for i = 1, #files do
|
||||
if files[i] == path then
|
||||
if files[i] == filename then
|
||||
current = i
|
||||
break
|
||||
end
|
||||
@@ -280,49 +187,38 @@ function find_and_add_entries()
|
||||
end
|
||||
msg.trace("current file position in files: "..current)
|
||||
|
||||
-- treat already existing playlist entries, independent of how they got added
|
||||
-- as if they got added by autoload
|
||||
for _, entry in ipairs(pl) do
|
||||
added_entries[entry.filename] = true
|
||||
end
|
||||
|
||||
local append = {[-1] = {}, [1] = {}}
|
||||
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
|
||||
|
||||
-- skip files that are/were already in the playlist
|
||||
if not added_entries[file] then
|
||||
if direction == -1 then
|
||||
msg.verbose("Prepending " .. file)
|
||||
table.insert(append[-1], 1, {file, pl_current + i * direction + 1})
|
||||
else
|
||||
msg.verbose("Adding " .. file)
|
||||
if pl_count > 1 then
|
||||
table.insert(append[1], {file, pl_current + i * direction - 1})
|
||||
else
|
||||
mp.commandv("loadfile", file, "append")
|
||||
end
|
||||
local filepath = dir .. file
|
||||
if pl_e then
|
||||
-- If there's a playlist entry, and it's the same file, stop.
|
||||
msg.trace(pl_e.filename.." == "..filepath.." ?")
|
||||
if pl_e.filename == filepath then
|
||||
break
|
||||
end
|
||||
end
|
||||
added_entries[file] = true
|
||||
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
|
||||
msg.info("Prepending " .. file)
|
||||
table.insert(append[-1], 1, filepath)
|
||||
end
|
||||
else
|
||||
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,46 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# This checks for the sdk path, the sdk version, and
|
||||
# the sdk build version.
|
||||
|
||||
import re
|
||||
import os
|
||||
import string
|
||||
import subprocess
|
||||
import sys
|
||||
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')
|
||||
xcodebuild = which('xcodebuild')
|
||||
|
||||
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':
|
||||
sdk_version = check_output([xcrun, '--sdk', 'macosx', '--show-sdk-version'],
|
||||
encoding="UTF-8")
|
||||
|
||||
# use xcode tools when installed, still necessary for xcode versions <12.0
|
||||
try:
|
||||
sdk_version = check_output([xcodebuild, '-sdk', 'macosx', '-version', 'ProductVersion'],
|
||||
encoding="UTF-8", stderr=subprocess.DEVNULL)
|
||||
except:
|
||||
pass
|
||||
|
||||
if not isinstance(sdk_version, str):
|
||||
sdk_version = '10.10.0'
|
||||
|
||||
return sdk.strip(),sdk_version.strip()
|
||||
|
||||
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)
|
||||
+2
-6
@@ -105,10 +105,6 @@ elements_matroska = (
|
||||
'DisplayWidth, 54b0, uint',
|
||||
'DisplayHeight, 54ba, uint',
|
||||
'DisplayUnit, 54b2, uint',
|
||||
'PixelCropTop, 54bb, uint',
|
||||
'PixelCropLeft, 54cc, uint',
|
||||
'PixelCropRight, 54dd, uint',
|
||||
'PixelCropBottom, 54aa, uint',
|
||||
'FrameRate, 2383e3, float',
|
||||
'ColourSpace, 2eb524, binary',
|
||||
'StereoMode, 53b8, uint',
|
||||
@@ -464,9 +460,9 @@ if __name__ == "__main__":
|
||||
parse_one(s, 0, None, 1 << 63)
|
||||
|
||||
if sys.argv[1] == '--generate-header':
|
||||
generate_C_header(open(sys.argv[2], "w"))
|
||||
generate_C_header(sys.stdout)
|
||||
elif sys.argv[1] == '--generate-definitions':
|
||||
generate_C_definitions(open(sys.argv[2], "w"))
|
||||
generate_C_definitions(sys.stdout)
|
||||
else:
|
||||
s = open(sys.argv[1], "rb")
|
||||
while 1:
|
||||
|
||||
@@ -79,7 +79,7 @@ EOF
|
||||
video-bitrate
|
||||
video-codec
|
||||
video-format
|
||||
video-params/aspect
|
||||
video-aspect
|
||||
container-fps
|
||||
width
|
||||
height
|
||||
|
||||
+5
-5
@@ -38,8 +38,9 @@ def apply_plist_template(plist_file, version):
|
||||
for line in fileinput.input(plist_file, inplace=1):
|
||||
print(line.rstrip().replace('${VERSION}', version))
|
||||
|
||||
def sign_bundle(binary_name):
|
||||
sh('codesign --force --deep -s - ' + bundle_path(binary_name))
|
||||
def create_bundle_symlink(binary_name, symlink_name):
|
||||
os.symlink(os.path.basename(binary_name),
|
||||
os.path.join(target_directory(binary_name), symlink_name))
|
||||
|
||||
def bundle_version():
|
||||
if os.path.exists('VERSION'):
|
||||
@@ -71,6 +72,8 @@ def main():
|
||||
copy_bundle(binary_name)
|
||||
print("> copying binary")
|
||||
copy_binary(binary_name)
|
||||
print("> create bundle symlink")
|
||||
create_bundle_symlink(binary_name, "mpv-bundle")
|
||||
print("> generating Info.plist")
|
||||
apply_plist_template(target_plist(binary_name), version)
|
||||
|
||||
@@ -78,9 +81,6 @@ def main():
|
||||
print("> bundling dependencies")
|
||||
print(sh(" ".join(["TOOLS/dylib-unhell.py", target_binary(binary_name)])))
|
||||
|
||||
print("> signing bundle with ad-hoc pseudo identity")
|
||||
sign_bundle(binary_name)
|
||||
|
||||
print("done.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
input = join_paths(source_root, 'TOOLS', 'osxbundle',
|
||||
'mpv.app', 'Contents', 'Resources', 'icon.icns')
|
||||
osxbundle = custom_target('osxbundle',
|
||||
input: input,
|
||||
output: 'icon.icns.inc',
|
||||
command: [file2string, '@INPUT@', '@OUTPUT@'],
|
||||
)
|
||||
sources += osxbundle
|
||||
@@ -173,7 +173,7 @@
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>mpv</string>
|
||||
<string>mpv-bundle</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>icon</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
@@ -192,8 +192,6 @@
|
||||
<dict>
|
||||
<key>MallocNanoZone</key>
|
||||
<string>0</string>
|
||||
<key>MPVBUNDLE</key>
|
||||
<string>true</string>
|
||||
</dict>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
@@ -225,7 +223,6 @@
|
||||
<string>udp</string>
|
||||
<string>smb</string>
|
||||
<string>srt</string>
|
||||
<string>rist</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
|
||||
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 [ "$TRAVIS_OS_NAME" != "linux" ] || [ "$CC" != "clang" ]; 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
|
||||
+2
-3
@@ -32,7 +32,6 @@ import socket
|
||||
import errno
|
||||
import subprocess
|
||||
import string
|
||||
import shlex
|
||||
|
||||
files = sys.argv[1:]
|
||||
|
||||
@@ -53,7 +52,7 @@ def make_abs(filename):
|
||||
return filename
|
||||
files = (make_abs(f) for f in files)
|
||||
|
||||
SOCK = os.path.join(os.getenv("XDG_RUNTIME_DIR", os.getenv("HOME")), ".umpv_socket")
|
||||
SOCK = os.path.join(os.getenv("HOME"), ".umpv_socket")
|
||||
|
||||
sock = None
|
||||
try:
|
||||
@@ -79,7 +78,7 @@ if sock:
|
||||
else:
|
||||
# Let mpv recreate socket if it doesn't already exist.
|
||||
|
||||
opts = shlex.split(os.getenv("MPV") or "mpv")
|
||||
opts = (os.getenv("MPV") or "mpv").split()
|
||||
opts.extend(["--no-terminal", "--force-window", "--input-ipc-server=" + SOCK,
|
||||
"--"])
|
||||
opts.extend(files)
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
image: Visual Studio 2019
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- appveyor
|
||||
- ci
|
||||
|
||||
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:
|
||||
# Disable checking disk space to speed up install time
|
||||
- C:\msys64\usr\bin\sed -i 's/^CheckSpace/#CheckSpace/g' /etc/pacman.conf
|
||||
# Update core packages
|
||||
- C:\msys64\usr\bin\pacman -Syyuu --noconfirm --noprogressbar --ask=20
|
||||
# Explicitly kill any remaining msys2 processes after core update
|
||||
- taskkill /f /fi "MODULES eq msys-2.0.dll"
|
||||
# Update non-core packages
|
||||
- C:\msys64\usr\bin\pacman -Suu --noconfirm --noprogressbar --ask=20
|
||||
# Install required MSYS2 packages
|
||||
- C:\msys64\usr\bin\pacman -S --noconfirm --noprogressbar --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
|
||||
+5
-66
@@ -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"
|
||||
@@ -52,7 +49,8 @@ struct mp_aframe *mp_aframe_create(void)
|
||||
{
|
||||
struct mp_aframe *frame = talloc_zero(NULL, struct mp_aframe);
|
||||
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;
|
||||
@@ -123,16 +121,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,15 +132,11 @@ 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);
|
||||
|
||||
// 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;
|
||||
@@ -220,16 +204,9 @@ 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;
|
||||
// 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
|
||||
}
|
||||
|
||||
// Copy "soft" attributes from src to dst, excluding things which affect
|
||||
@@ -338,21 +315,13 @@ 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;
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -465,37 +434,6 @@ void mp_aframe_skip_samples(struct mp_aframe *f, int samples)
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the timestamp of the sample just after the end of this frame.
|
||||
double mp_aframe_end_pts(struct mp_aframe *f)
|
||||
{
|
||||
@@ -699,8 +637,9 @@ int mp_aframe_pool_allocate(struct mp_aframe_pool *pool, struct mp_aframe *frame
|
||||
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);
|
||||
av_mallocz_array(planes, sizeof(av_frame->extended_data[0]));
|
||||
if (!av_frame->extended_data)
|
||||
abort();
|
||||
} else {
|
||||
av_frame->extended_data = av_frame->data;
|
||||
}
|
||||
|
||||
@@ -60,7 +60,6 @@ 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);
|
||||
|
||||
+1
-28
@@ -52,11 +52,6 @@ static const char *const speaker_names[MP_SPEAKER_ID_COUNT][2] = {
|
||||
[MP_SPEAKER_ID_SDL] = {"sdl", "surround direct left"},
|
||||
[MP_SPEAKER_ID_SDR] = {"sdr", "surround direct right"},
|
||||
[MP_SPEAKER_ID_LFE2] = {"lfe2", "low frequency 2"},
|
||||
[MP_SPEAKER_ID_TSL] = {"tsl", "top side left"},
|
||||
[MP_SPEAKER_ID_TSR] = {"tsr", "top side right"},
|
||||
[MP_SPEAKER_ID_BFC] = {"bfc", "bottom front center"},
|
||||
[MP_SPEAKER_ID_BFL] = {"bfl", "bottom front left"},
|
||||
[MP_SPEAKER_ID_BFR] = {"bfr", "bottom front right"},
|
||||
[MP_SPEAKER_ID_NA] = {"na", "not available"},
|
||||
};
|
||||
|
||||
@@ -88,7 +83,7 @@ static const char *const std_layout_names[][2] = {
|
||||
{"6.0(front)", "fl-fr-flc-frc-sl-sr"},
|
||||
{"hexagonal", "fl-fr-fc-bl-br-bc"},
|
||||
{"6.1", "fl-fr-fc-lfe-bc-sl-sr"},
|
||||
{"6.1(back)", "fl-fr-fc-lfe-bl-br-bc"},
|
||||
{"6.1(back)", "fl-fr-fc-lfe-bl-br-bc"}, // lavc calls this "6.1" too
|
||||
{"6.1(top)", "fl-fr-fc-lfe-bl-br-tc"}, // not in lavc
|
||||
{"6.1(front)", "fl-fr-lfe-flc-frc-sl-sr"},
|
||||
{"7.0", "fl-fr-fc-bl-br-sl-sr"},
|
||||
@@ -98,13 +93,8 @@ static const char *const std_layout_names[][2] = {
|
||||
{"7.1(alsa)", "fl-fr-bl-br-fc-lfe-sl-sr"}, // not in lavc
|
||||
{"7.1(wide)", "fl-fr-fc-lfe-bl-br-flc-frc"},
|
||||
{"7.1(wide-side)", "fl-fr-fc-lfe-flc-frc-sl-sr"},
|
||||
{"7.1(top)", "fl-fr-fc-lfe-bl-br-tfl-tfr"},
|
||||
{"7.1(rear)", "fl-fr-fc-lfe-bl-br-sdl-sdr"}, // not in lavc
|
||||
{"octagonal", "fl-fr-fc-bl-br-bc-sl-sr"},
|
||||
{"cube", "fl-fr-bl-br-tfl-tfr-tbl-tbr"},
|
||||
{"hexadecagonal", "fl-fr-fc-bl-br-bc-sl-sr-tfc-tfl-tfr-tbl-tbc-tbr-wl-wr"},
|
||||
{"downmix", "fl-fr"},
|
||||
{"22.2", "fl-fr-fc-lfe-bl-br-flc-frc-bc-sl-sr-tc-tfl-tfc-tfr-tbl-tbc-tbr-lfe2-tsl-tsr-bfc-bfl-bfr"},
|
||||
{"auto", ""}, // not in lavc
|
||||
{0}
|
||||
};
|
||||
@@ -480,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");
|
||||
|
||||
+3
-25
@@ -22,7 +22,7 @@
|
||||
#include <stdbool.h>
|
||||
#include "misc/bstr.h"
|
||||
|
||||
#define MP_NUM_CHANNELS 64
|
||||
#define MP_NUM_CHANNELS 16
|
||||
|
||||
// Speaker a channel can be assigned to.
|
||||
// This corresponds to WAVEFORMATEXTENSIBLE channel mask bit indexes.
|
||||
@@ -55,11 +55,6 @@ enum mp_speaker_id {
|
||||
MP_SPEAKER_ID_SDL, // SURROUND_DIRECT_LEFT
|
||||
MP_SPEAKER_ID_SDR, // SURROUND_DIRECT_RIGHT
|
||||
MP_SPEAKER_ID_LFE2, // LOW_FREQUENCY_2
|
||||
MP_SPEAKER_ID_TSL, // TOP_SIDE_LEFT
|
||||
MP_SPEAKER_ID_TSR, // TOP_SIDE_RIGHT,
|
||||
MP_SPEAKER_ID_BFC, // BOTTOM_FRONT_CENTER
|
||||
MP_SPEAKER_ID_BFL, // BOTTOM_FRONT_LEFT
|
||||
MP_SPEAKER_ID_BFR, // BOTTOM_FRONT_RIGHT
|
||||
|
||||
// Speaker IDs >= 64 are not representable in WAVEFORMATEXTENSIBLE or libav*.
|
||||
|
||||
@@ -80,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) \
|
||||
@@ -129,28 +122,13 @@ void mp_chmap_get_reorder(int src[MP_NUM_CHANNELS], const struct mp_chmap *from,
|
||||
int mp_chmap_diffn(const struct mp_chmap *a, const struct mp_chmap *b);
|
||||
|
||||
char *mp_chmap_to_str_buf(char *buf, size_t buf_size, const struct mp_chmap *src);
|
||||
#define mp_chmap_to_str_(m, sz) mp_chmap_to_str_buf((char[sz]){0}, sz, (m))
|
||||
#define mp_chmap_to_str(m) mp_chmap_to_str_(m, MP_NUM_CHANNELS * 4)
|
||||
#define mp_chmap_to_str(m) mp_chmap_to_str_buf((char[64]){0}, 64, (m))
|
||||
|
||||
char *mp_chmap_to_str_hr_buf(char *buf, size_t buf_size, const struct mp_chmap *src);
|
||||
#define mp_chmap_to_str_hr_(m, sz) mp_chmap_to_str_hr_buf((char[sz]){0}, sz, (m))
|
||||
#define mp_chmap_to_str_hr(m) mp_chmap_to_str_hr_(m, MP_NUM_CHANNELS * 4)
|
||||
#define mp_chmap_to_str_hr(m) mp_chmap_to_str_hr_buf((char[128]){0}, 128, (m))
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
+7
-29
@@ -26,11 +26,8 @@
|
||||
#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 "common/av_common.h"
|
||||
#include "common/codecs.h"
|
||||
@@ -46,7 +43,6 @@
|
||||
struct priv {
|
||||
AVCodecContext *avctx;
|
||||
AVFrame *avframe;
|
||||
AVPacket *avpkt;
|
||||
struct mp_chmap force_channel_map;
|
||||
uint32_t skip_samples, trim_samples;
|
||||
bool preroll_done;
|
||||
@@ -60,7 +56,7 @@ struct priv {
|
||||
#define OPT_BASE_STRUCT struct ad_lavc_params
|
||||
struct ad_lavc_params {
|
||||
float ac3drc;
|
||||
bool downmix;
|
||||
int downmix;
|
||||
int threads;
|
||||
char **avopts;
|
||||
};
|
||||
@@ -68,7 +64,7 @@ struct ad_lavc_params {
|
||||
const struct m_sub_options ad_lavc_conf = {
|
||||
.opts = (const m_option_t[]) {
|
||||
{"ac3drc", OPT_FLOAT(ac3drc), M_RANGE(0, 6)},
|
||||
{"downmix", OPT_BOOL(downmix)},
|
||||
{"downmix", OPT_FLAG(downmix)},
|
||||
{"threads", OPT_INT(threads), M_RANGE(0, 16)},
|
||||
{"o", OPT_KEYVALUELIST(avopts)},
|
||||
{0}
|
||||
@@ -76,6 +72,7 @@ const struct m_sub_options ad_lavc_conf = {
|
||||
.size = sizeof(struct ad_lavc_params),
|
||||
.defaults = &(const struct ad_lavc_params){
|
||||
.ac3drc = 0,
|
||||
.downmix = 0,
|
||||
.threads = 1,
|
||||
},
|
||||
};
|
||||
@@ -104,29 +101,13 @@ static bool init(struct mp_filter *da, struct mp_codec_params *codec,
|
||||
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;
|
||||
lavc_context->pkt_timebase = ctx->codec_timebase;
|
||||
|
||||
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
|
||||
@@ -162,7 +143,6 @@ static void destroy(struct mp_filter *da)
|
||||
|
||||
avcodec_free_context(&ctx->avctx);
|
||||
av_frame_free(&ctx->avframe);
|
||||
mp_free_av_packet(&ctx->avpkt);
|
||||
}
|
||||
|
||||
static void reset(struct mp_filter *da)
|
||||
@@ -188,9 +168,10 @@ 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 ? priv->avpkt : NULL);
|
||||
int ret = avcodec_send_packet(avctx, mpkt ? &pkt : NULL);
|
||||
if (ret < 0)
|
||||
MP_ERR(da, "Error decoding audio.\n");
|
||||
return ret;
|
||||
@@ -262,9 +243,6 @@ 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 {
|
||||
|
||||
+8
-17
@@ -41,7 +41,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;
|
||||
@@ -83,7 +82,6 @@ static void destroy(struct mp_filter *da)
|
||||
avformat_free_context(lavf_ctx);
|
||||
spdif_ctx->lavf_ctx = NULL;
|
||||
}
|
||||
mp_free_av_packet(&spdif_ctx->avpkt);
|
||||
}
|
||||
|
||||
static void determine_codec_params(struct mp_filter *da, AVPacket *pkt,
|
||||
@@ -149,12 +147,10 @@ done:
|
||||
MP_WARN(da, "Failed to parse codec profile.\n");
|
||||
}
|
||||
|
||||
static int init_filter(struct mp_filter *da)
|
||||
static int init_filter(struct mp_filter *da, AVPacket *pkt)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
|
||||
AVPacket *pkt = spdif_ctx->avpkt;
|
||||
|
||||
int profile = FF_PROFILE_UNKNOWN;
|
||||
int c_rate = 0;
|
||||
determine_codec_params(da, pkt, &profile, &c_rate);
|
||||
@@ -171,7 +167,8 @@ static int init_filter(struct mp_filter *da)
|
||||
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) {
|
||||
@@ -298,20 +295,15 @@ static void process(struct mp_filter *da)
|
||||
struct mp_aframe *out = NULL;
|
||||
double pts = mpkt->pts;
|
||||
|
||||
if (!spdif_ctx->avpkt) {
|
||||
spdif_ctx->avpkt = av_packet_alloc();
|
||||
MP_HANDLE_OOM(spdif_ctx->avpkt);
|
||||
}
|
||||
mp_set_av_packet(spdif_ctx->avpkt, mpkt, NULL);
|
||||
spdif_ctx->avpkt->pts = spdif_ctx->avpkt->dts = 0;
|
||||
AVPacket pkt;
|
||||
mp_set_av_packet(&pkt, mpkt, NULL);
|
||||
pkt.pts = pkt.dts = 0;
|
||||
if (!spdif_ctx->lavf_ctx) {
|
||||
if (init_filter(da) < 0)
|
||||
if (init_filter(da, &pkt) < 0)
|
||||
goto done;
|
||||
assert(spdif_ctx->avpkt);
|
||||
}
|
||||
|
||||
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)));
|
||||
@@ -432,7 +424,6 @@ static struct mp_decoder *create(struct mp_filter *parent,
|
||||
talloc_free(da);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return &spdif_ctx->public;
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ struct f_opts {
|
||||
int out_srate;
|
||||
struct m_channels out_channels;
|
||||
|
||||
bool fail;
|
||||
int fail;
|
||||
};
|
||||
|
||||
struct priv {
|
||||
@@ -135,7 +135,7 @@ const struct mp_user_filter_entry af_format = {
|
||||
{"out-srate", OPT_INT(out_srate), M_RANGE(1000, 8*48000)},
|
||||
{"out-channels", OPT_CHANNELS(out_channels),
|
||||
.flags = M_OPT_CHANNELS_LIMITED},
|
||||
{"fail", OPT_BOOL(fail)},
|
||||
{"fail", OPT_FLAG(fail)},
|
||||
{0}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -31,10 +31,7 @@
|
||||
#include <libavutil/bswap.h>
|
||||
#include <libavutil/mem.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "audio/aframe.h"
|
||||
#include "audio/chmap_avchannel.h"
|
||||
#include "audio/chmap_sel.h"
|
||||
#include "audio/fmt-conversion.h"
|
||||
#include "audio/format.h"
|
||||
@@ -50,13 +47,13 @@
|
||||
#define AC3_MAX_CHANNELS 6
|
||||
#define AC3_MAX_CODED_FRAME_SIZE 3840
|
||||
#define AC3_FRAME_SIZE (6 * 256)
|
||||
const static uint16_t ac3_bitrate_tab[19] = {
|
||||
const uint16_t ac3_bitrate_tab[19] = {
|
||||
32, 40, 48, 56, 64, 80, 96, 112, 128,
|
||||
160, 192, 224, 256, 320, 384, 448, 512, 576, 640
|
||||
};
|
||||
|
||||
struct f_opts {
|
||||
bool add_iec61937_header;
|
||||
int add_iec61937_header;
|
||||
int bit_rate;
|
||||
int min_channel_num;
|
||||
char *encoder;
|
||||
@@ -73,7 +70,6 @@ struct priv {
|
||||
|
||||
const struct AVCodec *lavc_acodec;
|
||||
struct AVCodecContext *lavc_actx;
|
||||
AVPacket *lavc_pkt;
|
||||
int bit_rate;
|
||||
int out_samples; // upper bound on encoded output per AC3 frame
|
||||
};
|
||||
@@ -107,13 +103,8 @@ static bool reinit(struct mp_filter *f)
|
||||
|
||||
// Put sample parameters
|
||||
s->lavc_actx->sample_fmt = af_to_avformat(format);
|
||||
|
||||
#if !HAVE_AV_CHANNEL_LAYOUT
|
||||
s->lavc_actx->channels = chmap.num;
|
||||
s->lavc_actx->channel_layout = mp_chmap_to_lavc(&chmap);
|
||||
#else
|
||||
mp_chmap_to_av_layout(&s->lavc_actx->ch_layout, &chmap);
|
||||
#endif
|
||||
s->lavc_actx->sample_rate = rate;
|
||||
s->lavc_actx->bit_rate = bit_rate;
|
||||
|
||||
@@ -143,7 +134,6 @@ static void destroy(struct mp_filter *f)
|
||||
struct priv *s = f->priv;
|
||||
|
||||
reset(f);
|
||||
av_packet_free(&s->lavc_pkt);
|
||||
avcodec_free_context(&s->lavc_actx);
|
||||
}
|
||||
|
||||
@@ -162,12 +152,13 @@ static void process(struct mp_filter *f)
|
||||
|
||||
bool err = true;
|
||||
struct mp_aframe *out = NULL;
|
||||
AVPacket *pkt = s->lavc_pkt;
|
||||
AVPacket pkt = {0};
|
||||
av_init_packet(&pkt);
|
||||
|
||||
// Send input as long as it wants.
|
||||
while (1) {
|
||||
if (avcodec_is_open(s->lavc_actx)) {
|
||||
int lavc_ret = avcodec_receive_packet(s->lavc_actx, pkt);
|
||||
int lavc_ret = avcodec_receive_packet(s->lavc_actx, &pkt);
|
||||
if (lavc_ret >= 0)
|
||||
break;
|
||||
if (lavc_ret < 0 && lavc_ret != AVERROR(EAGAIN)) {
|
||||
@@ -226,12 +217,12 @@ static void process(struct mp_filter *f)
|
||||
|
||||
mp_aframe_copy_attributes(out, s->in_frame);
|
||||
|
||||
int frame_size = pkt->size;
|
||||
int frame_size = pkt.size;
|
||||
int header_len = 0;
|
||||
char hdr[8];
|
||||
|
||||
if (s->opts->add_iec61937_header && pkt->size > 5) {
|
||||
int bsmod = pkt->data[5] & 0x7;
|
||||
if (s->opts->add_iec61937_header && pkt.size > 5) {
|
||||
int bsmod = pkt.data[5] & 0x7;
|
||||
int len = frame_size;
|
||||
|
||||
frame_size = AC3_FRAME_SIZE * 2 * 2;
|
||||
@@ -252,10 +243,10 @@ static void process(struct mp_filter *f)
|
||||
goto error;
|
||||
char *buf = planes[0];
|
||||
memcpy(buf, hdr, header_len);
|
||||
memcpy(buf + header_len, pkt->data, pkt->size);
|
||||
memset(buf + header_len + pkt->size, 0,
|
||||
frame_size - (header_len + pkt->size));
|
||||
swap_16((uint16_t *)(buf + header_len), pkt->size / 2);
|
||||
memcpy(buf + header_len, pkt.data, pkt.size);
|
||||
memset(buf + header_len + pkt.size, 0,
|
||||
frame_size - (header_len + pkt.size));
|
||||
swap_16((uint16_t *)(buf + header_len), pkt.size / 2);
|
||||
mp_aframe_set_size(out, frame_size / sstride);
|
||||
mp_pin_in_write(f->ppins[1], MAKE_FRAME(MP_FRAME_AUDIO, out));
|
||||
out = NULL;
|
||||
@@ -264,7 +255,7 @@ done:
|
||||
err = false;
|
||||
// fall through
|
||||
error:
|
||||
av_packet_unref(pkt);
|
||||
av_packet_unref(&pkt);
|
||||
talloc_free(out);
|
||||
if (err)
|
||||
mp_filter_internal_mark_failed(f);
|
||||
@@ -278,38 +269,6 @@ static const struct mp_filter_info af_lavcac3enc_filter = {
|
||||
.destroy = destroy,
|
||||
};
|
||||
|
||||
static void add_chmaps_to_autoconv(struct mp_filter *f,
|
||||
struct mp_autoconvert *conv,
|
||||
const struct AVCodec *codec)
|
||||
{
|
||||
#if !HAVE_AV_CHANNEL_LAYOUT
|
||||
const uint64_t *lch = codec->channel_layouts;
|
||||
for (int n = 0; lch && lch[n]; n++) {
|
||||
struct mp_chmap chmap = {0};
|
||||
mp_chmap_from_lavc(&chmap, lch[n]);
|
||||
if (mp_chmap_is_valid(&chmap))
|
||||
mp_autoconvert_add_chmap(conv, &chmap);
|
||||
}
|
||||
#else
|
||||
const AVChannelLayout *lch = codec->ch_layouts;
|
||||
for (int n = 0; lch && lch[n].nb_channels; n++) {
|
||||
struct mp_chmap chmap = {0};
|
||||
|
||||
if (!mp_chmap_from_av_layout(&chmap, &lch[n])) {
|
||||
char layout[128] = {0};
|
||||
MP_VERBOSE(f, "Skipping unsupported channel layout: %s\n",
|
||||
av_channel_layout_describe(&lch[n],
|
||||
layout, 128) < 0 ?
|
||||
"undefined" : layout);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mp_chmap_is_valid(&chmap))
|
||||
mp_autoconvert_add_chmap(conv, &chmap);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static struct mp_filter *af_lavcac3enc_create(struct mp_filter *parent,
|
||||
void *options)
|
||||
{
|
||||
@@ -339,23 +298,14 @@ static struct mp_filter *af_lavcac3enc_create(struct mp_filter *parent,
|
||||
goto error;
|
||||
}
|
||||
|
||||
s->lavc_pkt = av_packet_alloc();
|
||||
if (!s->lavc_pkt)
|
||||
goto error;
|
||||
|
||||
if (mp_set_avopts(f->log, s->lavc_actx, s->opts->avopts) < 0)
|
||||
goto error;
|
||||
|
||||
// For this one, we require the decoder to export lists of all supported
|
||||
// For this one, we require the decoder to expert lists of all supported
|
||||
// parameters. (Not all decoders do that, but the ones we're interested
|
||||
// in do.)
|
||||
if (!s->lavc_acodec->sample_fmts ||
|
||||
#if !HAVE_AV_CHANNEL_LAYOUT
|
||||
!s->lavc_acodec->channel_layouts
|
||||
#else
|
||||
!s->lavc_acodec->ch_layouts
|
||||
#endif
|
||||
)
|
||||
!s->lavc_acodec->channel_layouts)
|
||||
{
|
||||
MP_ERR(f, "Audio encoder doesn't list supported parameters.\n");
|
||||
goto error;
|
||||
@@ -387,7 +337,13 @@ static struct mp_filter *af_lavcac3enc_create(struct mp_filter *parent,
|
||||
mp_autoconvert_add_afmt(conv, mpfmt);
|
||||
}
|
||||
|
||||
add_chmaps_to_autoconv(f, conv, s->lavc_acodec);
|
||||
const uint64_t *lch = s->lavc_acodec->channel_layouts;
|
||||
for (int n = 0; lch && lch[n]; n++) {
|
||||
struct mp_chmap chmap = {0};
|
||||
mp_chmap_from_lavc(&chmap, lch[n]);
|
||||
if (mp_chmap_is_valid(&chmap))
|
||||
mp_autoconvert_add_chmap(conv, &chmap);
|
||||
}
|
||||
|
||||
// At least currently, the AC3 encoder doesn't export sample rates.
|
||||
mp_autoconvert_add_srate(conv, 48000);
|
||||
@@ -404,8 +360,6 @@ static struct mp_filter *af_lavcac3enc_create(struct mp_filter *parent,
|
||||
return f;
|
||||
|
||||
error:
|
||||
av_packet_free(&s->lavc_pkt);
|
||||
avcodec_free_context(&s->lavc_actx);
|
||||
talloc_free(f);
|
||||
return NULL;
|
||||
}
|
||||
@@ -418,13 +372,13 @@ const struct mp_user_filter_entry af_lavcac3enc = {
|
||||
.name = "lavcac3enc",
|
||||
.priv_size = sizeof(OPT_BASE_STRUCT),
|
||||
.priv_defaults = &(const OPT_BASE_STRUCT) {
|
||||
.add_iec61937_header = true,
|
||||
.add_iec61937_header = 1,
|
||||
.bit_rate = 640,
|
||||
.min_channel_num = 3,
|
||||
.encoder = "ac3",
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
{"tospdif", OPT_BOOL(add_iec61937_header)},
|
||||
{"tospdif", OPT_FLAG(add_iec61937_header)},
|
||||
{"bitrate", OPT_CHOICE(bit_rate,
|
||||
{"auto", 0}, {"default", 0}), M_RANGE(32, 640)},
|
||||
{"minch", OPT_INT(min_channel_num), M_RANGE(2, 6)},
|
||||
|
||||
@@ -20,8 +20,6 @@
|
||||
|
||||
#include <rubberband/rubberband-c.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "audio/aframe.h"
|
||||
#include "audio/format.h"
|
||||
#include "common/common.h"
|
||||
@@ -33,7 +31,7 @@
|
||||
// command line options
|
||||
struct f_opts {
|
||||
int transients, detector, phase, window,
|
||||
smoothing, formant, pitch, channels, engine;
|
||||
smoothing, formant, pitch, channels;
|
||||
double scale;
|
||||
};
|
||||
|
||||
@@ -80,10 +78,7 @@ static bool init_rubberband(struct mp_filter *f)
|
||||
|
||||
int opts = p->opts->transients | p->opts->detector | p->opts->phase |
|
||||
p->opts->window | p->opts->smoothing | p->opts->formant |
|
||||
p->opts->pitch | p->opts->channels |
|
||||
#if HAVE_RUBBERBAND_3
|
||||
p->opts->engine |
|
||||
#endif
|
||||
p->opts->pitch | p-> opts->channels |
|
||||
RubberBandOptionProcessRealTime;
|
||||
|
||||
int rate = mp_aframe_get_rate(p->pending);
|
||||
@@ -336,9 +331,6 @@ const struct mp_user_filter_entry af_rubberband = {
|
||||
.transients = RubberBandOptionTransientsMixed,
|
||||
.formant = RubberBandOptionFormantPreserved,
|
||||
.channels = RubberBandOptionChannelsTogether,
|
||||
#if HAVE_RUBBERBAND_3
|
||||
.engine = RubberBandOptionEngineFiner,
|
||||
#endif
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
{"transients", OPT_CHOICE(transients,
|
||||
@@ -369,11 +361,6 @@ const struct mp_user_filter_entry af_rubberband = {
|
||||
{"channels", OPT_CHOICE(channels,
|
||||
{"apart", RubberBandOptionChannelsApart},
|
||||
{"together", RubberBandOptionChannelsTogether})},
|
||||
#if HAVE_RUBBERBAND_3
|
||||
{"engine", OPT_CHOICE(engine,
|
||||
{"finer", RubberBandOptionEngineFiner},
|
||||
{"faster", RubberBandOptionEngineFaster})},
|
||||
#endif
|
||||
{"pitch-scale", OPT_DOUBLE(scale), M_RANGE(0.01, 100)},
|
||||
{0}
|
||||
},
|
||||
|
||||
@@ -48,7 +48,7 @@ struct f_opts {
|
||||
float scale_nominal;
|
||||
float ms_stride;
|
||||
float ms_search;
|
||||
float factor_overlap;
|
||||
float percent_overlap;
|
||||
#define SCALE_TEMPO 1
|
||||
#define SCALE_PITCH 2
|
||||
int speed_opt;
|
||||
@@ -400,7 +400,7 @@ static bool reinit(struct mp_filter *f)
|
||||
|
||||
update_speed(s, s->speed);
|
||||
|
||||
int frames_overlap = s->frames_stride * s->opts->factor_overlap;
|
||||
int frames_overlap = s->frames_stride * s->opts->percent_overlap;
|
||||
if (frames_overlap <= 0) {
|
||||
s->bytes_standing = s->bytes_stride;
|
||||
s->samples_standing = s->bytes_standing / bps;
|
||||
@@ -604,7 +604,7 @@ const struct mp_user_filter_entry af_scaletempo = {
|
||||
.priv_size = sizeof(OPT_BASE_STRUCT),
|
||||
.priv_defaults = &(const OPT_BASE_STRUCT) {
|
||||
.ms_stride = 60,
|
||||
.factor_overlap = .20,
|
||||
.percent_overlap = .20,
|
||||
.ms_search = 14,
|
||||
.speed_opt = SCALE_TEMPO,
|
||||
.scale_nominal = 1.0,
|
||||
@@ -612,7 +612,7 @@ const struct mp_user_filter_entry af_scaletempo = {
|
||||
.options = (const struct m_option[]) {
|
||||
{"scale", OPT_FLOAT(scale_nominal), M_RANGE(0.01, DBL_MAX)},
|
||||
{"stride", OPT_FLOAT(ms_stride), M_RANGE(0.01, DBL_MAX)},
|
||||
{"overlap", OPT_FLOAT(factor_overlap), M_RANGE(0, 1)},
|
||||
{"overlap", OPT_FLOAT(percent_overlap), M_RANGE(0, 1)},
|
||||
{"search", OPT_FLOAT(ms_search), M_RANGE(0, DBL_MAX)},
|
||||
{"speed", OPT_CHOICE(speed_opt,
|
||||
{"pitch", SCALE_PITCH},
|
||||
|
||||
@@ -15,6 +15,7 @@ struct priv {
|
||||
bool sent_final;
|
||||
struct mp_aframe *pending;
|
||||
bool initialized;
|
||||
double frame_delay;
|
||||
float speed;
|
||||
};
|
||||
|
||||
@@ -29,7 +30,7 @@ static void process(struct mp_filter *f)
|
||||
return;
|
||||
|
||||
while (!p->initialized || !p->pending ||
|
||||
!mp_scaletempo2_frames_available(&p->data, p->speed))
|
||||
!mp_scaletempo2_frames_available(&p->data))
|
||||
{
|
||||
bool eof = false;
|
||||
if (!p->pending || !mp_aframe_get_size(p->pending)) {
|
||||
@@ -65,15 +66,13 @@ static void process(struct mp_filter *f)
|
||||
int frame_size = mp_aframe_get_size(p->pending);
|
||||
uint8_t **planes = mp_aframe_get_data_ro(p->pending);
|
||||
int read = mp_scaletempo2_fill_input_buffer(&p->data,
|
||||
planes, frame_size, p->speed);
|
||||
planes, frame_size, final);
|
||||
p->frame_delay += read;
|
||||
mp_aframe_skip_samples(p->pending, read);
|
||||
}
|
||||
if (final && p->pending && !p->sent_final) {
|
||||
mp_scaletempo2_set_final(&p->data);
|
||||
p->sent_final = true;
|
||||
}
|
||||
p->sent_final |= final;
|
||||
|
||||
if (mp_scaletempo2_frames_available(&p->data, p->speed)) {
|
||||
if (mp_scaletempo2_frames_available(&p->data)) {
|
||||
if (eof) {
|
||||
mp_pin_out_repeat_eof(p->in_pin); // drain more next time
|
||||
}
|
||||
@@ -83,13 +82,16 @@ static void process(struct mp_filter *f)
|
||||
if (eof) {
|
||||
mp_pin_in_write(f->ppins[1], MP_EOF_FRAME);
|
||||
return;
|
||||
} else if (format_change) {
|
||||
// go on with proper reinit on the next iteration
|
||||
p->initialized = false;
|
||||
p->sent_final = false;
|
||||
}
|
||||
// for format change go on with proper reinit on the next iteration
|
||||
}
|
||||
}
|
||||
|
||||
assert(p->pending);
|
||||
if (mp_scaletempo2_frames_available(&p->data, p->speed)) {
|
||||
if (mp_scaletempo2_frames_available(&p->data)) {
|
||||
struct mp_aframe *out = mp_aframe_new_ref(p->cur_format);
|
||||
int out_samples = p->data.ola_hop_size;
|
||||
if (mp_aframe_pool_allocate(p->out_pool, out, out_samples) < 0) {
|
||||
@@ -107,24 +109,11 @@ static void process(struct mp_filter *f)
|
||||
(float**)planes, out_samples, p->speed);
|
||||
|
||||
double pts = mp_aframe_get_pts(p->pending);
|
||||
p->frame_delay -= out_samples * p->speed;
|
||||
|
||||
if (pts != MP_NOPTS_VALUE) {
|
||||
double frame_delay = mp_scaletempo2_get_latency(&p->data, p->speed)
|
||||
+ out_samples * p->speed;
|
||||
mp_aframe_set_pts(out, pts - frame_delay / mp_aframe_get_effective_rate(out));
|
||||
|
||||
if (p->sent_final) {
|
||||
double remain_pts = pts - mp_aframe_get_pts(out);
|
||||
double rate = mp_aframe_get_effective_rate(out) / p->speed;
|
||||
int max_samples = MPMAX(0, (int) (remain_pts * rate));
|
||||
// truncate final packet to expected length
|
||||
if (out_samples >= max_samples) {
|
||||
out_samples = max_samples;
|
||||
|
||||
// reset the filter to ensure it stops generating audio
|
||||
// and mp_scaletempo2_frames_available returns false
|
||||
mp_scaletempo2_reset(&p->data);
|
||||
}
|
||||
}
|
||||
double delay = p->frame_delay / mp_aframe_get_effective_rate(out);
|
||||
mp_aframe_set_pts(out, pts - delay);
|
||||
}
|
||||
|
||||
mp_aframe_set_size(out, out_samples);
|
||||
@@ -148,6 +137,7 @@ static bool init_scaletempo2(struct mp_filter *f)
|
||||
mp_aframe_reset(p->cur_format);
|
||||
p->initialized = true;
|
||||
p->sent_final = false;
|
||||
p->frame_delay = 0;
|
||||
mp_aframe_config_copy(p->cur_format, p->pending);
|
||||
|
||||
mp_scaletempo2_init(&p->data, mp_aframe_get_channels(p->pending),
|
||||
@@ -173,6 +163,7 @@ static void reset(struct mp_filter *f)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
mp_scaletempo2_reset(&p->data);
|
||||
p->frame_delay = 0;
|
||||
p->initialized = false;
|
||||
TA_FREEP(&p->pending);
|
||||
}
|
||||
@@ -234,12 +225,12 @@ const struct mp_user_filter_entry af_scaletempo2 = {
|
||||
.priv_size = sizeof(OPT_BASE_STRUCT),
|
||||
.priv_defaults = &(const OPT_BASE_STRUCT) {
|
||||
.min_playback_rate = 0.25,
|
||||
.max_playback_rate = 8.0,
|
||||
.ola_window_size_ms = 12,
|
||||
.wsola_search_interval_ms = 40,
|
||||
.max_playback_rate = 4.0,
|
||||
.ola_window_size_ms = 20,
|
||||
.wsola_search_interval_ms = 30,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
{"search-interval",
|
||||
{"search-interval",
|
||||
OPT_FLOAT(wsola_search_interval_ms), M_RANGE(1, 1000)},
|
||||
{"window-size",
|
||||
OPT_FLOAT(ola_window_size_ms), M_RANGE(1, 1000)},
|
||||
|
||||
@@ -421,15 +421,18 @@ static void seek_buffer(struct mp_scaletempo2 *p, int frames)
|
||||
{
|
||||
assert(p->input_buffer_frames >= frames);
|
||||
p->input_buffer_frames -= frames;
|
||||
if (p->input_buffer_final_frames > 0) {
|
||||
p->input_buffer_final_frames = MPMAX(0, p->input_buffer_final_frames - frames);
|
||||
}
|
||||
for (int i = 0; i < p->channels; ++i) {
|
||||
memmove(p->input_buffer[i], p->input_buffer[i] + frames,
|
||||
p->input_buffer_frames * sizeof(float));
|
||||
}
|
||||
}
|
||||
|
||||
static void read_buffer(struct mp_scaletempo2 *p, int frames, float **dest)
|
||||
{
|
||||
peek_buffer(p, frames, 0, 0, dest);
|
||||
seek_buffer(p, frames);
|
||||
}
|
||||
|
||||
static int write_completed_frames_to(struct mp_scaletempo2 *p,
|
||||
int requested_frames, int dest_offset, float **dest)
|
||||
{
|
||||
@@ -453,86 +456,48 @@ static int write_completed_frames_to(struct mp_scaletempo2 *p,
|
||||
return rendered_frames;
|
||||
}
|
||||
|
||||
// next output_time for the given playback_rate
|
||||
static double get_updated_time(struct mp_scaletempo2 *p, double playback_rate)
|
||||
static bool can_perform_wsola(struct mp_scaletempo2 *p)
|
||||
{
|
||||
return p->output_time + p->ola_hop_size * playback_rate;
|
||||
}
|
||||
|
||||
// search_block_index for the given output_time
|
||||
static int get_search_block_index(struct mp_scaletempo2 *p, double output_time)
|
||||
{
|
||||
return (int)(output_time - p->search_block_center_offset + 0.5);
|
||||
return p->target_block_index + p->ola_window_size <= p->input_buffer_frames
|
||||
&& p->search_block_index + p->search_block_size <= p->input_buffer_frames;
|
||||
}
|
||||
|
||||
// number of frames needed until a wsola iteration can be performed
|
||||
static int frames_needed(struct mp_scaletempo2 *p, double playback_rate)
|
||||
static int frames_needed(struct mp_scaletempo2 *p)
|
||||
{
|
||||
int search_block_index =
|
||||
get_search_block_index(p, get_updated_time(p, playback_rate));
|
||||
return MPMAX(0, MPMAX(
|
||||
p->target_block_index + p->ola_window_size - p->input_buffer_frames,
|
||||
search_block_index + p->search_block_size - p->input_buffer_frames));
|
||||
}
|
||||
|
||||
static bool can_perform_wsola(struct mp_scaletempo2 *p, double playback_rate)
|
||||
{
|
||||
return frames_needed(p, playback_rate) <= 0;
|
||||
p->search_block_index + p->search_block_size - p->input_buffer_frames));
|
||||
}
|
||||
|
||||
static void resize_input_buffer(struct mp_scaletempo2 *p, int size)
|
||||
{
|
||||
p->input_buffer_size = size;
|
||||
p->input_buffer = realloc_2d(p->input_buffer, p->channels, size);
|
||||
}
|
||||
|
||||
// pad end with silence until a wsola iteration can be performed
|
||||
static void add_input_buffer_final_silence(struct mp_scaletempo2 *p, double playback_rate)
|
||||
{
|
||||
int needed = frames_needed(p, playback_rate);
|
||||
if (needed <= 0)
|
||||
return; // no silence needed for iteration
|
||||
|
||||
int required_size = needed + p->input_buffer_frames;
|
||||
if (required_size > p->input_buffer_size)
|
||||
resize_input_buffer(p, required_size);
|
||||
|
||||
for (int i = 0; i < p->channels; ++i) {
|
||||
float *ch_input = p->input_buffer[i];
|
||||
for (int j = 0; j < needed; ++j) {
|
||||
ch_input[p->input_buffer_frames + j] = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
p->input_buffer_added_silence += needed;
|
||||
p->input_buffer_frames += needed;
|
||||
}
|
||||
|
||||
void mp_scaletempo2_set_final(struct mp_scaletempo2 *p)
|
||||
{
|
||||
if (p->input_buffer_final_frames <= 0) {
|
||||
p->input_buffer_final_frames = p->input_buffer_frames;
|
||||
if (size > p->input_buffer_size) {
|
||||
p->input_buffer_size = size;
|
||||
p->input_buffer = realloc_2d(p->input_buffer, p->channels, size);
|
||||
}
|
||||
}
|
||||
|
||||
int mp_scaletempo2_fill_input_buffer(struct mp_scaletempo2 *p,
|
||||
uint8_t **planes, int frame_size, double playback_rate)
|
||||
uint8_t **planes, int frame_size, bool final)
|
||||
{
|
||||
int needed = frames_needed(p, playback_rate);
|
||||
int needed = frames_needed(p);
|
||||
int read = MPMIN(needed, frame_size);
|
||||
if (read == 0)
|
||||
return 0;
|
||||
int total_fill = final ? needed : read;
|
||||
if (total_fill == 0) return 0;
|
||||
|
||||
int required_size = read + p->input_buffer_frames;
|
||||
if (required_size > p->input_buffer_size)
|
||||
resize_input_buffer(p, required_size);
|
||||
int required_size = total_fill + p->input_buffer_frames;
|
||||
resize_input_buffer(p, required_size);
|
||||
|
||||
for (int i = 0; i < p->channels; ++i) {
|
||||
memcpy(p->input_buffer[i] + p->input_buffer_frames,
|
||||
planes[i], read * sizeof(float));
|
||||
for (int j = read; j < total_fill; ++j) {
|
||||
p->input_buffer[p->input_buffer_frames + j] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
p->input_buffer_frames += read;
|
||||
p->input_buffer_frames += total_fill;
|
||||
return read;
|
||||
}
|
||||
|
||||
@@ -623,13 +588,17 @@ static void get_optimal_block(struct mp_scaletempo2 *p)
|
||||
p->target_block_index = optimal_index + p->ola_hop_size;
|
||||
}
|
||||
|
||||
static void set_output_time(struct mp_scaletempo2 *p, double output_time)
|
||||
static void update_output_time(struct mp_scaletempo2 *p,
|
||||
float playback_rate, double time_change)
|
||||
{
|
||||
p->output_time = output_time;
|
||||
p->search_block_index = get_search_block_index(p, output_time);
|
||||
p->output_time += time_change;
|
||||
// Center of the search region, in frames.
|
||||
int search_block_center_index = (int)(p->output_time * playback_rate + 0.5);
|
||||
p->search_block_index = search_block_center_index
|
||||
- p->search_block_center_offset;
|
||||
}
|
||||
|
||||
static void remove_old_input_frames(struct mp_scaletempo2 *p)
|
||||
static void remove_old_input_frames(struct mp_scaletempo2 *p, float playback_rate)
|
||||
{
|
||||
const int earliest_used_index = MPMIN(
|
||||
p->target_block_index, p->search_block_index);
|
||||
@@ -639,69 +608,46 @@ static void remove_old_input_frames(struct mp_scaletempo2 *p)
|
||||
// Remove frames from input and adjust indices accordingly.
|
||||
seek_buffer(p, earliest_used_index);
|
||||
p->target_block_index -= earliest_used_index;
|
||||
p->output_time -= earliest_used_index;
|
||||
p->search_block_index -= earliest_used_index;
|
||||
|
||||
// Adjust output index.
|
||||
double output_time_change = ((double) earliest_used_index) / playback_rate;
|
||||
assert(p->output_time >= output_time_change);
|
||||
update_output_time(p, playback_rate, -output_time_change);
|
||||
}
|
||||
|
||||
static bool run_one_wsola_iteration(struct mp_scaletempo2 *p, double playback_rate)
|
||||
static bool run_one_wsola_iteration(struct mp_scaletempo2 *p, float playback_rate)
|
||||
{
|
||||
if (!can_perform_wsola(p, playback_rate)) {
|
||||
if (!can_perform_wsola(p)){
|
||||
return false;
|
||||
}
|
||||
|
||||
set_output_time(p, get_updated_time(p, playback_rate));
|
||||
remove_old_input_frames(p);
|
||||
|
||||
assert(p->search_block_index + p->search_block_size <= p->input_buffer_frames);
|
||||
|
||||
get_optimal_block(p);
|
||||
|
||||
// Overlap-and-add.
|
||||
for (int k = 0; k < p->channels; ++k) {
|
||||
float* ch_opt_frame = p->optimal_block[k];
|
||||
float* ch_output = p->wsola_output[k] + p->num_complete_frames;
|
||||
if (p->wsola_output_started) {
|
||||
for (int n = 0; n < p->ola_hop_size; ++n) {
|
||||
ch_output[n] = ch_output[n] * p->ola_window[p->ola_hop_size + n] +
|
||||
ch_opt_frame[n] * p->ola_window[n];
|
||||
}
|
||||
|
||||
// Copy the second half to the output.
|
||||
memcpy(&ch_output[p->ola_hop_size], &ch_opt_frame[p->ola_hop_size],
|
||||
sizeof(*ch_opt_frame) * p->ola_hop_size);
|
||||
} else {
|
||||
// No overlap for the first iteration.
|
||||
memcpy(ch_output, ch_opt_frame,
|
||||
sizeof(*ch_opt_frame) * p->ola_window_size);
|
||||
for (int n = 0; n < p->ola_hop_size; ++n) {
|
||||
ch_output[n] = ch_output[n] * p->ola_window[p->ola_hop_size + n] +
|
||||
ch_opt_frame[n] * p->ola_window[n];
|
||||
}
|
||||
|
||||
// Copy the second half to the output.
|
||||
memcpy(&ch_output[p->ola_hop_size], &ch_opt_frame[p->ola_hop_size],
|
||||
sizeof(*ch_opt_frame) * p->ola_hop_size);
|
||||
}
|
||||
|
||||
p->num_complete_frames += p->ola_hop_size;
|
||||
p->wsola_output_started = true;
|
||||
update_output_time(p, playback_rate, p->ola_hop_size);
|
||||
remove_old_input_frames(p, playback_rate);
|
||||
return true;
|
||||
}
|
||||
|
||||
static int read_input_buffer(struct mp_scaletempo2 *p, int dest_size, float **dest)
|
||||
{
|
||||
int frames_to_copy = MPMIN(dest_size, p->input_buffer_frames - p->target_block_index);
|
||||
|
||||
if (frames_to_copy <= 0)
|
||||
return 0; // There is nothing to read from input buffer; return.
|
||||
|
||||
peek_buffer(p, frames_to_copy, p->target_block_index, 0, dest);
|
||||
seek_buffer(p, frames_to_copy);
|
||||
return frames_to_copy;
|
||||
}
|
||||
|
||||
int mp_scaletempo2_fill_buffer(struct mp_scaletempo2 *p,
|
||||
float **dest, int dest_size, double playback_rate)
|
||||
float **dest, int dest_size, float playback_rate)
|
||||
{
|
||||
if (playback_rate == 0) return 0;
|
||||
|
||||
if (p->input_buffer_final_frames > 0) {
|
||||
add_input_buffer_final_silence(p, playback_rate);
|
||||
}
|
||||
|
||||
// Optimize the muted case to issue a single clear instead of performing
|
||||
// the full crossfade and clearing each crossfaded frame.
|
||||
if (playback_rate < p->opts->min_playback_rate
|
||||
@@ -735,16 +681,9 @@ int mp_scaletempo2_fill_buffer(struct mp_scaletempo2 *p,
|
||||
// Optimize the most common |playback_rate| ~= 1 case to use a single copy
|
||||
// instead of copying frame by frame.
|
||||
if (p->ola_window_size <= faster_step && slower_step >= p->ola_window_size) {
|
||||
|
||||
if (p->wsola_output_started) {
|
||||
p->wsola_output_started = false;
|
||||
|
||||
// sync audio precisely again
|
||||
set_output_time(p, p->target_block_index);
|
||||
remove_old_input_frames(p);
|
||||
}
|
||||
|
||||
return read_input_buffer(p, dest_size, dest);
|
||||
int frames_to_copy = MPMIN(dest_size, p->input_buffer_frames);
|
||||
read_buffer(p, frames_to_copy, dest);
|
||||
return frames_to_copy;
|
||||
}
|
||||
|
||||
int rendered_frames = 0;
|
||||
@@ -756,18 +695,9 @@ int mp_scaletempo2_fill_buffer(struct mp_scaletempo2 *p,
|
||||
return rendered_frames;
|
||||
}
|
||||
|
||||
double mp_scaletempo2_get_latency(struct mp_scaletempo2 *p, double playback_rate)
|
||||
bool mp_scaletempo2_frames_available(struct mp_scaletempo2 *p)
|
||||
{
|
||||
return p->input_buffer_frames - p->output_time
|
||||
- p->input_buffer_added_silence
|
||||
+ p->num_complete_frames * playback_rate;
|
||||
}
|
||||
|
||||
bool mp_scaletempo2_frames_available(struct mp_scaletempo2 *p, double playback_rate)
|
||||
{
|
||||
return p->input_buffer_final_frames > p->target_block_index
|
||||
|| can_perform_wsola(p, playback_rate)
|
||||
|| p->num_complete_frames > 0;
|
||||
return can_perform_wsola(p) || p->num_complete_frames > 0;
|
||||
}
|
||||
|
||||
void mp_scaletempo2_destroy(struct mp_scaletempo2 *p)
|
||||
@@ -785,15 +715,12 @@ void mp_scaletempo2_destroy(struct mp_scaletempo2 *p)
|
||||
void mp_scaletempo2_reset(struct mp_scaletempo2 *p)
|
||||
{
|
||||
p->input_buffer_frames = 0;
|
||||
p->input_buffer_final_frames = 0;
|
||||
p->input_buffer_added_silence = 0;
|
||||
p->output_time = 0.0;
|
||||
p->search_block_index = 0;
|
||||
p->target_block_index = 0;
|
||||
// Clear the queue of decoded packets.
|
||||
zero_2d(p->wsola_output, p->channels, p->wsola_output_size);
|
||||
p->num_complete_frames = 0;
|
||||
p->wsola_output_started = false;
|
||||
}
|
||||
|
||||
// Return a "periodic" Hann window. This is the first L samples of an L+1
|
||||
@@ -810,16 +737,15 @@ void mp_scaletempo2_init(struct mp_scaletempo2 *p, int channels, int rate)
|
||||
{
|
||||
p->muted_partial_frame = 0;
|
||||
p->output_time = 0;
|
||||
p->search_block_center_offset = 0;
|
||||
p->search_block_index = 0;
|
||||
p->target_block_index = 0;
|
||||
p->num_complete_frames = 0;
|
||||
p->wsola_output_started = false;
|
||||
p->channels = channels;
|
||||
|
||||
p->samples_per_second = rate;
|
||||
p->num_candidate_blocks = (int)(p->opts->wsola_search_interval_ms
|
||||
p->num_candidate_blocks = (int)(p->opts->wsola_search_interval_ms
|
||||
* p->samples_per_second / 1000);
|
||||
p->ola_window_size = (int)(p->opts->ola_window_size_ms
|
||||
p->ola_window_size = (int)(p->opts->ola_window_size_ms
|
||||
* p->samples_per_second / 1000);
|
||||
// Make sure window size in an even number.
|
||||
p->ola_window_size += p->ola_window_size & 1;
|
||||
@@ -865,8 +791,6 @@ void mp_scaletempo2_init(struct mp_scaletempo2 *p, int channels, int rate)
|
||||
|
||||
resize_input_buffer(p, 4 * MPMAX(p->ola_window_size, p->search_block_size));
|
||||
p->input_buffer_frames = 0;
|
||||
p->input_buffer_final_frames = 0;
|
||||
p->input_buffer_added_silence = 0;
|
||||
|
||||
p->energy_candidate_blocks = realloc(p->energy_candidate_blocks,
|
||||
sizeof(float) * p->channels * p->num_candidate_blocks);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// This filter was ported from Chromium
|
||||
// This filter was ported from Chromium
|
||||
// (https://chromium.googlesource.com/chromium/chromium/+/51ed77e3f37a9a9b80d6d0a8259e84a8ca635259/media/filters/audio_renderer_algorithm.cc)
|
||||
//
|
||||
// Copyright 2015 The Chromium Authors. All rights reserved.
|
||||
@@ -53,19 +53,18 @@ struct mp_scaletempo2 {
|
||||
int samples_per_second;
|
||||
// If muted, keep track of partial frames that should have been skipped over.
|
||||
double muted_partial_frame;
|
||||
// Book keeping of the current time of generated audio, in frames.
|
||||
// Corresponds to the center of |search_block|. This is increased in
|
||||
// intervals of |ola_hop_size| multiplied by the current playback_rate,
|
||||
// for every WSOLA iteration. This tracks the number of advanced frames as
|
||||
// a double to achieve accurate playback rates beyond the integer precision
|
||||
// of |search_block_index|.
|
||||
// Needs to be adjusted like any other index when frames are evicted from
|
||||
// |input_buffer|.
|
||||
// Book keeping of the current time of generated audio, in frames. This
|
||||
// should be appropriately updated when out samples are generated, regardless
|
||||
// of whether we push samples out when fill_buffer() is called or we store
|
||||
// audio in |wsola_output| for the subsequent calls to fill_buffer().
|
||||
// Furthermore, if samples from |audio_buffer| are evicted then this
|
||||
// member variable should be updated based on |playback_rate|.
|
||||
// Note that this member should be updated ONLY by calling update_output_time(),
|
||||
// so that |search_block_index| is update accordingly.
|
||||
double output_time;
|
||||
// The offset of the center frame of |search_block| w.r.t. its first frame.
|
||||
int search_block_center_offset;
|
||||
// Index of the beginning of the |search_block|, in frames. This may be
|
||||
// negative, which is handled by |peek_audio_with_zero_prepend|.
|
||||
// Index of the beginning of the |search_block|, in frames.
|
||||
int search_block_index;
|
||||
// Number of Blocks to search to find the most similar one to the target
|
||||
// frame.
|
||||
@@ -81,9 +80,6 @@ struct mp_scaletempo2 {
|
||||
// them and can be copied to output if fill_buffer() is called. It also
|
||||
// specifies the index where the next WSOLA window has to overlap-and-add.
|
||||
int num_complete_frames;
|
||||
// Whether |wsola_output| contains an additional |ola_hop_size| of overlap
|
||||
// frames for the next iteration.
|
||||
bool wsola_output_started;
|
||||
// Overlap-and-add window.
|
||||
float *ola_window;
|
||||
// Transition window, used to update |optimal_block| by a weighted sum of
|
||||
@@ -112,23 +108,14 @@ struct mp_scaletempo2 {
|
||||
float **input_buffer;
|
||||
int input_buffer_size;
|
||||
int input_buffer_frames;
|
||||
// How many frames in |input_buffer| need to be flushed by padding with
|
||||
// silence to process the final packet. While this is nonzero, the filter
|
||||
// appends silence to |input_buffer| until these frames are processed.
|
||||
int input_buffer_final_frames;
|
||||
// How many additional frames of silence have been added to |input_buffer|
|
||||
// for padding after the final packet.
|
||||
int input_buffer_added_silence;
|
||||
float *energy_candidate_blocks;
|
||||
};
|
||||
|
||||
void mp_scaletempo2_destroy(struct mp_scaletempo2 *p);
|
||||
void mp_scaletempo2_reset(struct mp_scaletempo2 *p);
|
||||
void mp_scaletempo2_init(struct mp_scaletempo2 *p, int channels, int rate);
|
||||
double mp_scaletempo2_get_latency(struct mp_scaletempo2 *p, double playback_rate);
|
||||
int mp_scaletempo2_fill_input_buffer(struct mp_scaletempo2 *p,
|
||||
uint8_t **planes, int frame_size, double playback_rate);
|
||||
void mp_scaletempo2_set_final(struct mp_scaletempo2 *p);
|
||||
uint8_t **planes, int frame_size, bool final);
|
||||
int mp_scaletempo2_fill_buffer(struct mp_scaletempo2 *p,
|
||||
float **dest, int dest_size, double playback_rate);
|
||||
bool mp_scaletempo2_frames_available(struct mp_scaletempo2 *p, double playback_rate);
|
||||
float **dest, int dest_size, float playback_rate);
|
||||
bool mp_scaletempo2_frames_available(struct mp_scaletempo2 *p);
|
||||
+4
-4
@@ -187,7 +187,7 @@ int af_format_conversion_score(int dst_format, int src_format)
|
||||
return score;
|
||||
}
|
||||
|
||||
struct mp_entry {
|
||||
struct entry {
|
||||
int fmt;
|
||||
int score;
|
||||
};
|
||||
@@ -195,7 +195,7 @@ struct mp_entry {
|
||||
static int cmp_entry(const void *a, const void *b)
|
||||
{
|
||||
#define CMP_INT(a, b) (a > b ? 1 : (a < b ? -1 : 0))
|
||||
return -CMP_INT(((struct mp_entry *)a)->score, ((struct mp_entry *)b)->score);
|
||||
return -CMP_INT(((struct entry *)a)->score, ((struct entry *)b)->score);
|
||||
}
|
||||
|
||||
// Return a list of sample format compatible to src_format, sorted by order
|
||||
@@ -207,11 +207,11 @@ static int cmp_entry(const void *a, const void *b)
|
||||
void af_get_best_sample_formats(int src_format, int *out_formats)
|
||||
{
|
||||
int num = 0;
|
||||
struct mp_entry e[AF_FORMAT_COUNT + 1];
|
||||
struct entry e[AF_FORMAT_COUNT + 1];
|
||||
for (int fmt = 1; fmt < AF_FORMAT_COUNT; fmt++) {
|
||||
int score = af_format_conversion_score(fmt, src_format);
|
||||
if (score > INT_MIN)
|
||||
e[num++] = (struct mp_entry){fmt, score};
|
||||
e[num++] = (struct entry){fmt, score};
|
||||
}
|
||||
qsort(e, num, sizeof(e[0]), cmp_entry);
|
||||
for (int n = 0; n < num; n++)
|
||||
|
||||
+15
-40
@@ -41,8 +41,6 @@ extern const struct ao_driver audio_out_audiounit;
|
||||
extern const struct ao_driver audio_out_coreaudio;
|
||||
extern const struct ao_driver audio_out_coreaudio_exclusive;
|
||||
extern const struct ao_driver audio_out_rsound;
|
||||
extern const struct ao_driver audio_out_pipewire;
|
||||
extern const struct ao_driver audio_out_sndio;
|
||||
extern const struct ao_driver audio_out_pulse;
|
||||
extern const struct ao_driver audio_out_jack;
|
||||
extern const struct ao_driver audio_out_openal;
|
||||
@@ -65,9 +63,6 @@ static const struct ao_driver * const audio_out_drivers[] = {
|
||||
#if HAVE_COREAUDIO
|
||||
&audio_out_coreaudio,
|
||||
#endif
|
||||
#if HAVE_PIPEWIRE
|
||||
&audio_out_pipewire,
|
||||
#endif
|
||||
#if HAVE_PULSE
|
||||
&audio_out_pulse,
|
||||
#endif
|
||||
@@ -92,9 +87,6 @@ static const struct ao_driver * const audio_out_drivers[] = {
|
||||
#endif
|
||||
#if HAVE_SDL2_AUDIO
|
||||
&audio_out_sdl,
|
||||
#endif
|
||||
#if HAVE_SNDIO
|
||||
&audio_out_sndio,
|
||||
#endif
|
||||
&audio_out_null,
|
||||
#if HAVE_COREAUDIO
|
||||
@@ -102,11 +94,12 @@ static const struct ao_driver * const audio_out_drivers[] = {
|
||||
#endif
|
||||
&audio_out_pcm,
|
||||
&audio_out_lavc,
|
||||
NULL
|
||||
};
|
||||
|
||||
static bool get_desc(struct m_obj_desc *dst, int index)
|
||||
{
|
||||
if (index >= MP_ARRAY_SIZE(audio_out_drivers))
|
||||
if (index >= MP_ARRAY_SIZE(audio_out_drivers) - 1)
|
||||
return false;
|
||||
const struct ao_driver *ao = audio_out_drivers[index];
|
||||
*dst = (struct m_obj_desc) {
|
||||
@@ -127,6 +120,7 @@ static bool get_desc(struct m_obj_desc *dst, int index)
|
||||
static const struct m_obj_list ao_obj_list = {
|
||||
.get_desc = get_desc,
|
||||
.description = "audio outputs",
|
||||
.allow_unknown_entries = true,
|
||||
.allow_trailer = true,
|
||||
.disallow_positional_parameters = true,
|
||||
.use_global_options = true,
|
||||
@@ -312,7 +306,7 @@ struct ao *ao_init_best(struct mpv_global *global,
|
||||
}
|
||||
|
||||
if (autoprobe) {
|
||||
for (int n = 0; n < MP_ARRAY_SIZE(audio_out_drivers); n++) {
|
||||
for (int n = 0; audio_out_drivers[n]; n++) {
|
||||
const struct ao_driver *driver = audio_out_drivers[n];
|
||||
if (driver == &audio_out_null)
|
||||
break;
|
||||
@@ -452,9 +446,8 @@ struct ao_hotplug {
|
||||
void *wakeup_ctx;
|
||||
// A single AO instance is used to listen to hotplug events. It wouldn't
|
||||
// make much sense to allow multiple AO drivers; all sane platforms have
|
||||
// a single audio API providing all events.
|
||||
// This is _not_ necessarily the same AO instance as used for playing
|
||||
// audio.
|
||||
// a single such audio API.
|
||||
// This is _not_ the same AO instance as used for playing audio.
|
||||
struct ao *ao;
|
||||
// cached
|
||||
struct ao_device_list *list;
|
||||
@@ -494,8 +487,7 @@ bool ao_hotplug_check_update(struct ao_hotplug *hp)
|
||||
}
|
||||
|
||||
// The return value is valid until the next call to this API.
|
||||
struct ao_device_list *ao_hotplug_get_device_list(struct ao_hotplug *hp,
|
||||
struct ao *playback_ao)
|
||||
struct ao_device_list *ao_hotplug_get_device_list(struct ao_hotplug *hp)
|
||||
{
|
||||
if (hp->list && !hp->needs_update)
|
||||
return hp->list;
|
||||
@@ -507,20 +499,7 @@ struct ao_device_list *ao_hotplug_get_device_list(struct ao_hotplug *hp,
|
||||
MP_TARRAY_APPEND(list, list->devices, list->num_devices,
|
||||
(struct ao_device_desc){"auto", "Autoselect device"});
|
||||
|
||||
// Try to use the same AO for hotplug handling as for playback.
|
||||
// Different AOs may not agree and the playback one is the only one the
|
||||
// user knows about and may even have configured explicitly.
|
||||
if (!hp->ao && playback_ao && playback_ao->driver->hotplug_init) {
|
||||
struct ao *ao = ao_alloc(true, hp->global, hp->wakeup_cb, hp->wakeup_ctx,
|
||||
(char *)playback_ao->driver->name);
|
||||
if (playback_ao->driver->hotplug_init(ao) >= 0) {
|
||||
hp->ao = ao;
|
||||
} else {
|
||||
talloc_free(ao);
|
||||
}
|
||||
}
|
||||
|
||||
for (int n = 0; n < MP_ARRAY_SIZE(audio_out_drivers); n++) {
|
||||
for (int n = 0; audio_out_drivers[n]; n++) {
|
||||
const struct ao_driver *d = audio_out_drivers[n];
|
||||
if (d == &audio_out_null)
|
||||
break; // don't add unsafe/special entries
|
||||
@@ -531,13 +510,10 @@ struct ao_device_list *ao_hotplug_get_device_list(struct ao_hotplug *hp,
|
||||
continue;
|
||||
|
||||
if (ao->driver->hotplug_init) {
|
||||
if (ao->driver->hotplug_init(ao) >= 0) {
|
||||
get_devices(ao, list);
|
||||
if (hp->ao)
|
||||
ao->driver->hotplug_uninit(ao);
|
||||
else
|
||||
hp->ao = ao; // keep this one
|
||||
}
|
||||
if (!hp->ao && ao->driver->hotplug_init(ao) >= 0)
|
||||
hp->ao = ao; // keep this one
|
||||
if (hp->ao && hp->ao->driver == d)
|
||||
get_devices(hp->ao, list);
|
||||
} else {
|
||||
get_devices(ao, list);
|
||||
}
|
||||
@@ -586,11 +562,10 @@ static void dummy_wakeup(void *ctx)
|
||||
{
|
||||
}
|
||||
|
||||
void ao_print_devices(struct mpv_global *global, struct mp_log *log,
|
||||
struct ao *playback_ao)
|
||||
void ao_print_devices(struct mpv_global *global, struct mp_log *log)
|
||||
{
|
||||
struct ao_hotplug *hp = ao_hotplug_create(global, dummy_wakeup, NULL);
|
||||
struct ao_device_list *list = ao_hotplug_get_device_list(hp, playback_ao);
|
||||
struct ao_device_list *list = ao_hotplug_get_device_list(hp);
|
||||
mp_info(log, "List of detected audio devices:\n");
|
||||
for (int n = 0; n < list->num_devices; n++) {
|
||||
struct ao_device_desc *desc = &list->devices[n];
|
||||
@@ -701,7 +676,7 @@ static void convert_plane(int type, void *data, int num_samples)
|
||||
break;
|
||||
}
|
||||
default:
|
||||
MP_ASSERT_UNREACHABLE();
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+10
-6
@@ -26,7 +26,8 @@
|
||||
#include "audio/chmap_sel.h"
|
||||
|
||||
enum aocontrol {
|
||||
// _VOLUME commands take a pointer to float for input/output.
|
||||
// _VOLUME commands take struct ao_control_vol pointer for input/output.
|
||||
// If there's only one volume, SET should use average of left/right.
|
||||
AOCONTROL_GET_VOLUME,
|
||||
AOCONTROL_SET_VOLUME,
|
||||
// _MUTE commands take a pointer to bool
|
||||
@@ -56,10 +57,13 @@ enum {
|
||||
AO_INIT_STREAM_SILENCE = 1 << 2,
|
||||
// Force exclusive mode, i.e. lock out the system mixer.
|
||||
AO_INIT_EXCLUSIVE = 1 << 3,
|
||||
// Initialize with music role.
|
||||
AO_INIT_MEDIA_ROLE_MUSIC = 1 << 4,
|
||||
};
|
||||
|
||||
typedef struct ao_control_vol {
|
||||
float left;
|
||||
float right;
|
||||
} ao_control_vol_t;
|
||||
|
||||
struct ao_device_desc {
|
||||
const char *name; // symbolic name; will be set on ao->device
|
||||
const char *desc; // verbose human readable name
|
||||
@@ -98,7 +102,7 @@ void ao_set_gain(struct ao *ao, float gain);
|
||||
double ao_get_delay(struct ao *ao);
|
||||
void ao_reset(struct ao *ao);
|
||||
void ao_start(struct ao *ao);
|
||||
void ao_set_paused(struct ao *ao, bool paused, bool eof);
|
||||
void ao_set_paused(struct ao *ao, bool paused);
|
||||
void ao_drain(struct ao *ao);
|
||||
bool ao_is_playing(struct ao *ao);
|
||||
struct mp_async_queue;
|
||||
@@ -115,8 +119,8 @@ struct ao_hotplug *ao_hotplug_create(struct mpv_global *global,
|
||||
void *wakeup_ctx);
|
||||
void ao_hotplug_destroy(struct ao_hotplug *hp);
|
||||
bool ao_hotplug_check_update(struct ao_hotplug *hp);
|
||||
struct ao_device_list *ao_hotplug_get_device_list(struct ao_hotplug *hp, struct ao *playback_ao);
|
||||
struct ao_device_list *ao_hotplug_get_device_list(struct ao_hotplug *hp);
|
||||
|
||||
void ao_print_devices(struct mpv_global *global, struct mp_log *log, struct ao *playback_ao);
|
||||
void ao_print_devices(struct mpv_global *global, struct mp_log *log);
|
||||
|
||||
#endif /* MPLAYER_AUDIO_OUT_H */
|
||||
+18
-15
@@ -34,6 +34,7 @@
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "options/options.h"
|
||||
#include "options/m_config.h"
|
||||
#include "options/m_option.h"
|
||||
@@ -56,9 +57,9 @@ struct ao_alsa_opts {
|
||||
char *mixer_device;
|
||||
char *mixer_name;
|
||||
int mixer_index;
|
||||
bool resample;
|
||||
bool ni;
|
||||
bool ignore_chmap;
|
||||
int resample;
|
||||
int ni;
|
||||
int ignore_chmap;
|
||||
int buffer_time;
|
||||
int frags;
|
||||
};
|
||||
@@ -66,12 +67,12 @@ struct ao_alsa_opts {
|
||||
#define OPT_BASE_STRUCT struct ao_alsa_opts
|
||||
static const struct m_sub_options ao_alsa_conf = {
|
||||
.opts = (const struct m_option[]) {
|
||||
{"alsa-resample", OPT_BOOL(resample)},
|
||||
{"alsa-resample", OPT_FLAG(resample)},
|
||||
{"alsa-mixer-device", OPT_STRING(mixer_device)},
|
||||
{"alsa-mixer-name", OPT_STRING(mixer_name)},
|
||||
{"alsa-mixer-index", OPT_INT(mixer_index), M_RANGE(0, 99)},
|
||||
{"alsa-non-interleaved", OPT_BOOL(ni)},
|
||||
{"alsa-ignore-chmap", OPT_BOOL(ignore_chmap)},
|
||||
{"alsa-non-interleaved", OPT_FLAG(ni)},
|
||||
{"alsa-ignore-chmap", OPT_FLAG(ignore_chmap)},
|
||||
{"alsa-buffer-time", OPT_INT(buffer_time), M_RANGE(0, INT_MAX)},
|
||||
{"alsa-periods", OPT_INT(frags), M_RANGE(0, INT_MAX)},
|
||||
{0}
|
||||
@@ -79,6 +80,8 @@ static const struct m_sub_options ao_alsa_conf = {
|
||||
.defaults = &(const struct ao_alsa_opts) {
|
||||
.mixer_device = "default",
|
||||
.mixer_name = "Master",
|
||||
.mixer_index = 0,
|
||||
.ni = 0,
|
||||
.buffer_time = 100000,
|
||||
.frags = 4,
|
||||
},
|
||||
@@ -165,13 +168,15 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
|
||||
switch (cmd) {
|
||||
case AOCONTROL_SET_VOLUME: {
|
||||
float *vol = arg;
|
||||
set_vol = *vol / f_multi + pmin + 0.5;
|
||||
ao_control_vol_t *vol = arg;
|
||||
set_vol = vol->left / f_multi + pmin + 0.5;
|
||||
|
||||
err = snd_mixer_selem_set_playback_volume(elem, 0, set_vol);
|
||||
CHECK_ALSA_ERROR("Error setting left channel");
|
||||
MP_DBG(ao, "left=%li, ", set_vol);
|
||||
|
||||
set_vol = vol->right / f_multi + pmin + 0.5;
|
||||
|
||||
err = snd_mixer_selem_set_playback_volume(elem, 1, set_vol);
|
||||
CHECK_ALSA_ERROR("Error setting right channel");
|
||||
MP_DBG(ao, "right=%li, pmin=%li, pmax=%li, mult=%f\n",
|
||||
@@ -179,14 +184,12 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
break;
|
||||
}
|
||||
case AOCONTROL_GET_VOLUME: {
|
||||
float *vol = arg;
|
||||
float left, right;
|
||||
ao_control_vol_t *vol = arg;
|
||||
snd_mixer_selem_get_playback_volume(elem, 0, &get_vol);
|
||||
left = (get_vol - pmin) * f_multi;
|
||||
vol->left = (get_vol - pmin) * f_multi;
|
||||
snd_mixer_selem_get_playback_volume(elem, 1, &get_vol);
|
||||
right = (get_vol - pmin) * f_multi;
|
||||
*vol = (left + right) / 2.0;
|
||||
MP_DBG(ao, "vol=%f\n", *vol);
|
||||
vol->right = (get_vol - pmin) * f_multi;
|
||||
MP_DBG(ao, "left=%f, right=%f\n", vol->left, vol->right);
|
||||
break;
|
||||
}
|
||||
case AOCONTROL_SET_MUTE: {
|
||||
@@ -560,7 +563,7 @@ static char *append_params(void *ta_parent, const char *device, const char *p)
|
||||
/* a simple list of parameters: add it at the end of the list */
|
||||
return talloc_asprintf(ta_parent, "%s,%s", device, p);
|
||||
}
|
||||
MP_ASSERT_UNREACHABLE();
|
||||
abort();
|
||||
}
|
||||
|
||||
static int try_open_device(struct ao *ao, const char *device, int mode)
|
||||
|
||||
+94
-115
@@ -54,7 +54,7 @@ struct priv {
|
||||
jfloatArray floatarray;
|
||||
jobject bbuf;
|
||||
|
||||
bool cfg_pcm_float;
|
||||
int cfg_pcm_float;
|
||||
int cfg_session_id;
|
||||
|
||||
bool needs_timestamp_offset;
|
||||
@@ -62,9 +62,9 @@ struct priv {
|
||||
|
||||
bool thread_terminate;
|
||||
bool thread_created;
|
||||
mp_thread thread;
|
||||
mp_mutex lock;
|
||||
mp_cond wakeup;
|
||||
pthread_t thread;
|
||||
pthread_mutex_t lock;
|
||||
pthread_cond_t wakeup;
|
||||
};
|
||||
|
||||
struct JNIByteBuffer {
|
||||
@@ -92,6 +92,7 @@ struct JNIAudioTrack {
|
||||
jmethodID pause;
|
||||
jmethodID write;
|
||||
jmethodID writeFloat;
|
||||
jmethodID writeV23;
|
||||
jmethodID writeShortV23;
|
||||
jmethodID writeBufferV21;
|
||||
jmethodID getBufferSizeInFramesV23;
|
||||
@@ -125,6 +126,7 @@ struct JNIAudioTrack {
|
||||
{"android/media/AudioTrack", "pause", "()V", MP_JNI_METHOD, OFFSET(pause), 1},
|
||||
{"android/media/AudioTrack", "write", "([BII)I", MP_JNI_METHOD, OFFSET(write), 1},
|
||||
{"android/media/AudioTrack", "write", "([FIII)I", MP_JNI_METHOD, OFFSET(writeFloat), 1},
|
||||
{"android/media/AudioTrack", "write", "([BIII)I", MP_JNI_METHOD, OFFSET(writeV23), 0},
|
||||
{"android/media/AudioTrack", "write", "([SIII)I", MP_JNI_METHOD, OFFSET(writeShortV23), 0},
|
||||
{"android/media/AudioTrack", "write", "(Ljava/nio/ByteBuffer;II)I", MP_JNI_METHOD, OFFSET(writeBufferV21), 1},
|
||||
{"android/media/AudioTrack", "getBufferSizeInFrames", "()I", MP_JNI_METHOD, OFFSET(getBufferSizeInFramesV23), 0},
|
||||
@@ -150,14 +152,12 @@ struct JNIAudioTrack {
|
||||
struct JNIAudioAttributes {
|
||||
jclass clazz;
|
||||
jint CONTENT_TYPE_MOVIE;
|
||||
jint CONTENT_TYPE_MUSIC;
|
||||
jint USAGE_MEDIA;
|
||||
struct MPJniField mapping[];
|
||||
} AudioAttributes = {.mapping = {
|
||||
#define OFFSET(member) offsetof(struct JNIAudioAttributes, member)
|
||||
{"android/media/AudioAttributes", NULL, NULL, MP_JNI_CLASS, OFFSET(clazz), 0},
|
||||
{"android/media/AudioAttributes", "CONTENT_TYPE_MOVIE", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CONTENT_TYPE_MOVIE), 0},
|
||||
{"android/media/AudioAttributes", "CONTENT_TYPE_MUSIC", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CONTENT_TYPE_MUSIC), 0},
|
||||
{"android/media/AudioAttributes", "USAGE_MEDIA", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(USAGE_MEDIA), 0},
|
||||
{0}
|
||||
#undef OFFSET
|
||||
@@ -187,13 +187,19 @@ struct JNIAudioFormat {
|
||||
jint ENCODING_PCM_16BIT;
|
||||
jint ENCODING_PCM_FLOAT;
|
||||
jint ENCODING_IEC61937;
|
||||
jint ENCODING_AC3;
|
||||
jint CHANNEL_OUT_MONO;
|
||||
jint CHANNEL_OUT_STEREO;
|
||||
jint CHANNEL_OUT_FRONT_LEFT;
|
||||
jint CHANNEL_OUT_FRONT_RIGHT;
|
||||
jint CHANNEL_OUT_BACK_LEFT;
|
||||
jint CHANNEL_OUT_BACK_RIGHT;
|
||||
jint CHANNEL_OUT_FRONT_CENTER;
|
||||
jint CHANNEL_OUT_QUAD;
|
||||
jint CHANNEL_OUT_5POINT1;
|
||||
jint CHANNEL_OUT_LOW_FREQUENCY;
|
||||
jint CHANNEL_OUT_BACK_CENTER;
|
||||
jint CHANNEL_OUT_7POINT1_SURROUND;
|
||||
jint CHANNEL_OUT_5POINT1;
|
||||
jint CHANNEL_OUT_SIDE_LEFT;
|
||||
jint CHANNEL_OUT_SIDE_RIGHT;
|
||||
struct MPJniField mapping[];
|
||||
} AudioFormat = {.mapping = {
|
||||
#define OFFSET(member) offsetof(struct JNIAudioFormat, member)
|
||||
@@ -201,14 +207,20 @@ struct JNIAudioFormat {
|
||||
{"android/media/AudioFormat", "ENCODING_PCM_8BIT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ENCODING_PCM_8BIT), 1},
|
||||
{"android/media/AudioFormat", "ENCODING_PCM_16BIT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ENCODING_PCM_16BIT), 1},
|
||||
{"android/media/AudioFormat", "ENCODING_PCM_FLOAT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ENCODING_PCM_FLOAT), 1},
|
||||
{"android/media/AudioFormat", "ENCODING_AC3", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ENCODING_AC3), 0},
|
||||
{"android/media/AudioFormat", "ENCODING_IEC61937", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ENCODING_IEC61937), 0},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_MONO", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_MONO), 1},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_STEREO", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_STEREO), 1},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_FRONT_CENTER", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_FRONT_CENTER), 1},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_QUAD", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_QUAD), 1},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_5POINT1", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_5POINT1), 1},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_FRONT_LEFT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_FRONT_LEFT), 1},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_FRONT_RIGHT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_FRONT_RIGHT), 1},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_FRONT_CENTER", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_FRONT_CENTER), 1},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_LOW_FREQUENCY", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_LOW_FREQUENCY), 1},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_BACK_LEFT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_BACK_LEFT), 1},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_BACK_RIGHT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_BACK_RIGHT), 1},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_BACK_CENTER", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_BACK_CENTER), 1},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_7POINT1_SURROUND", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_7POINT1_SURROUND), 0},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_SIDE_LEFT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_SIDE_LEFT), 0},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_SIDE_RIGHT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_SIDE_RIGHT), 0},
|
||||
{0}
|
||||
#undef OFFSET
|
||||
}};
|
||||
@@ -291,9 +303,7 @@ static int AudioTrack_New(struct ao *ao)
|
||||
MP_JNI_EXCEPTION_LOG(ao);
|
||||
tmp = MP_JNI_CALL_OBJECT(attr_builder, AudioAttributesBuilder.setUsage, AudioAttributes.USAGE_MEDIA);
|
||||
MP_JNI_DELETELOCAL(tmp);
|
||||
jint content_type = (ao->init_flags & AO_INIT_MEDIA_ROLE_MUSIC) ?
|
||||
AudioAttributes.CONTENT_TYPE_MUSIC : AudioAttributes.CONTENT_TYPE_MOVIE;
|
||||
tmp = MP_JNI_CALL_OBJECT(attr_builder, AudioAttributesBuilder.setContentType, content_type);
|
||||
tmp = MP_JNI_CALL_OBJECT(attr_builder, AudioAttributesBuilder.setContentType, AudioAttributes.CONTENT_TYPE_MOVIE);
|
||||
MP_JNI_DELETELOCAL(tmp);
|
||||
jobject attr = MP_JNI_CALL_OBJECT(attr_builder, AudioAttributesBuilder.build);
|
||||
MP_JNI_DELETELOCAL(attr_builder);
|
||||
@@ -324,7 +334,7 @@ static int AudioTrack_New(struct ao *ao)
|
||||
p->cfg_session_id
|
||||
);
|
||||
}
|
||||
if (MP_JNI_EXCEPTION_LOG(ao) < 0 || !audiotrack) {
|
||||
if (!audiotrack || MP_JNI_EXCEPTION_LOG(ao) < 0) {
|
||||
MP_FATAL(ao, "AudioTrack Init failed\n");
|
||||
return -1;
|
||||
}
|
||||
@@ -372,26 +382,26 @@ static uint32_t AudioTrack_getPlaybackHeadPosition(struct ao *ao)
|
||||
return 0;
|
||||
JNIEnv *env = MP_JNI_GET_ENV(ao);
|
||||
uint32_t pos = 0;
|
||||
int64_t now = mp_raw_time_ns();
|
||||
int64_t now = mp_raw_time_us();
|
||||
int state = MP_JNI_CALL_INT(p->audiotrack, AudioTrack.getPlayState);
|
||||
|
||||
int stable_count = 20;
|
||||
int64_t wait = p->timestamp_stable < stable_count ? 50000000 : 3000000000;
|
||||
int64_t wait = p->timestamp_stable < stable_count ? 50000 : 3000000;
|
||||
|
||||
if (state == AudioTrack.PLAYSTATE_PLAYING && p->format != AudioFormat.ENCODING_IEC61937 &&
|
||||
(p->timestamp_fetched == 0 || now - p->timestamp_fetched >= wait)) {
|
||||
if (!p->timestamp_fetched)
|
||||
p->timestamp_stable = 0;
|
||||
|
||||
int64_t time1 = MP_JNI_GET_LONG(p->timestamp, AudioTimestamp.nanoTime);
|
||||
int64_t utime1 = MP_JNI_GET_LONG(p->timestamp, AudioTimestamp.nanoTime) / 1000;
|
||||
if (MP_JNI_CALL_BOOL(p->audiotrack, AudioTrack.getTimestamp, p->timestamp)) {
|
||||
p->timestamp_set = true;
|
||||
p->timestamp_fetched = now;
|
||||
if (p->timestamp_stable < stable_count) {
|
||||
uint32_t fpos = 0xFFFFFFFFL & MP_JNI_GET_LONG(p->timestamp, AudioTimestamp.framePosition);
|
||||
int64_t time2 = MP_JNI_GET_LONG(p->timestamp, AudioTimestamp.nanoTime);
|
||||
//MP_VERBOSE(ao, "getTimestamp: fpos= %u / time= %"PRId64" / now= %"PRId64" / stable= %d\n", fpos, time2, now, p->timestamp_stable);
|
||||
if (time1 != time2 && time2 != 0 && fpos != 0) {
|
||||
int64_t utime2 = MP_JNI_GET_LONG(p->timestamp, AudioTimestamp.nanoTime) / 1000;
|
||||
//MP_VERBOSE(ao, "getTimestamp: fpos= %u / time= %"PRId64" / now= %"PRId64" / stable= %d\n", fpos, utime2, now, p->timestamp_stable);
|
||||
if (utime1 != utime2 && utime2 != 0 && fpos != 0) {
|
||||
p->timestamp_stable++;
|
||||
}
|
||||
}
|
||||
@@ -404,19 +414,19 @@ static uint32_t AudioTrack_getPlaybackHeadPosition(struct ao *ao)
|
||||
if (p->timestamp_set) {
|
||||
pos = 0xFFFFFFFFL & MP_JNI_GET_LONG(p->timestamp, AudioTimestamp.framePosition);
|
||||
uint32_t fpos = pos;
|
||||
int64_t time = MP_JNI_GET_LONG(p->timestamp, AudioTimestamp.nanoTime);
|
||||
if (time == 0)
|
||||
int64_t utime = MP_JNI_GET_LONG(p->timestamp, AudioTimestamp.nanoTime) / 1000;
|
||||
if (utime == 0)
|
||||
fpos = pos = 0;
|
||||
if (p->needs_timestamp_offset) {
|
||||
if (time != 0 && !p->timestamp_offset)
|
||||
p->timestamp_offset = now - time;
|
||||
time += p->timestamp_offset;
|
||||
if (utime != 0 && !p->timestamp_offset)
|
||||
p->timestamp_offset = now - utime;
|
||||
utime += p->timestamp_offset;
|
||||
}
|
||||
if (fpos != 0 && time != 0 && state == AudioTrack.PLAYSTATE_PLAYING) {
|
||||
double diff = (double)(now - time) / 1e9;
|
||||
if (fpos != 0 && utime != 0 && state == AudioTrack.PLAYSTATE_PLAYING) {
|
||||
double diff = (double)(now - utime) / 1e6;
|
||||
pos += diff * ao->samplerate;
|
||||
}
|
||||
//MP_VERBOSE(ao, "position = %u via getTimestamp (state = %d / fpos= %u / time= %"PRId64")\n", pos, state, fpos, time);
|
||||
//MP_VERBOSE(ao, "position = %u via getTimestamp (state = %d / fpos= %u / time= %"PRId64")\n", pos, state, fpos, utime);
|
||||
} else {
|
||||
pos = 0xFFFFFFFFL & MP_JNI_CALL_INT(p->audiotrack, AudioTrack.getPlaybackHeadPosition);
|
||||
//MP_VERBOSE(ao, "playbackHeadPosition = %u (reset_pending=%d)\n", pos, p->reset_pending);
|
||||
@@ -493,14 +503,6 @@ static int AudioTrack_write(struct ao *ao, int len)
|
||||
if (MP_JNI_EXCEPTION_LOG(ao) < 0) return -1;
|
||||
if (ret > 0) ret *= 2;
|
||||
|
||||
} else if (AudioTrack.writeBufferV21) {
|
||||
// reset positions for reading
|
||||
jobject bbuf = MP_JNI_CALL_OBJECT(p->bbuf, ByteBuffer.clear);
|
||||
if (MP_JNI_EXCEPTION_LOG(ao) < 0) return -1;
|
||||
(*env)->DeleteLocalRef(env, bbuf);
|
||||
ret = MP_JNI_CALL_INT(p->audiotrack, AudioTrack.writeBufferV21, p->bbuf, len, AudioTrack.WRITE_BLOCKING);
|
||||
if (MP_JNI_EXCEPTION_LOG(ao) < 0) return -1;
|
||||
|
||||
} else if (p->format == AudioFormat.ENCODING_PCM_FLOAT) {
|
||||
(*env)->SetFloatArrayRegion(env, p->floatarray, 0, len / sizeof(float), buf);
|
||||
if (MP_JNI_EXCEPTION_LOG(ao) < 0) return -1;
|
||||
@@ -508,6 +510,13 @@ static int AudioTrack_write(struct ao *ao, int len)
|
||||
if (MP_JNI_EXCEPTION_LOG(ao) < 0) return -1;
|
||||
if (ret > 0) ret *= sizeof(float);
|
||||
|
||||
} else if (AudioTrack.writeBufferV21) {
|
||||
jobject bbuf = MP_JNI_CALL_OBJECT(p->bbuf, ByteBuffer.clear);
|
||||
if (MP_JNI_EXCEPTION_LOG(ao) < 0) return -1;
|
||||
(*env)->DeleteLocalRef(env, bbuf);
|
||||
ret = MP_JNI_CALL_INT(p->audiotrack, AudioTrack.writeBufferV21, p->bbuf, len, AudioTrack.WRITE_BLOCKING);
|
||||
if (MP_JNI_EXCEPTION_LOG(ao) < 0) return -1;
|
||||
|
||||
} else {
|
||||
(*env)->SetByteArrayRegion(env, p->bytearray, 0, len, buf);
|
||||
if (MP_JNI_EXCEPTION_LOG(ao) < 0) return -1;
|
||||
@@ -549,13 +558,13 @@ static int init_jni(struct ao *ao)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static MP_THREAD_VOID playthread(void *arg)
|
||||
static void *playthread(void *arg)
|
||||
{
|
||||
struct ao *ao = arg;
|
||||
struct priv *p = ao->priv;
|
||||
JNIEnv *env = MP_JNI_GET_ENV(ao);
|
||||
mp_thread_set_name("ao/audiotrack");
|
||||
mp_mutex_lock(&p->lock);
|
||||
mpthread_set_name("audiotrack");
|
||||
pthread_mutex_lock(&p->lock);
|
||||
while (!p->thread_terminate) {
|
||||
int state = AudioTrack.PLAYSTATE_PAUSED;
|
||||
if (p->audiotrack) {
|
||||
@@ -563,11 +572,12 @@ static MP_THREAD_VOID playthread(void *arg)
|
||||
}
|
||||
if (state == AudioTrack.PLAYSTATE_PLAYING) {
|
||||
int read_samples = p->chunksize / ao->sstride;
|
||||
int64_t ts = mp_time_ns();
|
||||
ts += MP_TIME_S_TO_NS(read_samples / (double)(ao->samplerate));
|
||||
ts += MP_TIME_S_TO_NS(AudioTrack_getLatency(ao));
|
||||
int samples = ao_read_data_nonblocking(ao, &p->chunk, read_samples, ts);
|
||||
int ret = AudioTrack_write(ao, samples * ao->sstride);
|
||||
int64_t ts = mp_time_us();
|
||||
ts += (read_samples / (double)(ao->samplerate)) * 1e6;
|
||||
ts += AudioTrack_getLatency(ao) * 1e6;
|
||||
int samples = ao_read_data(ao, &p->chunk, read_samples, ts);
|
||||
int write_samples = read_samples;
|
||||
int ret = AudioTrack_write(ao, write_samples * ao->sstride);
|
||||
if (ret >= 0) {
|
||||
p->written_frames += ret / ao->sstride;
|
||||
} else if (ret == AudioManager.ERROR_DEAD_OBJECT) {
|
||||
@@ -579,11 +589,12 @@ static MP_THREAD_VOID playthread(void *arg)
|
||||
MP_ERR(ao, "AudioTrack.write failed with %d\n", ret);
|
||||
}
|
||||
} else {
|
||||
mp_cond_timedwait(&p->wakeup, &p->lock, MP_TIME_MS_TO_NS(300));
|
||||
struct timespec wait = mp_rel_time_to_timespec(0.300);
|
||||
pthread_cond_timedwait(&p->wakeup, &p->lock, &wait);
|
||||
}
|
||||
}
|
||||
mp_mutex_unlock(&p->lock);
|
||||
MP_THREAD_RETURN();
|
||||
pthread_mutex_unlock(&p->lock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void uninit(struct ao *ao)
|
||||
@@ -597,13 +608,13 @@ static void uninit(struct ao *ao)
|
||||
MP_JNI_EXCEPTION_LOG(ao);
|
||||
}
|
||||
|
||||
mp_mutex_lock(&p->lock);
|
||||
pthread_mutex_lock(&p->lock);
|
||||
p->thread_terminate = true;
|
||||
mp_cond_signal(&p->wakeup);
|
||||
mp_mutex_unlock(&p->lock);
|
||||
pthread_cond_signal(&p->wakeup);
|
||||
pthread_mutex_unlock(&p->lock);
|
||||
|
||||
if (p->thread_created)
|
||||
mp_thread_join(p->thread);
|
||||
pthread_join(p->thread, NULL);
|
||||
|
||||
if (p->audiotrack) {
|
||||
MP_JNI_CALL_VOID(p->audiotrack, AudioTrack.release);
|
||||
@@ -637,8 +648,13 @@ static void uninit(struct ao *ao)
|
||||
p->timestamp = NULL;
|
||||
}
|
||||
|
||||
mp_cond_destroy(&p->wakeup);
|
||||
mp_mutex_destroy(&p->lock);
|
||||
if (p->chunk) {
|
||||
free(p->chunk);
|
||||
p->chunk = NULL;
|
||||
}
|
||||
|
||||
pthread_cond_destroy(&p->wakeup);
|
||||
pthread_mutex_destroy(&p->lock);
|
||||
|
||||
uninit_jni(ao);
|
||||
}
|
||||
@@ -650,8 +666,8 @@ static int init(struct ao *ao)
|
||||
if (!env)
|
||||
return -1;
|
||||
|
||||
mp_mutex_init(&p->lock);
|
||||
mp_cond_init(&p->wakeup);
|
||||
pthread_mutex_init(&p->lock, NULL);
|
||||
pthread_cond_init(&p->wakeup, NULL);
|
||||
|
||||
if (init_jni(ao) < 0)
|
||||
return -1;
|
||||
@@ -660,7 +676,7 @@ static int init(struct ao *ao)
|
||||
p->format = AudioFormat.ENCODING_IEC61937;
|
||||
} else if (ao->format == AF_FORMAT_U8) {
|
||||
p->format = AudioFormat.ENCODING_PCM_8BIT;
|
||||
} else if (p->cfg_pcm_float && af_fmt_is_float(ao->format)) {
|
||||
} else if (p->cfg_pcm_float && (ao->format == AF_FORMAT_FLOAT || ao->format == AF_FORMAT_FLOATP)) {
|
||||
ao->format = AF_FORMAT_FLOAT;
|
||||
p->format = AudioFormat.ENCODING_PCM_FLOAT;
|
||||
} else {
|
||||
@@ -675,48 +691,22 @@ static int init(struct ao *ao)
|
||||
AudioManager.STREAM_MUSIC
|
||||
);
|
||||
if (MP_JNI_EXCEPTION_LOG(ao) == 0) {
|
||||
ao->samplerate = samplerate;
|
||||
MP_VERBOSE(ao, "AudioTrack.nativeOutputSampleRate = %d\n", samplerate);
|
||||
ao->samplerate = MPMIN(samplerate, ao->samplerate);
|
||||
}
|
||||
}
|
||||
p->samplerate = ao->samplerate;
|
||||
|
||||
/* https://developer.android.com/reference/android/media/AudioFormat#channelPositionMask */
|
||||
static const struct mp_chmap layouts[] = {
|
||||
{0}, // empty
|
||||
MP_CHMAP_INIT_MONO, // mono
|
||||
MP_CHMAP_INIT_STEREO, // stereo
|
||||
MP_CHMAP3(FL, FR, FC), // 3.0
|
||||
MP_CHMAP4(FL, FR, BL, BR), // quad
|
||||
MP_CHMAP5(FL, FR, FC, BL, BR), // 5.0
|
||||
MP_CHMAP6(FL, FR, FC, LFE, BL, BR), // 5.1
|
||||
MP_CHMAP7(FL, FR, FC, LFE, BL, BR, BC), // 6.1
|
||||
MP_CHMAP8(FL, FR, FC, LFE, BL, BR, SL, SR), // 7.1
|
||||
};
|
||||
const jint layout_map[] = {
|
||||
0,
|
||||
AudioFormat.CHANNEL_OUT_MONO,
|
||||
AudioFormat.CHANNEL_OUT_STEREO,
|
||||
AudioFormat.CHANNEL_OUT_STEREO | AudioFormat.CHANNEL_OUT_FRONT_CENTER,
|
||||
AudioFormat.CHANNEL_OUT_QUAD,
|
||||
AudioFormat.CHANNEL_OUT_QUAD | AudioFormat.CHANNEL_OUT_FRONT_CENTER,
|
||||
AudioFormat.CHANNEL_OUT_5POINT1,
|
||||
AudioFormat.CHANNEL_OUT_5POINT1 | AudioFormat.CHANNEL_OUT_BACK_CENTER,
|
||||
AudioFormat.CHANNEL_OUT_7POINT1_SURROUND,
|
||||
};
|
||||
static_assert(MP_ARRAY_SIZE(layout_map) == MP_ARRAY_SIZE(layouts), "");
|
||||
if (p->format == AudioFormat.ENCODING_IEC61937) {
|
||||
p->channel_config = AudioFormat.CHANNEL_OUT_STEREO;
|
||||
} else if (ao->channels.num == 1) {
|
||||
p->channel_config = AudioFormat.CHANNEL_OUT_MONO;
|
||||
} else if (ao->channels.num == 6) {
|
||||
p->channel_config = AudioFormat.CHANNEL_OUT_5POINT1;
|
||||
ao->channels = (struct mp_chmap)MP_CHMAP6(FL, FR, FC, LFE, BL, BR);
|
||||
} else {
|
||||
struct mp_chmap_sel sel = {0};
|
||||
for (int i = 0; i < MP_ARRAY_SIZE(layouts); i++) {
|
||||
if (layout_map[i])
|
||||
mp_chmap_sel_add_map(&sel, &layouts[i]);
|
||||
}
|
||||
if (!ao_chmap_sel_adjust(ao, &sel, &ao->channels))
|
||||
goto error;
|
||||
p->channel_config = layout_map[ao->channels.num];
|
||||
assert(p->channel_config);
|
||||
p->channel_config = AudioFormat.CHANNEL_OUT_STEREO;
|
||||
ao->channels = (struct mp_chmap)MP_CHMAP_INIT_STEREO;
|
||||
}
|
||||
|
||||
jint buffer_size = MP_JNI_CALL_STATIC_INT(
|
||||
@@ -726,48 +716,40 @@ static int init(struct ao *ao)
|
||||
p->channel_config,
|
||||
p->format
|
||||
);
|
||||
if (MP_JNI_EXCEPTION_LOG(ao) < 0 || buffer_size <= 0) {
|
||||
if (buffer_size <= 0 || MP_JNI_EXCEPTION_LOG(ao) < 0) {
|
||||
MP_FATAL(ao, "AudioTrack.getMinBufferSize returned an invalid size: %d", buffer_size);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Choose double of the minimum buffer size suggested by the driver, but not
|
||||
// less than 75ms or more than 150ms.
|
||||
const int bps = af_fmt_to_bytes(ao->format);
|
||||
int min = 0.075 * p->samplerate * bps * ao->channels.num;
|
||||
int min = 0.075 * p->samplerate * af_fmt_to_bytes(ao->format) * ao->channels.num;
|
||||
int max = min * 2;
|
||||
min = MP_ALIGN_UP(min, bps);
|
||||
max = MP_ALIGN_UP(max, bps);
|
||||
p->size = MPCLAMP(buffer_size * 2, min, max);
|
||||
MP_VERBOSE(ao, "Setting bufferSize = %d (driver=%d, min=%d, max=%d)\n", p->size, buffer_size, min, max);
|
||||
assert(p->size % bps == 0);
|
||||
ao->device_buffer = p->size / bps;
|
||||
ao->device_buffer = p->size / af_fmt_to_bytes(ao->format);
|
||||
|
||||
p->chunksize = p->size;
|
||||
p->chunk = talloc_size(ao, p->size);
|
||||
p->chunk = malloc(p->size);
|
||||
|
||||
jobject timestamp = MP_JNI_NEW(AudioTimestamp.clazz, AudioTimestamp.ctor);
|
||||
if (MP_JNI_EXCEPTION_LOG(ao) < 0 || !timestamp) {
|
||||
if (!timestamp || MP_JNI_EXCEPTION_LOG(ao) < 0) {
|
||||
MP_FATAL(ao, "AudioTimestamp could not be created\n");
|
||||
return -1;
|
||||
}
|
||||
p->timestamp = (*env)->NewGlobalRef(env, timestamp);
|
||||
(*env)->DeleteLocalRef(env, timestamp);
|
||||
|
||||
// decide and create buffer of right type
|
||||
if (p->format == AudioFormat.ENCODING_IEC61937) {
|
||||
jshortArray shortarray = (*env)->NewShortArray(env, p->chunksize / 2);
|
||||
p->shortarray = (*env)->NewGlobalRef(env, shortarray);
|
||||
(*env)->DeleteLocalRef(env, shortarray);
|
||||
} else if (AudioTrack.writeBufferV21) {
|
||||
MP_VERBOSE(ao, "Using NIO ByteBuffer\n");
|
||||
jobject bbuf = (*env)->NewDirectByteBuffer(env, p->chunk, p->chunksize);
|
||||
p->bbuf = (*env)->NewGlobalRef(env, bbuf);
|
||||
(*env)->DeleteLocalRef(env, bbuf);
|
||||
} else if (p->format == AudioFormat.ENCODING_PCM_FLOAT) {
|
||||
jfloatArray floatarray = (*env)->NewFloatArray(env, p->chunksize / sizeof(float));
|
||||
p->floatarray = (*env)->NewGlobalRef(env, floatarray);
|
||||
(*env)->DeleteLocalRef(env, floatarray);
|
||||
} else if (AudioTrack.writeBufferV21) {
|
||||
jobject bbuf = (*env)->NewDirectByteBuffer(env, p->chunk, p->chunksize);
|
||||
p->bbuf = (*env)->NewGlobalRef(env, bbuf);
|
||||
(*env)->DeleteLocalRef(env, bbuf);
|
||||
} else {
|
||||
jbyteArray bytearray = (*env)->NewByteArray(env, p->chunksize);
|
||||
p->bytearray = (*env)->NewGlobalRef(env, bytearray);
|
||||
@@ -780,7 +762,7 @@ static int init(struct ao *ao)
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (mp_thread_create(&p->thread, playthread, ao)) {
|
||||
if (pthread_create(&p->thread, NULL, playthread, ao)) {
|
||||
MP_ERR(ao, "pthread creation failed\n");
|
||||
goto error;
|
||||
}
|
||||
@@ -827,7 +809,7 @@ static void start(struct ao *ao)
|
||||
MP_JNI_CALL_VOID(p->audiotrack, AudioTrack.play);
|
||||
MP_JNI_EXCEPTION_LOG(ao);
|
||||
|
||||
mp_cond_signal(&p->wakeup);
|
||||
pthread_cond_signal(&p->wakeup);
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
@@ -840,11 +822,8 @@ const struct ao_driver audio_out_audiotrack = {
|
||||
.reset = stop,
|
||||
.start = start,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.priv_defaults = &(const OPT_BASE_STRUCT) {
|
||||
.cfg_pcm_float = 1,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
{"pcm-float", OPT_BOOL(cfg_pcm_float)},
|
||||
{"pcm-float", OPT_FLAG(cfg_pcm_float)},
|
||||
{"session-id", OPT_INT(cfg_session_id)},
|
||||
{0}
|
||||
},
|
||||
|
||||
+18
-81
@@ -15,6 +15,7 @@
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "ao.h"
|
||||
#include "internal.h"
|
||||
#include "audio/format.h"
|
||||
@@ -35,54 +36,6 @@ struct priv {
|
||||
double device_latency;
|
||||
};
|
||||
|
||||
static OSStatus au_get_ary(AudioUnit unit, AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, void **data, UInt32 *outDataSize)
|
||||
{
|
||||
OSStatus err;
|
||||
|
||||
err = AudioUnitGetPropertyInfo(unit, inID, inScope, inElement, outDataSize, NULL);
|
||||
CHECK_CA_ERROR_SILENT_L(coreaudio_error);
|
||||
|
||||
*data = talloc_zero_size(NULL, *outDataSize);
|
||||
|
||||
err = AudioUnitGetProperty(unit, inID, inScope, inElement, *data, outDataSize);
|
||||
CHECK_CA_ERROR_SILENT_L(coreaudio_error_free);
|
||||
|
||||
return err;
|
||||
coreaudio_error_free:
|
||||
talloc_free(*data);
|
||||
coreaudio_error:
|
||||
return err;
|
||||
}
|
||||
|
||||
static AudioChannelLayout *convert_layout(AudioChannelLayout *layout, UInt32* size)
|
||||
{
|
||||
AudioChannelLayoutTag tag = layout->mChannelLayoutTag;
|
||||
AudioChannelLayout *new_layout;
|
||||
if (tag == kAudioChannelLayoutTag_UseChannelDescriptions)
|
||||
return layout;
|
||||
else if (tag == kAudioChannelLayoutTag_UseChannelBitmap)
|
||||
AudioFormatGetPropertyInfo(kAudioFormatProperty_ChannelLayoutForBitmap,
|
||||
sizeof(UInt32), &layout->mChannelBitmap, size);
|
||||
else
|
||||
AudioFormatGetPropertyInfo(kAudioFormatProperty_ChannelLayoutForTag,
|
||||
sizeof(AudioChannelLayoutTag), &tag, size);
|
||||
new_layout = talloc_zero_size(NULL, *size);
|
||||
if (!new_layout) {
|
||||
talloc_free(layout);
|
||||
return NULL;
|
||||
}
|
||||
if (tag == kAudioChannelLayoutTag_UseChannelBitmap)
|
||||
AudioFormatGetProperty(kAudioFormatProperty_ChannelLayoutForBitmap,
|
||||
sizeof(UInt32), &layout->mChannelBitmap, size, new_layout);
|
||||
else
|
||||
AudioFormatGetProperty(kAudioFormatProperty_ChannelLayoutForTag,
|
||||
sizeof(AudioChannelLayoutTag), &tag, size, new_layout);
|
||||
new_layout->mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions;
|
||||
talloc_free(layout);
|
||||
return new_layout;
|
||||
}
|
||||
|
||||
|
||||
static OSStatus render_cb_lpcm(void *ctx, AudioUnitRenderActionFlags *aflags,
|
||||
const AudioTimeStamp *ts, UInt32 bus,
|
||||
UInt32 frames, AudioBufferList *buffer_list)
|
||||
@@ -94,9 +47,9 @@ static OSStatus render_cb_lpcm(void *ctx, AudioUnitRenderActionFlags *aflags,
|
||||
for (int n = 0; n < ao->num_planes; n++)
|
||||
planes[n] = buffer_list->mBuffers[n].mData;
|
||||
|
||||
int64_t end = mp_time_ns();
|
||||
end += MP_TIME_S_TO_NS(p->device_latency);
|
||||
end += ca_get_latency(ts) + ca_frames_to_ns(ao, frames);
|
||||
int64_t end = mp_time_us();
|
||||
end += p->device_latency * 1e6;
|
||||
end += ca_get_latency(ts) + ca_frames_to_us(ao, frames);
|
||||
ao_read_data(ao, planes, frames, end);
|
||||
return noErr;
|
||||
}
|
||||
@@ -106,15 +59,12 @@ static bool init_audiounit(struct ao *ao)
|
||||
AudioStreamBasicDescription asbd;
|
||||
OSStatus err;
|
||||
uint32_t size;
|
||||
AudioChannelLayout *layout = NULL;
|
||||
struct priv *p = ao->priv;
|
||||
AVAudioSession *instance = AVAudioSession.sharedInstance;
|
||||
AVAudioSessionPortDescription *port = nil;
|
||||
NSInteger maxChannels = instance.maximumOutputNumberOfChannels;
|
||||
NSInteger prefChannels = MIN(maxChannels, ao->channels.num);
|
||||
|
||||
MP_VERBOSE(ao, "max channels: %ld, requested: %d\n", maxChannels, (int)ao->channels.num);
|
||||
|
||||
[instance setCategory:AVAudioSessionCategoryPlayback error:nil];
|
||||
[instance setMode:AVAudioSessionModeMoviePlayback error:nil];
|
||||
[instance setActive:YES error:nil];
|
||||
@@ -141,33 +91,23 @@ static bool init_audiounit(struct ao *ao)
|
||||
CHECK_CA_ERROR_L(coreaudio_error_component,
|
||||
"unable to initialize audio unit");
|
||||
|
||||
err = au_get_ary(p->audio_unit, kAudioUnitProperty_AudioChannelLayout, kAudioUnitScope_Output,
|
||||
0, (void**)&layout, &size);
|
||||
CHECK_CA_ERROR_L(coreaudio_error_audiounit,
|
||||
"unable to retrieve audio unit channel layout");
|
||||
|
||||
MP_VERBOSE(ao, "AU channel layout tag: %x (%x)\n", layout->mChannelLayoutTag, layout->mChannelBitmap);
|
||||
|
||||
layout = convert_layout(layout, &size);
|
||||
if (!layout) {
|
||||
MP_ERR(ao, "unable to convert channel layout to list format\n");
|
||||
goto coreaudio_error_audiounit;
|
||||
}
|
||||
|
||||
for (UInt32 i = 0; i < layout->mNumberChannelDescriptions; i++) {
|
||||
MP_VERBOSE(ao, "channel map: %i: %u\n", i, layout->mChannelDescriptions[i].mChannelLabel);
|
||||
}
|
||||
|
||||
if (af_fmt_is_spdif(ao->format) || instance.outputNumberOfChannels <= 2) {
|
||||
ao->channels = (struct mp_chmap)MP_CHMAP_INIT_STEREO;
|
||||
MP_VERBOSE(ao, "using stereo output\n");
|
||||
} else {
|
||||
ao->channels.num = (uint8_t)layout->mNumberChannelDescriptions;
|
||||
for (UInt32 i = 0; i < layout->mNumberChannelDescriptions; i++) {
|
||||
ao->channels.speaker[i] =
|
||||
ca_label_to_mp_speaker_id(layout->mChannelDescriptions[i].mChannelLabel);
|
||||
port = instance.currentRoute.outputs.firstObject;
|
||||
if (port.channels.count == 2 &&
|
||||
port.channels[0].channelLabel == kAudioChannelLabel_Unknown) {
|
||||
// Special case when using an HDMI adapter. The iOS device will
|
||||
// perform SPDIF conversion for us, so send all available channels
|
||||
// using the AC3 mapping.
|
||||
ao->channels = (struct mp_chmap)MP_CHMAP6(FL, FC, FR, SL, SR, LFE);
|
||||
} else {
|
||||
ao->channels.num = (uint8_t)port.channels.count;
|
||||
for (AVAudioSessionChannelDescription *ch in port.channels) {
|
||||
ao->channels.speaker[ch.channelNumber - 1] =
|
||||
ca_label_to_mp_speaker_id(ch.channelLabel);
|
||||
}
|
||||
}
|
||||
MP_VERBOSE(ao, "using standard channel mapping\n");
|
||||
}
|
||||
|
||||
ca_fill_asbd(ao, &asbd);
|
||||
@@ -192,8 +132,6 @@ static bool init_audiounit(struct ao *ao)
|
||||
CHECK_CA_ERROR_L(coreaudio_error_audiounit,
|
||||
"unable to set render callback on audio unit");
|
||||
|
||||
talloc_free(layout);
|
||||
|
||||
return true;
|
||||
|
||||
coreaudio_error_audiounit:
|
||||
@@ -201,7 +139,6 @@ coreaudio_error_audiounit:
|
||||
coreaudio_error_component:
|
||||
AudioComponentInstanceDispose(p->audio_unit);
|
||||
coreaudio_error:
|
||||
talloc_free(layout);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -217,7 +154,7 @@ static void start(struct ao *ao)
|
||||
struct priv *p = ao->priv;
|
||||
AVAudioSession *instance = AVAudioSession.sharedInstance;
|
||||
|
||||
p->device_latency = [instance outputLatency];
|
||||
p->device_latency = [instance outputLatency] + [instance IOBufferDuration];
|
||||
|
||||
OSStatus err = AudioOutputUnitStart(p->audio_unit);
|
||||
CHECK_CA_WARN("can't start audio unit");
|
||||
|
||||
+21
-27
@@ -17,6 +17,7 @@
|
||||
|
||||
#include <CoreAudio/HostTime.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "ao.h"
|
||||
#include "internal.h"
|
||||
#include "audio/format.h"
|
||||
@@ -31,12 +32,12 @@ struct priv {
|
||||
AudioDeviceID device;
|
||||
AudioUnit audio_unit;
|
||||
|
||||
uint64_t hw_latency_ns;
|
||||
uint64_t hw_latency_us;
|
||||
|
||||
AudioStreamBasicDescription original_asbd;
|
||||
AudioStreamID original_asbd_stream;
|
||||
|
||||
bool change_physical_format;
|
||||
int change_physical_format;
|
||||
};
|
||||
|
||||
static int64_t ca_get_hardware_latency(struct ao *ao) {
|
||||
@@ -53,13 +54,13 @@ static int64_t ca_get_hardware_latency(struct ao *ao) {
|
||||
&size);
|
||||
CHECK_CA_ERROR("cannot get audio unit latency");
|
||||
|
||||
uint64_t audiounit_latency_ns = MP_TIME_S_TO_NS(audiounit_latency_sec);
|
||||
uint64_t device_latency_ns = ca_get_device_latency_ns(ao, p->device);
|
||||
uint64_t audiounit_latency_us = audiounit_latency_sec * 1e6;
|
||||
uint64_t device_latency_us = ca_get_device_latency_us(ao, p->device);
|
||||
|
||||
MP_VERBOSE(ao, "audiounit latency [ns]: %lld\n", audiounit_latency_ns);
|
||||
MP_VERBOSE(ao, "device latency [ns]: %lld\n", device_latency_ns);
|
||||
MP_VERBOSE(ao, "audiounit latency [us]: %lld\n", audiounit_latency_us);
|
||||
MP_VERBOSE(ao, "device latency [us]: %lld\n", device_latency_us);
|
||||
|
||||
return audiounit_latency_ns + device_latency_ns;
|
||||
return audiounit_latency_us + device_latency_us;
|
||||
|
||||
coreaudio_error:
|
||||
return 0;
|
||||
@@ -76,20 +77,13 @@ static OSStatus render_cb_lpcm(void *ctx, AudioUnitRenderActionFlags *aflags,
|
||||
for (int n = 0; n < ao->num_planes; n++)
|
||||
planes[n] = buffer_list->mBuffers[n].mData;
|
||||
|
||||
int64_t end = mp_time_ns();
|
||||
end += p->hw_latency_ns + ca_get_latency(ts) + ca_frames_to_ns(ao, frames);
|
||||
int samples = ao_read_data_nonblocking(ao, planes, frames, end);
|
||||
|
||||
if (samples == 0)
|
||||
*aflags |= kAudioUnitRenderAction_OutputIsSilence;
|
||||
|
||||
for (int n = 0; n < buffer_list->mNumberBuffers; n++)
|
||||
buffer_list->mBuffers[n].mDataByteSize = samples * ao->sstride;
|
||||
|
||||
int64_t end = mp_time_us();
|
||||
end += p->hw_latency_us + ca_get_latency(ts) + ca_frames_to_us(ao, frames);
|
||||
ao_read_data(ao, planes, frames, end);
|
||||
return noErr;
|
||||
}
|
||||
|
||||
static int get_volume(struct ao *ao, float *vol) {
|
||||
static int get_volume(struct ao *ao, struct ao_control_vol *vol) {
|
||||
struct priv *p = ao->priv;
|
||||
float auvol;
|
||||
OSStatus err =
|
||||
@@ -97,15 +91,15 @@ static int get_volume(struct ao *ao, float *vol) {
|
||||
kAudioUnitScope_Global, 0, &auvol);
|
||||
|
||||
CHECK_CA_ERROR("could not get HAL output volume");
|
||||
*vol = auvol * 100.0;
|
||||
vol->left = vol->right = auvol * 100.0;
|
||||
return CONTROL_TRUE;
|
||||
coreaudio_error:
|
||||
return CONTROL_ERROR;
|
||||
}
|
||||
|
||||
static int set_volume(struct ao *ao, float *vol) {
|
||||
static int set_volume(struct ao *ao, struct ao_control_vol *vol) {
|
||||
struct priv *p = ao->priv;
|
||||
float auvol = *vol / 100.0;
|
||||
float auvol = (vol->left + vol->right) / 200.0;
|
||||
OSStatus err =
|
||||
AudioUnitSetParameter(p->audio_unit, kHALOutputParam_Volume,
|
||||
kAudioUnitScope_Global, 0, auvol, 0);
|
||||
@@ -295,7 +289,7 @@ static bool init_audiounit(struct ao *ao, AudioStreamBasicDescription asbd)
|
||||
CHECK_CA_ERROR_L(coreaudio_error_audiounit,
|
||||
"can't link audio unit to selected device");
|
||||
|
||||
p->hw_latency_ns = ca_get_hardware_latency(ao);
|
||||
p->hw_latency_us = ca_get_hardware_latency(ao);
|
||||
|
||||
AURenderCallbackStruct render_cb = (AURenderCallbackStruct) {
|
||||
.inputProc = render_cb_lpcm,
|
||||
@@ -320,11 +314,11 @@ coreaudio_error:
|
||||
return false;
|
||||
}
|
||||
|
||||
static void reset(struct ao *ao)
|
||||
static void stop(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
OSStatus err = AudioUnitReset(p->audio_unit, kAudioUnitScope_Global, 0);
|
||||
CHECK_CA_WARN("can't reset audio unit");
|
||||
OSStatus err = AudioOutputUnitStop(p->audio_unit);
|
||||
CHECK_CA_WARN("can't stop audio unit");
|
||||
}
|
||||
|
||||
static void start(struct ao *ao)
|
||||
@@ -421,14 +415,14 @@ const struct ao_driver audio_out_coreaudio = {
|
||||
.uninit = uninit,
|
||||
.init = init,
|
||||
.control = control,
|
||||
.reset = reset,
|
||||
.reset = stop,
|
||||
.start = start,
|
||||
.hotplug_init = hotplug_init,
|
||||
.hotplug_uninit = hotplug_uninit,
|
||||
.list_devs = ca_get_device_list,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.options = (const struct m_option[]){
|
||||
{"change-physical-format", OPT_BOOL(change_physical_format)},
|
||||
{"change-physical-format", OPT_FLAG(change_physical_format)},
|
||||
{0}
|
||||
},
|
||||
.options_prefix = "coreaudio",
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <Availability.h>
|
||||
|
||||
#include "common/common.h"
|
||||
|
||||
#include "ao_coreaudio_utils.h"
|
||||
@@ -49,13 +47,6 @@ static const int speaker_map[][2] = {
|
||||
{ kAudioChannelLabel_LeftWide, MP_SPEAKER_ID_WL },
|
||||
{ kAudioChannelLabel_RightWide, MP_SPEAKER_ID_WR },
|
||||
{ kAudioChannelLabel_LFE2, MP_SPEAKER_ID_LFE2 },
|
||||
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 130000
|
||||
{ kAudioChannelLabel_LeftTopSurround, MP_SPEAKER_ID_TSL },
|
||||
{ kAudioChannelLabel_RightTopSurround, MP_SPEAKER_ID_TSR },
|
||||
{ kAudioChannelLabel_CenterBottom, MP_SPEAKER_ID_BFC },
|
||||
{ kAudioChannelLabel_LeftBottom, MP_SPEAKER_ID_BFL },
|
||||
{ kAudioChannelLabel_RightBottom, MP_SPEAKER_ID_BFR },
|
||||
#endif
|
||||
|
||||
{ kAudioChannelLabel_HeadphonesLeft, MP_SPEAKER_ID_DL },
|
||||
{ kAudioChannelLabel_HeadphonesRight, MP_SPEAKER_ID_DR },
|
||||
|
||||
@@ -20,8 +20,6 @@
|
||||
|
||||
#include <AudioToolbox/AudioToolbox.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
struct mp_chmap;
|
||||
|
||||
int ca_label_to_mp_speaker_id(AudioChannelLabel label);
|
||||
|
||||
@@ -34,17 +34,17 @@
|
||||
* when you are wanting to do good buffering of audio).
|
||||
*/
|
||||
|
||||
#include <stdatomic.h>
|
||||
|
||||
#include <CoreAudio/HostTime.h>
|
||||
|
||||
#include <libavutil/intreadwrite.h>
|
||||
#include <libavutil/intfloat.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "ao.h"
|
||||
#include "internal.h"
|
||||
#include "audio/format.h"
|
||||
#include "osdep/timer.h"
|
||||
#include "osdep/atomic.h"
|
||||
#include "options/m_option.h"
|
||||
#include "common/msg.h"
|
||||
#include "audio/out/ao_coreaudio_chmap.h"
|
||||
@@ -73,13 +73,13 @@ struct priv {
|
||||
AudioStreamBasicDescription original_asbd;
|
||||
|
||||
// Output s16 physical format, float32 virtual format, ac3/dts mpv format
|
||||
bool spdif_hack;
|
||||
int spdif_hack;
|
||||
|
||||
bool changed_mixing;
|
||||
|
||||
atomic_bool reload_requested;
|
||||
|
||||
uint64_t hw_latency_ns;
|
||||
uint32_t hw_latency_us;
|
||||
};
|
||||
|
||||
static OSStatus property_listener_cb(
|
||||
@@ -177,9 +177,9 @@ static OSStatus render_cb_compressed(
|
||||
return kAudioHardwareUnspecifiedError;
|
||||
}
|
||||
|
||||
int64_t end = mp_time_ns();
|
||||
end += p->hw_latency_ns + ca_get_latency(ts)
|
||||
+ ca_frames_to_ns(ao, pseudo_frames);
|
||||
int64_t end = mp_time_us();
|
||||
end += p->hw_latency_us + ca_get_latency(ts)
|
||||
+ ca_frames_to_us(ao, pseudo_frames);
|
||||
|
||||
ao_read_data(ao, &buf.mData, pseudo_frames, end);
|
||||
|
||||
@@ -384,8 +384,8 @@ static int init(struct ao *ao)
|
||||
MP_WARN(ao, "Using spdif passthrough hack. This could produce noise.\n");
|
||||
}
|
||||
|
||||
p->hw_latency_ns = ca_get_device_latency_ns(ao, p->device);
|
||||
MP_VERBOSE(ao, "base latency: %lld nanoseconds\n", p->hw_latency_ns);
|
||||
p->hw_latency_us = ca_get_device_latency_us(ao, p->device);
|
||||
MP_VERBOSE(ao, "base latency: %d microseconds\n", (int)p->hw_latency_us);
|
||||
|
||||
err = enable_property_listener(ao, true);
|
||||
CHECK_CA_ERROR("cannot install format change listener during init");
|
||||
@@ -465,7 +465,7 @@ const struct ao_driver audio_out_coreaudio_exclusive = {
|
||||
.changed_mixing = false,
|
||||
},
|
||||
.options = (const struct m_option[]){
|
||||
{"spdif-hack", OPT_BOOL(spdif_hack)},
|
||||
{"spdif-hack", OPT_FLAG(spdif_hack)},
|
||||
{0}
|
||||
},
|
||||
.options_prefix = "coreaudio",
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
*/
|
||||
|
||||
/*
|
||||
* Abstractions on the CoreAudio API to make property setting/getting suck less
|
||||
* Abstractions on the CoreAudio API to make property setting/getting suck les
|
||||
*/
|
||||
|
||||
#include "audio/out/ao_coreaudio_properties.h"
|
||||
|
||||
@@ -35,6 +35,21 @@
|
||||
#include <mach/mach_time.h>
|
||||
#endif
|
||||
|
||||
CFStringRef cfstr_from_cstr(char *str)
|
||||
{
|
||||
return CFStringCreateWithCString(NULL, str, CA_CFSTR_ENCODING);
|
||||
}
|
||||
|
||||
char *cfstr_get_cstr(CFStringRef cfstr)
|
||||
{
|
||||
CFIndex size =
|
||||
CFStringGetMaximumSizeForEncoding(
|
||||
CFStringGetLength(cfstr), CA_CFSTR_ENCODING) + 1;
|
||||
char *buffer = talloc_zero_size(NULL, size);
|
||||
CFStringGetCString(cfstr, buffer, size, CA_CFSTR_ENCODING);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
#if HAVE_COREAUDIO
|
||||
static bool ca_is_output_device(struct ao *ao, AudioDeviceID dev)
|
||||
{
|
||||
@@ -292,9 +307,9 @@ bool ca_asbd_is_better(AudioStreamBasicDescription *req,
|
||||
return true;
|
||||
}
|
||||
|
||||
int64_t ca_frames_to_ns(struct ao *ao, uint32_t frames)
|
||||
int64_t ca_frames_to_us(struct ao *ao, uint32_t frames)
|
||||
{
|
||||
return MP_TIME_S_TO_NS(frames / (double)ao->samplerate);
|
||||
return frames / (float) ao->samplerate * 1e6;
|
||||
}
|
||||
|
||||
int64_t ca_get_latency(const AudioTimeStamp *ts)
|
||||
@@ -306,7 +321,7 @@ int64_t ca_get_latency(const AudioTimeStamp *ts)
|
||||
if (now > out)
|
||||
return 0;
|
||||
|
||||
return out - now;
|
||||
return (out - now) * 1e-3;
|
||||
#else
|
||||
static mach_timebase_info_data_t timebase;
|
||||
if (timebase.denom == 0)
|
||||
@@ -318,7 +333,7 @@ int64_t ca_get_latency(const AudioTimeStamp *ts)
|
||||
if (now > out)
|
||||
return 0;
|
||||
|
||||
return (out - now) * timebase.numer / timebase.denom;
|
||||
return (out - now) * timebase.numer / timebase.denom / 1e3;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -422,7 +437,7 @@ OSStatus ca_enable_mixing(struct ao *ao, AudioDeviceID device, bool changed)
|
||||
return noErr;
|
||||
}
|
||||
|
||||
int64_t ca_get_device_latency_ns(struct ao *ao, AudioDeviceID device)
|
||||
int64_t ca_get_device_latency_us(struct ao *ao, AudioDeviceID device)
|
||||
{
|
||||
uint32_t latency_frames = 0;
|
||||
uint32_t latency_properties[] = {
|
||||
@@ -441,15 +456,7 @@ int64_t ca_get_device_latency_ns(struct ao *ao, AudioDeviceID device)
|
||||
}
|
||||
}
|
||||
|
||||
double sample_rate = ao->samplerate;
|
||||
OSStatus err = CA_GET_O(device, kAudioDevicePropertyNominalSampleRate,
|
||||
&sample_rate);
|
||||
CHECK_CA_WARN("cannot get device sample rate, falling back to AO sample rate!");
|
||||
if (err == noErr) {
|
||||
MP_VERBOSE(ao, "Device sample rate: %f\n", sample_rate);
|
||||
}
|
||||
|
||||
return MP_TIME_S_TO_NS(latency_frames / sample_rate);
|
||||
return ca_frames_to_us(ao, latency_frames);
|
||||
}
|
||||
|
||||
static OSStatus ca_change_format_listener(
|
||||
@@ -457,8 +464,8 @@ static OSStatus ca_change_format_listener(
|
||||
const AudioObjectPropertyAddress addresses[],
|
||||
void *data)
|
||||
{
|
||||
mp_sem_t *sem = data;
|
||||
mp_sem_post(sem);
|
||||
sem_t *sem = data;
|
||||
sem_post(sem);
|
||||
return noErr;
|
||||
}
|
||||
|
||||
@@ -471,7 +478,7 @@ bool ca_change_physical_format_sync(struct ao *ao, AudioStreamID stream,
|
||||
ca_print_asbd(ao, "setting stream physical format:", &change_format);
|
||||
|
||||
sem_t wakeup;
|
||||
if (mp_sem_init(&wakeup, 0, 0)) {
|
||||
if (sem_init(&wakeup, 0, 0)) {
|
||||
MP_WARN(ao, "OOM\n");
|
||||
return false;
|
||||
}
|
||||
@@ -500,7 +507,7 @@ bool ca_change_physical_format_sync(struct ao *ao, AudioStreamID stream,
|
||||
|
||||
/* The AudioStreamSetProperty is not only asynchronous,
|
||||
* it is also not Atomic, in its behaviour. */
|
||||
int64_t wait_until = mp_time_ns() + MP_TIME_S_TO_NS(2);
|
||||
struct timespec timeout = mp_rel_time_to_timespec(2.0);
|
||||
AudioStreamBasicDescription actual_format = {0};
|
||||
while (1) {
|
||||
err = CA_GET(stream, kAudioStreamPropertyPhysicalFormat, &actual_format);
|
||||
@@ -511,7 +518,7 @@ bool ca_change_physical_format_sync(struct ao *ao, AudioStreamID stream,
|
||||
if (format_set)
|
||||
break;
|
||||
|
||||
if (mp_sem_timedwait(&wakeup, wait_until)) {
|
||||
if (sem_timedwait(&wakeup, &timeout)) {
|
||||
MP_VERBOSE(ao, "reached timeout\n");
|
||||
break;
|
||||
}
|
||||
@@ -533,7 +540,7 @@ bool ca_change_physical_format_sync(struct ao *ao, AudioStreamID stream,
|
||||
CHECK_CA_ERROR("can't remove property listener");
|
||||
|
||||
coreaudio_error:
|
||||
mp_sem_destroy(&wakeup);
|
||||
sem_destroy(&wakeup);
|
||||
return format_set;
|
||||
}
|
||||
#endif
|
||||
@@ -22,12 +22,14 @@
|
||||
#include <AudioToolbox/AudioToolbox.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "common/msg.h"
|
||||
#include "audio/out/ao.h"
|
||||
#include "internal.h"
|
||||
#include "osdep/apple_utils.h"
|
||||
|
||||
#define CA_CFSTR_ENCODING kCFStringEncodingASCII
|
||||
|
||||
CFStringRef cfstr_from_cstr(char *str);
|
||||
char *cfstr_get_cstr(CFStringRef cfstr);
|
||||
|
||||
bool check_ca_st(struct ao *ao, int level, OSStatus code, const char *message);
|
||||
|
||||
@@ -62,7 +64,7 @@ bool ca_asbd_is_better(AudioStreamBasicDescription *req,
|
||||
AudioStreamBasicDescription *old,
|
||||
AudioStreamBasicDescription *new);
|
||||
|
||||
int64_t ca_frames_to_ns(struct ao *ao, uint32_t frames);
|
||||
int64_t ca_frames_to_us(struct ao *ao, uint32_t frames);
|
||||
int64_t ca_get_latency(const AudioTimeStamp *ts);
|
||||
|
||||
#if HAVE_COREAUDIO
|
||||
@@ -71,7 +73,7 @@ OSStatus ca_lock_device(AudioDeviceID device, pid_t *pid);
|
||||
OSStatus ca_unlock_device(AudioDeviceID device, pid_t *pid);
|
||||
OSStatus ca_disable_mixing(struct ao *ao, AudioDeviceID device, bool *changed);
|
||||
OSStatus ca_enable_mixing(struct ao *ao, AudioDeviceID device, bool changed);
|
||||
int64_t ca_get_device_latency_ns(struct ao *ao, AudioDeviceID device);
|
||||
int64_t ca_get_device_latency_us(struct ao *ao, AudioDeviceID device);
|
||||
bool ca_change_physical_format_sync(struct ao *ao, AudioStreamID stream,
|
||||
AudioStreamBasicDescription change_format);
|
||||
#endif
|
||||
|
||||
+8
-10
@@ -22,7 +22,6 @@
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdatomic.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -34,6 +33,7 @@
|
||||
#include "ao.h"
|
||||
#include "internal.h"
|
||||
#include "audio/format.h"
|
||||
#include "osdep/atomic.h"
|
||||
#include "osdep/timer.h"
|
||||
#include "options/m_config.h"
|
||||
#include "options/m_option.h"
|
||||
@@ -47,8 +47,8 @@
|
||||
struct jack_opts {
|
||||
char *port;
|
||||
char *client_name;
|
||||
bool connect;
|
||||
bool autostart;
|
||||
int connect;
|
||||
int autostart;
|
||||
int stdlayout;
|
||||
};
|
||||
|
||||
@@ -57,15 +57,15 @@ static const struct m_sub_options ao_jack_conf = {
|
||||
.opts = (const struct m_option[]){
|
||||
{"jack-port", OPT_STRING(port)},
|
||||
{"jack-name", OPT_STRING(client_name)},
|
||||
{"jack-autostart", OPT_BOOL(autostart)},
|
||||
{"jack-connect", OPT_BOOL(connect)},
|
||||
{"jack-autostart", OPT_FLAG(autostart)},
|
||||
{"jack-connect", OPT_FLAG(connect)},
|
||||
{"jack-std-channel-layout", OPT_CHOICE(stdlayout,
|
||||
{"waveext", 0}, {"any", 1})},
|
||||
{0}
|
||||
},
|
||||
.defaults = &(const struct jack_opts) {
|
||||
.client_name = "mpv",
|
||||
.connect = true,
|
||||
.connect = 1,
|
||||
},
|
||||
.size = sizeof(struct jack_opts),
|
||||
};
|
||||
@@ -122,8 +122,8 @@ static int process(jack_nframes_t nframes, void *arg)
|
||||
jack_nframes_t jack_latency =
|
||||
atomic_load(&p->graph_latency_max) + atomic_load(&p->buffer_size);
|
||||
|
||||
int64_t end_time = mp_time_ns();
|
||||
end_time += MP_TIME_S_TO_NS((jack_latency + nframes) / (double)ao->samplerate);
|
||||
int64_t end_time = mp_time_us();
|
||||
end_time += (jack_latency + nframes) / (double)ao->samplerate * 1000000.0;
|
||||
|
||||
ao_read_data(ao, buffers, nframes, end_time);
|
||||
|
||||
@@ -246,8 +246,6 @@ static int init(struct ao *ao)
|
||||
jack_set_process_callback(p->client, process, ao);
|
||||
|
||||
ao->samplerate = jack_get_sample_rate(p->client);
|
||||
// The actual device buffer can change, but this is enough for pre-buffer
|
||||
ao->device_buffer = jack_get_buffer_size(p->client);
|
||||
|
||||
jack_set_buffer_size_callback(p->client, buffer_size_cb, ao);
|
||||
jack_set_graph_order_callback(p->client, graph_order_cb, ao);
|
||||
|
||||
+18
-10
@@ -31,7 +31,6 @@
|
||||
#include "options/options.h"
|
||||
#include "common/common.h"
|
||||
#include "audio/aframe.h"
|
||||
#include "audio/chmap_avchannel.h"
|
||||
#include "audio/format.h"
|
||||
#include "audio/fmt-conversion.h"
|
||||
#include "filters/filter_internal.h"
|
||||
@@ -125,13 +124,8 @@ static int init(struct ao *ao)
|
||||
if (!ao_chmap_sel_adjust2(ao, &sel, &ao->channels, false))
|
||||
goto fail;
|
||||
mp_chmap_reorder_to_lavc(&ao->channels);
|
||||
|
||||
#if !HAVE_AV_CHANNEL_LAYOUT
|
||||
encoder->channels = ao->channels.num;
|
||||
encoder->channel_layout = mp_chmap_to_lavc(&ao->channels);
|
||||
#else
|
||||
mp_chmap_to_av_layout(&encoder->ch_layout, &ao->channels);
|
||||
#endif
|
||||
|
||||
encoder->sample_fmt = AV_SAMPLE_FMT_NONE;
|
||||
|
||||
@@ -173,7 +167,7 @@ static int init(struct ao *ao)
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
mp_mutex_unlock(&ao->encode_lavc_ctx->lock);
|
||||
pthread_mutex_unlock(&ao->encode_lavc_ctx->lock);
|
||||
ac->shutdown = true;
|
||||
return -1;
|
||||
}
|
||||
@@ -182,8 +176,18 @@ fail:
|
||||
static void uninit(struct ao *ao)
|
||||
{
|
||||
struct priv *ac = ao->priv;
|
||||
struct encode_lavc_context *ectx = ao->encode_lavc_ctx;
|
||||
|
||||
if (!ac->shutdown) {
|
||||
double outpts = ac->expected_next_pts;
|
||||
|
||||
pthread_mutex_lock(&ectx->lock);
|
||||
if (!ac->enc->options->rawts)
|
||||
outpts += ectx->discontinuity_pts_offset;
|
||||
pthread_mutex_unlock(&ectx->lock);
|
||||
|
||||
outpts += encoder_get_offset(ac->enc);
|
||||
|
||||
if (!write_frame(ao, MP_EOF_FRAME))
|
||||
MP_WARN(ao, "could not flush last frame\n");
|
||||
encoder_encode(ac->enc, NULL);
|
||||
@@ -200,7 +204,8 @@ static void encode(struct ao *ao, struct mp_aframe *af)
|
||||
double outpts = mp_aframe_get_pts(af);
|
||||
|
||||
AVFrame *frame = mp_aframe_to_avframe(af);
|
||||
MP_HANDLE_OOM(frame);
|
||||
if (!frame)
|
||||
abort();
|
||||
|
||||
frame->pts = rint(outpts * av_q2d(av_inv_q(encoder->time_base)));
|
||||
|
||||
@@ -261,7 +266,7 @@ static bool audio_write(struct ao *ao, void **data, int samples)
|
||||
double outpts = pts;
|
||||
|
||||
// for ectx PTS fields
|
||||
mp_mutex_lock(&ectx->lock);
|
||||
pthread_mutex_lock(&ectx->lock);
|
||||
|
||||
if (!ectx->options->rawts) {
|
||||
// Fix and apply the discontinuity pts offset.
|
||||
@@ -280,6 +285,9 @@ static bool audio_write(struct ao *ao, void **data, int samples)
|
||||
outpts = pts + ectx->discontinuity_pts_offset;
|
||||
}
|
||||
|
||||
// Shift pts by the pts offset first.
|
||||
outpts += encoder_get_offset(ac->enc);
|
||||
|
||||
// Calculate expected pts of next audio frame (input side).
|
||||
ac->expected_next_pts = pts + mp_aframe_get_size(af) / (double) ao->samplerate;
|
||||
|
||||
@@ -290,7 +298,7 @@ static bool audio_write(struct ao *ao, void **data, int samples)
|
||||
ectx->next_in_pts = nextpts;
|
||||
}
|
||||
|
||||
mp_mutex_unlock(&ectx->lock);
|
||||
pthread_mutex_unlock(&ectx->lock);
|
||||
|
||||
mp_aframe_set_pts(af, outpts);
|
||||
|
||||
|
||||
+7
-6
@@ -28,6 +28,7 @@
|
||||
|
||||
#include "mpv_talloc.h"
|
||||
|
||||
#include "config.h"
|
||||
#include "osdep/timer.h"
|
||||
#include "options/m_option.h"
|
||||
#include "common/common.h"
|
||||
@@ -43,13 +44,13 @@ struct priv {
|
||||
int buffersize; // samples
|
||||
bool playing;
|
||||
|
||||
bool untimed;
|
||||
int untimed;
|
||||
float bufferlen; // seconds
|
||||
float speed; // multiplier
|
||||
float latency_sec; // seconds
|
||||
float latency; // samples
|
||||
bool broken_eof;
|
||||
bool broken_delay;
|
||||
int broken_eof;
|
||||
int broken_delay;
|
||||
|
||||
// Minimal unit of audio samples that can be written at once. If play() is
|
||||
// called with sizes not aligned to this, a rounded size will be returned.
|
||||
@@ -215,13 +216,13 @@ const struct ao_driver audio_out_null = {
|
||||
.speed = 1,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
{"untimed", OPT_BOOL(untimed)},
|
||||
{"untimed", OPT_FLAG(untimed)},
|
||||
{"buffer", OPT_FLOAT(bufferlen), M_RANGE(0, 100)},
|
||||
{"outburst", OPT_INT(outburst), M_RANGE(1, 100000)},
|
||||
{"speed", OPT_FLOAT(speed), M_RANGE(0, 10000)},
|
||||
{"latency", OPT_FLOAT(latency_sec), M_RANGE(0, 100)},
|
||||
{"broken-eof", OPT_BOOL(broken_eof)},
|
||||
{"broken-delay", OPT_BOOL(broken_delay)},
|
||||
{"broken-eof", OPT_FLAG(broken_eof)},
|
||||
{"broken-delay", OPT_FLAG(broken_delay)},
|
||||
{"channel-layouts", OPT_CHANNELS(channel_layouts)},
|
||||
{"format", OPT_AUDIOFORMAT(format)},
|
||||
{0}
|
||||
|
||||
+10
-14
@@ -19,6 +19,8 @@
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <inttypes.h>
|
||||
@@ -56,7 +58,7 @@ struct priv {
|
||||
ALenum al_format;
|
||||
int num_buffers;
|
||||
int num_samples;
|
||||
bool direct_channels;
|
||||
int direct_channels;
|
||||
};
|
||||
|
||||
static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
@@ -65,13 +67,13 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
case AOCONTROL_GET_VOLUME:
|
||||
case AOCONTROL_SET_VOLUME: {
|
||||
ALfloat volume;
|
||||
float *vol = arg;
|
||||
ao_control_vol_t *vol = (ao_control_vol_t *)arg;
|
||||
if (cmd == AOCONTROL_SET_VOLUME) {
|
||||
volume = *vol / 100.0;
|
||||
volume = (vol->left + vol->right) / 200.0;
|
||||
alListenerf(AL_GAIN, volume);
|
||||
}
|
||||
alGetListenerf(AL_GAIN, &volume);
|
||||
*vol = volume * 100;
|
||||
vol->left = vol->right = volume * 100;
|
||||
return CONTROL_TRUE;
|
||||
}
|
||||
case AOCONTROL_GET_MUTE:
|
||||
@@ -197,14 +199,8 @@ static int init(struct ao *ao)
|
||||
alListenerfv(AL_ORIENTATION, direction);
|
||||
|
||||
alGenSources(1, &source);
|
||||
if (p->direct_channels) {
|
||||
if (alIsExtensionPresent("AL_SOFT_direct_channels_remix")) {
|
||||
alSourcei(source,
|
||||
alGetEnumValue((ALchar*)"AL_DIRECT_CHANNELS_SOFT"),
|
||||
alcGetEnumValue(dev, "AL_REMIX_UNMATCHED_SOFT"));
|
||||
} else {
|
||||
MP_WARN(ao, "Direct channels aren't supported by this version of OpenAL\n");
|
||||
}
|
||||
if (p->direct_channels && alGetEnumValue((ALchar*)"AL_DIRECT_CHANNELS_SOFT")) {
|
||||
alSourcei(source, alGetEnumValue((ALchar*)"AL_DIRECT_CHANNELS_SOFT"), AL_TRUE);
|
||||
}
|
||||
|
||||
cur_buf = 0;
|
||||
@@ -389,12 +385,12 @@ const struct ao_driver audio_out_openal = {
|
||||
.priv_defaults = &(const struct priv) {
|
||||
.num_buffers = 4,
|
||||
.num_samples = 8192,
|
||||
.direct_channels = true,
|
||||
.direct_channels = 0,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
{"num-buffers", OPT_INT(num_buffers), M_RANGE(2, MAX_BUF)},
|
||||
{"num-samples", OPT_INT(num_samples), M_RANGE(256, MAX_SAMPLES)},
|
||||
{"direct-channels", OPT_BOOL(direct_channels)},
|
||||
{"direct-channels", OPT_FLAG(direct_channels)},
|
||||
{0}
|
||||
},
|
||||
.options_prefix = "openal",
|
||||
|
||||
@@ -23,12 +23,13 @@
|
||||
#include "common/msg.h"
|
||||
#include "audio/format.h"
|
||||
#include "options/m_option.h"
|
||||
#include "osdep/threads.h"
|
||||
#include "osdep/timer.h"
|
||||
|
||||
#include <SLES/OpenSLES.h>
|
||||
#include <SLES/OpenSLES_Android.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
struct priv {
|
||||
SLObjectItf sl, output_mix, player;
|
||||
SLBufferQueueItf buffer_queue;
|
||||
@@ -36,7 +37,7 @@ struct priv {
|
||||
SLPlayItf play;
|
||||
void *buf;
|
||||
int bytes_per_enqueue;
|
||||
mp_mutex buffer_lock;
|
||||
pthread_mutex_t buffer_lock;
|
||||
double audio_latency;
|
||||
|
||||
int frames_per_enqueue;
|
||||
@@ -61,7 +62,7 @@ static void uninit(struct ao *ao)
|
||||
p->engine = NULL;
|
||||
p->play = NULL;
|
||||
|
||||
mp_mutex_destroy(&p->buffer_lock);
|
||||
pthread_mutex_destroy(&p->buffer_lock);
|
||||
|
||||
free(p->buf);
|
||||
p->buf = NULL;
|
||||
@@ -76,18 +77,18 @@ static void buffer_callback(SLBufferQueueItf buffer_queue, void *context)
|
||||
SLresult res;
|
||||
double delay;
|
||||
|
||||
mp_mutex_lock(&p->buffer_lock);
|
||||
pthread_mutex_lock(&p->buffer_lock);
|
||||
|
||||
delay = p->frames_per_enqueue / (double)ao->samplerate;
|
||||
delay += p->audio_latency;
|
||||
ao_read_data(ao, &p->buf, p->frames_per_enqueue,
|
||||
mp_time_ns() + MP_TIME_S_TO_NS(delay));
|
||||
mp_time_us() + 1000000LL * delay);
|
||||
|
||||
res = (*buffer_queue)->Enqueue(buffer_queue, p->buf, p->bytes_per_enqueue);
|
||||
if (res != SL_RESULT_SUCCESS)
|
||||
MP_ERR(ao, "Failed to Enqueue: %d\n", res);
|
||||
|
||||
mp_mutex_unlock(&p->buffer_lock);
|
||||
pthread_mutex_unlock(&p->buffer_lock);
|
||||
}
|
||||
|
||||
#define CHK(stmt) \
|
||||
@@ -110,8 +111,6 @@ static int init(struct ao *ao)
|
||||
|
||||
// This AO only supports two channels at the moment
|
||||
mp_chmap_from_channels(&ao->channels, 2);
|
||||
// Upstream "Wilhelm" supports only 8000 <= rate <= 192000
|
||||
ao->samplerate = MPCLAMP(ao->samplerate, 8000, 192000);
|
||||
|
||||
CHK(slCreateEngine(&p->sl, 0, NULL, 0, NULL, NULL));
|
||||
CHK((*p->sl)->Realize(p->sl, SL_BOOLEAN_FALSE));
|
||||
@@ -169,7 +168,7 @@ static int init(struct ao *ao)
|
||||
goto error;
|
||||
}
|
||||
|
||||
int r = mp_mutex_init(&p->buffer_lock);
|
||||
int r = pthread_mutex_init(&p->buffer_lock, NULL);
|
||||
if (r) {
|
||||
MP_ERR(ao, "Failed to initialize the mutex: %d\n", r);
|
||||
goto error;
|
||||
|
||||
+22
-11
@@ -4,7 +4,7 @@
|
||||
* Original author: A'rpi
|
||||
* Support for >2 output channels added 2001-11-25
|
||||
* - Steve Davies <steve@daviesfam.org>
|
||||
* Rozhuk Ivan <rozhuk.im@gmail.com> 2020-2023
|
||||
* Rozhuk Ivan <rozhuk.im@gmail.com> 2020
|
||||
*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
@@ -35,6 +35,7 @@
|
||||
#endif
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "audio/format.h"
|
||||
#include "common/msg.h"
|
||||
#include "options/options.h"
|
||||
@@ -52,6 +53,7 @@
|
||||
|
||||
struct priv {
|
||||
int dsp_fd;
|
||||
bool playing;
|
||||
double bps; /* Bytes per second. */
|
||||
};
|
||||
|
||||
@@ -114,7 +116,7 @@ static void device_descr_get(size_t dev_idx, char *buf, size_t buf_size)
|
||||
snprintf(dev_path, sizeof(dev_path), PATH_DEV_MIXER"%zu", dev_idx);
|
||||
int fd = open(dev_path, O_RDONLY);
|
||||
if (ioctl(fd, SOUND_MIXER_INFO, &mi) == 0) {
|
||||
strncpy(buf, mi.name, buf_size - 1);
|
||||
strncpy(buf, mi.name, buf_size);
|
||||
tmp = (buf_size - 1);
|
||||
}
|
||||
close(fd);
|
||||
@@ -186,11 +188,11 @@ static int init(struct ao *ao)
|
||||
|
||||
/* Channels count. */
|
||||
if (af_fmt_is_spdif(format)) {
|
||||
nchannels = reqchannels = channels.num;
|
||||
if (ioctl(p->dsp_fd, SNDCTL_DSP_CHANNELS, &nchannels) == -1) {
|
||||
MP_ERR(ao, "Failed to set audio device to %d channels.\n",
|
||||
reqchannels);
|
||||
goto err_out_ioctl;
|
||||
/* Probably could be fixed by setting number of channels;
|
||||
* needs testing. */
|
||||
if (channels.num != 2) {
|
||||
MP_ERR(ao, "Format %s not implemented.\n", af_fmt_to_str(format));
|
||||
goto err_out;
|
||||
}
|
||||
} else {
|
||||
struct mp_chmap_sel sel = {0};
|
||||
@@ -239,6 +241,7 @@ static int init(struct ao *ao)
|
||||
ao->samplerate = samplerate;
|
||||
ao->channels = channels;
|
||||
p->bps = (channels.num * samplerate * af_fmt_to_bytes(format));
|
||||
p->playing = false;
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -258,12 +261,13 @@ static void uninit(struct ao *ao)
|
||||
ioctl(p->dsp_fd, SNDCTL_DSP_HALT, NULL);
|
||||
close(p->dsp_fd);
|
||||
p->dsp_fd = -1;
|
||||
p->playing = false;
|
||||
}
|
||||
|
||||
static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
float *vol = arg;
|
||||
ao_control_vol_t *vol = (ao_control_vol_t *)arg;
|
||||
int v;
|
||||
|
||||
if (p->dsp_fd < 0)
|
||||
@@ -275,10 +279,11 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
MP_WARN_IOCTL_ERR(ao);
|
||||
return CONTROL_ERROR;
|
||||
}
|
||||
*vol = ((v & 0x00ff) + ((v & 0xff00) >> 8)) / 2.0;
|
||||
vol->right = ((v & 0xff00) >> 8);
|
||||
vol->left = (v & 0x00ff);
|
||||
return CONTROL_OK;
|
||||
case AOCONTROL_SET_VOLUME:
|
||||
v = ((int)*vol << 8) | (int)*vol;
|
||||
v = ((int)vol->right << 8) | (int)vol->left;
|
||||
if (ioctl(p->dsp_fd, SNDCTL_DSP_SETPLAYVOL, &v) == -1) {
|
||||
MP_WARN_IOCTL_ERR(ao);
|
||||
return CONTROL_ERROR;
|
||||
@@ -295,6 +300,7 @@ static void reset(struct ao *ao)
|
||||
int trig = 0;
|
||||
|
||||
/* Clear buf and do not start playback after data written. */
|
||||
p->playing = false;
|
||||
if (ioctl(p->dsp_fd, SNDCTL_DSP_HALT, NULL) == -1 ||
|
||||
ioctl(p->dsp_fd, SNDCTL_DSP_SETTRIGGER, &trig) == -1)
|
||||
{
|
||||
@@ -314,6 +320,7 @@ static void start(struct ao *ao)
|
||||
MP_WARN_IOCTL_ERR(ao);
|
||||
return;
|
||||
}
|
||||
p->playing = true;
|
||||
}
|
||||
|
||||
static bool audio_write(struct ao *ao, void **data, int samples)
|
||||
@@ -330,11 +337,13 @@ static bool audio_write(struct ao *ao, void **data, int samples)
|
||||
continue;
|
||||
MP_WARN(ao, "audio_write: write() fail, err = %i: %s.\n",
|
||||
errno, strerror(errno));
|
||||
p->playing = false;
|
||||
return false;
|
||||
}
|
||||
if ((size_t)rc != size) {
|
||||
MP_WARN(ao, "audio_write: unexpected partial write: required: %zu, written: %zu.\n",
|
||||
size, (size_t)rc);
|
||||
p->playing = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -351,6 +360,7 @@ static void get_state(struct ao *ao, struct mp_pcm_state *state)
|
||||
ioctl(p->dsp_fd, SNDCTL_DSP_GETODELAY, &odelay) == -1)
|
||||
{
|
||||
MP_WARN_IOCTL_ERR(ao);
|
||||
p->playing = false;
|
||||
memset(state, 0x00, sizeof(struct mp_pcm_state));
|
||||
state->delay = 0.0;
|
||||
return;
|
||||
@@ -358,7 +368,7 @@ static void get_state(struct ao *ao, struct mp_pcm_state *state)
|
||||
state->free_samples = (info.bytes / ao->sstride);
|
||||
state->queued_samples = (ao->device_buffer - state->free_samples);
|
||||
state->delay = (odelay / p->bps);
|
||||
state->playing = (state->queued_samples != 0);
|
||||
state->playing = p->playing;
|
||||
}
|
||||
|
||||
static void list_devs(struct ao *ao, struct ao_device_list *list)
|
||||
@@ -396,5 +406,6 @@ const struct ao_driver audio_out_oss = {
|
||||
.priv_size = sizeof(struct priv),
|
||||
.priv_defaults = &(const struct priv) {
|
||||
.dsp_fd = -1,
|
||||
.playing = false,
|
||||
},
|
||||
};
|
||||
+7
-5
@@ -19,6 +19,8 @@
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -42,8 +44,8 @@
|
||||
|
||||
struct priv {
|
||||
char *outputfilename;
|
||||
bool waveheader;
|
||||
bool append;
|
||||
int waveheader;
|
||||
int append;
|
||||
uint64_t data_length;
|
||||
FILE *fp;
|
||||
};
|
||||
@@ -237,11 +239,11 @@ const struct ao_driver audio_out_pcm = {
|
||||
.start = start,
|
||||
.reset = reset,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.priv_defaults = &(const struct priv) { .waveheader = true },
|
||||
.priv_defaults = &(const struct priv) { .waveheader = 1 },
|
||||
.options = (const struct m_option[]) {
|
||||
{"file", OPT_STRING(outputfilename), .flags = M_OPT_FILE},
|
||||
{"waveheader", OPT_BOOL(waveheader)},
|
||||
{"append", OPT_BOOL(append)},
|
||||
{"waveheader", OPT_FLAG(waveheader)},
|
||||
{"append", OPT_FLAG(append)},
|
||||
{0}
|
||||
},
|
||||
.options_prefix = "ao-pcm",
|
||||
|
||||
@@ -1,883 +0,0 @@
|
||||
/*
|
||||
* PipeWire audio output driver.
|
||||
* Copyright (C) 2021 Thomas Weißschuh <thomas@t-8ch.de>
|
||||
* Copyright (C) 2021 Oschowa <oschowa@web.de>
|
||||
* Copyright (C) 2020 Andreas Kempf <aakempf@gmail.com>
|
||||
*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <pipewire/pipewire.h>
|
||||
#include <pipewire/global.h>
|
||||
#include <spa/param/audio/format-utils.h>
|
||||
#include <spa/param/props.h>
|
||||
#include <spa/utils/result.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "common/msg.h"
|
||||
#include "options/m_config.h"
|
||||
#include "options/m_option.h"
|
||||
#include "ao.h"
|
||||
#include "audio/format.h"
|
||||
#include "internal.h"
|
||||
#include "osdep/timer.h"
|
||||
|
||||
#if !PW_CHECK_VERSION(0, 3, 50)
|
||||
static inline int pw_stream_get_time_n(struct pw_stream *stream, struct pw_time *time, size_t size) {
|
||||
return pw_stream_get_time(stream, time);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !PW_CHECK_VERSION(0, 3, 57)
|
||||
// Earlier versions segfault on zeroed hooks
|
||||
#define spa_hook_remove(hook) if ((hook)->link.prev) spa_hook_remove(hook)
|
||||
#endif
|
||||
|
||||
enum init_state {
|
||||
INIT_STATE_NONE,
|
||||
INIT_STATE_SUCCESS,
|
||||
INIT_STATE_ERROR,
|
||||
};
|
||||
|
||||
enum {
|
||||
VOLUME_MODE_CHANNEL,
|
||||
VOLUME_MODE_GLOBAL,
|
||||
};
|
||||
|
||||
struct priv {
|
||||
struct pw_thread_loop *loop;
|
||||
struct pw_stream *stream;
|
||||
struct pw_core *core;
|
||||
struct spa_hook stream_listener;
|
||||
struct spa_hook core_listener;
|
||||
enum init_state init_state;
|
||||
|
||||
bool muted;
|
||||
float volume;
|
||||
|
||||
struct {
|
||||
int buffer_msec;
|
||||
char *remote;
|
||||
int volume_mode;
|
||||
} options;
|
||||
|
||||
struct {
|
||||
struct pw_registry *registry;
|
||||
struct spa_hook registry_listener;
|
||||
struct spa_list sinks;
|
||||
} hotplug;
|
||||
};
|
||||
|
||||
struct id_list {
|
||||
uint32_t id;
|
||||
struct spa_list node;
|
||||
};
|
||||
|
||||
static enum spa_audio_format af_fmt_to_pw(struct ao *ao, enum af_format format)
|
||||
{
|
||||
switch (format) {
|
||||
case AF_FORMAT_U8: return SPA_AUDIO_FORMAT_U8;
|
||||
case AF_FORMAT_S16: return SPA_AUDIO_FORMAT_S16;
|
||||
case AF_FORMAT_S32: return SPA_AUDIO_FORMAT_S32;
|
||||
case AF_FORMAT_FLOAT: return SPA_AUDIO_FORMAT_F32;
|
||||
case AF_FORMAT_DOUBLE: return SPA_AUDIO_FORMAT_F64;
|
||||
case AF_FORMAT_U8P: return SPA_AUDIO_FORMAT_U8P;
|
||||
case AF_FORMAT_S16P: return SPA_AUDIO_FORMAT_S16P;
|
||||
case AF_FORMAT_S32P: return SPA_AUDIO_FORMAT_S32P;
|
||||
case AF_FORMAT_FLOATP: return SPA_AUDIO_FORMAT_F32P;
|
||||
case AF_FORMAT_DOUBLEP: return SPA_AUDIO_FORMAT_F64P;
|
||||
default:
|
||||
MP_WARN(ao, "Unhandled format %d\n", format);
|
||||
return SPA_AUDIO_FORMAT_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
static enum spa_audio_channel mp_speaker_id_to_spa(struct ao *ao, enum mp_speaker_id mp_speaker_id)
|
||||
{
|
||||
switch (mp_speaker_id) {
|
||||
case MP_SPEAKER_ID_FL: return SPA_AUDIO_CHANNEL_FL;
|
||||
case MP_SPEAKER_ID_FR: return SPA_AUDIO_CHANNEL_FR;
|
||||
case MP_SPEAKER_ID_FC: return SPA_AUDIO_CHANNEL_FC;
|
||||
case MP_SPEAKER_ID_LFE: return SPA_AUDIO_CHANNEL_LFE;
|
||||
case MP_SPEAKER_ID_BL: return SPA_AUDIO_CHANNEL_RL;
|
||||
case MP_SPEAKER_ID_BR: return SPA_AUDIO_CHANNEL_RR;
|
||||
case MP_SPEAKER_ID_FLC: return SPA_AUDIO_CHANNEL_FLC;
|
||||
case MP_SPEAKER_ID_FRC: return SPA_AUDIO_CHANNEL_FRC;
|
||||
case MP_SPEAKER_ID_BC: return SPA_AUDIO_CHANNEL_RC;
|
||||
case MP_SPEAKER_ID_SL: return SPA_AUDIO_CHANNEL_SL;
|
||||
case MP_SPEAKER_ID_SR: return SPA_AUDIO_CHANNEL_SR;
|
||||
case MP_SPEAKER_ID_TC: return SPA_AUDIO_CHANNEL_TC;
|
||||
case MP_SPEAKER_ID_TFL: return SPA_AUDIO_CHANNEL_TFL;
|
||||
case MP_SPEAKER_ID_TFC: return SPA_AUDIO_CHANNEL_TFC;
|
||||
case MP_SPEAKER_ID_TFR: return SPA_AUDIO_CHANNEL_TFR;
|
||||
case MP_SPEAKER_ID_TBL: return SPA_AUDIO_CHANNEL_TRL;
|
||||
case MP_SPEAKER_ID_TBC: return SPA_AUDIO_CHANNEL_TRC;
|
||||
case MP_SPEAKER_ID_TBR: return SPA_AUDIO_CHANNEL_TRR;
|
||||
case MP_SPEAKER_ID_DL: return SPA_AUDIO_CHANNEL_FL;
|
||||
case MP_SPEAKER_ID_DR: return SPA_AUDIO_CHANNEL_FR;
|
||||
case MP_SPEAKER_ID_WL: return SPA_AUDIO_CHANNEL_FL;
|
||||
case MP_SPEAKER_ID_WR: return SPA_AUDIO_CHANNEL_FR;
|
||||
case MP_SPEAKER_ID_SDL: return SPA_AUDIO_CHANNEL_SL;
|
||||
case MP_SPEAKER_ID_SDR: return SPA_AUDIO_CHANNEL_SL;
|
||||
case MP_SPEAKER_ID_LFE2: return SPA_AUDIO_CHANNEL_LFE2;
|
||||
case MP_SPEAKER_ID_TSL: return SPA_AUDIO_CHANNEL_TSL;
|
||||
case MP_SPEAKER_ID_TSR: return SPA_AUDIO_CHANNEL_TSR;
|
||||
case MP_SPEAKER_ID_BFC: return SPA_AUDIO_CHANNEL_BC;
|
||||
case MP_SPEAKER_ID_BFL: return SPA_AUDIO_CHANNEL_BLC;
|
||||
case MP_SPEAKER_ID_BFR: return SPA_AUDIO_CHANNEL_BRC;
|
||||
case MP_SPEAKER_ID_NA: return SPA_AUDIO_CHANNEL_NA;
|
||||
default:
|
||||
MP_WARN(ao, "Unhandled channel %d\n", mp_speaker_id);
|
||||
return SPA_AUDIO_CHANNEL_UNKNOWN;
|
||||
};
|
||||
}
|
||||
|
||||
static void on_process(void *userdata)
|
||||
{
|
||||
struct ao *ao = userdata;
|
||||
struct priv *p = ao->priv;
|
||||
struct pw_time time;
|
||||
struct pw_buffer *b;
|
||||
void *data[MP_NUM_CHANNELS];
|
||||
|
||||
if ((b = pw_stream_dequeue_buffer(p->stream)) == NULL) {
|
||||
MP_WARN(ao, "out of buffers: %s\n", strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
struct spa_buffer *buf = b->buffer;
|
||||
|
||||
int bytes_per_channel = buf->datas[0].maxsize / ao->channels.num;
|
||||
int nframes = bytes_per_channel / ao->sstride;
|
||||
#if PW_CHECK_VERSION(0, 3, 49)
|
||||
if (b->requested != 0)
|
||||
nframes = MPMIN(b->requested, nframes);
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < buf->n_datas; i++)
|
||||
data[i] = buf->datas[i].data;
|
||||
|
||||
pw_stream_get_time_n(p->stream, &time, sizeof(time));
|
||||
if (time.rate.denom == 0)
|
||||
time.rate.denom = ao->samplerate;
|
||||
if (time.rate.num == 0)
|
||||
time.rate.num = 1;
|
||||
|
||||
int64_t end_time = mp_time_ns();
|
||||
/* time.queued is always going to be 0, so we don't need to care */
|
||||
end_time += (nframes * 1e9 / ao->samplerate) +
|
||||
((double) time.delay * SPA_NSEC_PER_SEC * time.rate.num / time.rate.denom);
|
||||
|
||||
int samples = ao_read_data_nonblocking(ao, data, nframes, end_time);
|
||||
b->size = samples;
|
||||
|
||||
for (int i = 0; i < buf->n_datas; i++) {
|
||||
buf->datas[i].chunk->size = samples * ao->sstride;
|
||||
buf->datas[i].chunk->offset = 0;
|
||||
buf->datas[i].chunk->stride = ao->sstride;
|
||||
}
|
||||
|
||||
pw_stream_queue_buffer(p->stream, b);
|
||||
|
||||
MP_TRACE(ao, "queued %d of %d samples\n", samples, nframes);
|
||||
}
|
||||
|
||||
static void on_param_changed(void *userdata, uint32_t id, const struct spa_pod *param)
|
||||
{
|
||||
struct ao *ao = userdata;
|
||||
struct priv *p = ao->priv;
|
||||
const struct spa_pod *params[1];
|
||||
uint8_t buffer[1024];
|
||||
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
|
||||
|
||||
/* We want to know when our node is linked.
|
||||
* As there is no proper callback for this we use the Latency param for this
|
||||
*/
|
||||
if (id == SPA_PARAM_Latency) {
|
||||
p->init_state = INIT_STATE_SUCCESS;
|
||||
pw_thread_loop_signal(p->loop, false);
|
||||
}
|
||||
|
||||
if (param == NULL || id != SPA_PARAM_Format)
|
||||
return;
|
||||
|
||||
int buffer_size = ao->device_buffer * af_fmt_to_bytes(ao->format) * ao->channels.num;
|
||||
|
||||
params[0] = spa_pod_builder_add_object(&b,
|
||||
SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
|
||||
SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(ao->num_planes),
|
||||
SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int(
|
||||
buffer_size, 0, INT32_MAX),
|
||||
SPA_PARAM_BUFFERS_stride, SPA_POD_Int(ao->sstride));
|
||||
if (!params[0]) {
|
||||
MP_ERR(ao, "Could not build parameter pod\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (pw_stream_update_params(p->stream, params, 1) < 0) {
|
||||
MP_ERR(ao, "Could not update stream parameters\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void on_state_changed(void *userdata, enum pw_stream_state old, enum pw_stream_state state, const char *error)
|
||||
{
|
||||
struct ao *ao = userdata;
|
||||
struct priv *p = ao->priv;
|
||||
MP_DBG(ao, "Stream state changed: old_state=%s state=%s error=%s\n",
|
||||
pw_stream_state_as_string(old), pw_stream_state_as_string(state), error);
|
||||
|
||||
if (state == PW_STREAM_STATE_ERROR) {
|
||||
MP_WARN(ao, "Stream in error state, trying to reload...\n");
|
||||
p->init_state = INIT_STATE_ERROR;
|
||||
pw_thread_loop_signal(p->loop, false);
|
||||
ao_request_reload(ao);
|
||||
}
|
||||
|
||||
if (state == PW_STREAM_STATE_UNCONNECTED && old != PW_STREAM_STATE_UNCONNECTED) {
|
||||
MP_WARN(ao, "Stream disconnected, trying to reload...\n");
|
||||
ao_request_reload(ao);
|
||||
}
|
||||
}
|
||||
|
||||
static float spa_volume_to_mp_volume(float vol)
|
||||
{
|
||||
return vol * 100;
|
||||
}
|
||||
|
||||
static float mp_volume_to_spa_volume(float vol)
|
||||
{
|
||||
return vol / 100;
|
||||
}
|
||||
|
||||
static float volume_avg(float* vols, uint32_t n)
|
||||
{
|
||||
float sum = 0.0;
|
||||
for (int i = 0; i < n; i++)
|
||||
sum += vols[i];
|
||||
return sum / n;
|
||||
}
|
||||
|
||||
static void on_control_info(void *userdata, uint32_t id,
|
||||
const struct pw_stream_control *control)
|
||||
{
|
||||
struct ao *ao = userdata;
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
switch (id) {
|
||||
case SPA_PROP_mute:
|
||||
if (control->n_values == 1)
|
||||
p->muted = control->values[0] >= 0.5;
|
||||
break;
|
||||
case SPA_PROP_channelVolumes:
|
||||
if (p->options.volume_mode != VOLUME_MODE_CHANNEL)
|
||||
break;
|
||||
if (control->n_values > 0)
|
||||
p->volume = volume_avg(control->values, control->n_values);
|
||||
break;
|
||||
case SPA_PROP_volume:
|
||||
if (p->options.volume_mode != VOLUME_MODE_GLOBAL)
|
||||
break;
|
||||
if (control->n_values > 0)
|
||||
p->volume = control->values[0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct pw_stream_events stream_events = {
|
||||
.version = PW_VERSION_STREAM_EVENTS,
|
||||
.param_changed = on_param_changed,
|
||||
.process = on_process,
|
||||
.state_changed = on_state_changed,
|
||||
.control_info = on_control_info,
|
||||
};
|
||||
|
||||
static void uninit(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
if (p->loop)
|
||||
pw_thread_loop_stop(p->loop);
|
||||
spa_hook_remove(&p->stream_listener);
|
||||
spa_zero(p->stream_listener);
|
||||
if (p->stream)
|
||||
pw_stream_destroy(p->stream);
|
||||
p->stream = NULL;
|
||||
if (p->core)
|
||||
pw_context_destroy(pw_core_get_context(p->core));
|
||||
p->core = NULL;
|
||||
if (p->loop)
|
||||
pw_thread_loop_destroy(p->loop);
|
||||
p->loop = NULL;
|
||||
pw_deinit();
|
||||
}
|
||||
|
||||
struct registry_event_global_ctx {
|
||||
struct ao *ao;
|
||||
void (*sink_cb) (struct ao *ao, uint32_t id, const struct spa_dict *props, void *sink_cb_ctx);
|
||||
void *sink_cb_ctx;
|
||||
};
|
||||
|
||||
static bool is_sink_node(const char *type, const struct spa_dict *props)
|
||||
{
|
||||
if (strcmp(type, PW_TYPE_INTERFACE_Node) != 0)
|
||||
return false;
|
||||
|
||||
if (!props)
|
||||
return false;
|
||||
|
||||
const char *class = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS);
|
||||
if (!class || strcmp(class, "Audio/Sink") != 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void for_each_sink_registry_event_global(void *data, uint32_t id,
|
||||
uint32_t permissions, const
|
||||
char *type, uint32_t version,
|
||||
const struct spa_dict *props)
|
||||
{
|
||||
struct registry_event_global_ctx *ctx = data;
|
||||
|
||||
if (!is_sink_node(type, props))
|
||||
return;
|
||||
|
||||
ctx->sink_cb(ctx->ao, id, props, ctx->sink_cb_ctx);
|
||||
}
|
||||
|
||||
|
||||
struct for_each_done_ctx {
|
||||
struct pw_thread_loop *loop;
|
||||
bool done;
|
||||
};
|
||||
|
||||
static const struct pw_registry_events for_each_sink_registry_events = {
|
||||
.version = PW_VERSION_REGISTRY_EVENTS,
|
||||
.global = for_each_sink_registry_event_global,
|
||||
};
|
||||
|
||||
static void for_each_sink_done(void *data, uint32_t it, int seq)
|
||||
{
|
||||
struct for_each_done_ctx *ctx = data;
|
||||
ctx->done = true;
|
||||
pw_thread_loop_signal(ctx->loop, false);
|
||||
}
|
||||
|
||||
static const struct pw_core_events for_each_sink_core_events = {
|
||||
.version = PW_VERSION_CORE_EVENTS,
|
||||
.done = for_each_sink_done,
|
||||
};
|
||||
|
||||
static int for_each_sink(struct ao *ao, void (cb) (struct ao *ao, uint32_t id,
|
||||
const struct spa_dict *props, void *ctx), void *cb_ctx)
|
||||
{
|
||||
struct priv *priv = ao->priv;
|
||||
struct pw_registry *registry;
|
||||
struct spa_hook core_listener;
|
||||
struct for_each_done_ctx done_ctx = {
|
||||
.loop = priv->loop,
|
||||
.done = false,
|
||||
};
|
||||
int ret = -1;
|
||||
|
||||
pw_thread_loop_lock(priv->loop);
|
||||
|
||||
spa_zero(core_listener);
|
||||
if (pw_core_add_listener(priv->core, &core_listener, &for_each_sink_core_events, &done_ctx) < 0)
|
||||
goto unlock_loop;
|
||||
|
||||
registry = pw_core_get_registry(priv->core, PW_VERSION_REGISTRY, 0);
|
||||
if (!registry)
|
||||
goto remove_core_listener;
|
||||
|
||||
pw_core_sync(priv->core, 0, 0);
|
||||
|
||||
struct spa_hook registry_listener;
|
||||
struct registry_event_global_ctx revents_ctx = {
|
||||
.ao = ao,
|
||||
.sink_cb = cb,
|
||||
.sink_cb_ctx = cb_ctx,
|
||||
};
|
||||
spa_zero(registry_listener);
|
||||
if (pw_registry_add_listener(registry, ®istry_listener, &for_each_sink_registry_events, &revents_ctx) < 0)
|
||||
goto destroy_registry;
|
||||
|
||||
while (!done_ctx.done)
|
||||
pw_thread_loop_wait(priv->loop);
|
||||
|
||||
spa_hook_remove(®istry_listener);
|
||||
|
||||
ret = 0;
|
||||
|
||||
destroy_registry:
|
||||
pw_proxy_destroy((struct pw_proxy *)registry);
|
||||
|
||||
remove_core_listener:
|
||||
spa_hook_remove(&core_listener);
|
||||
|
||||
unlock_loop:
|
||||
pw_thread_loop_unlock(priv->loop);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void have_sink(struct ao *ao, uint32_t id, const struct spa_dict *props, void *ctx)
|
||||
{
|
||||
bool *b = ctx;
|
||||
*b = true;
|
||||
}
|
||||
|
||||
static bool session_has_sinks(struct ao *ao)
|
||||
{
|
||||
bool b = false;
|
||||
|
||||
if (for_each_sink(ao, have_sink, &b) < 0)
|
||||
MP_WARN(ao, "Could not list devices, sink detection may be wrong\n");
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
static void on_error(void *data, uint32_t id, int seq, int res, const char *message)
|
||||
{
|
||||
struct ao *ao = data;
|
||||
|
||||
MP_WARN(ao, "Error during playback: %s, %s\n", spa_strerror(res), message);
|
||||
}
|
||||
|
||||
static void on_core_info(void *data, const struct pw_core_info *info)
|
||||
{
|
||||
struct ao *ao = data;
|
||||
|
||||
MP_VERBOSE(ao, "Core user: %s\n", info->user_name);
|
||||
MP_VERBOSE(ao, "Core host: %s\n", info->host_name);
|
||||
MP_VERBOSE(ao, "Core version: %s\n", info->version);
|
||||
MP_VERBOSE(ao, "Core name: %s\n", info->name);
|
||||
}
|
||||
|
||||
static const struct pw_core_events core_events = {
|
||||
.version = PW_VERSION_CORE_EVENTS,
|
||||
.error = on_error,
|
||||
.info = on_core_info,
|
||||
};
|
||||
|
||||
static int pipewire_init_boilerplate(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
struct pw_context *context;
|
||||
|
||||
pw_init(NULL, NULL);
|
||||
|
||||
MP_VERBOSE(ao, "Headers version: %s\n", pw_get_headers_version());
|
||||
MP_VERBOSE(ao, "Library version: %s\n", pw_get_library_version());
|
||||
|
||||
p->loop = pw_thread_loop_new("mpv/ao/pipewire", NULL);
|
||||
if (p->loop == NULL)
|
||||
return -1;
|
||||
|
||||
pw_thread_loop_lock(p->loop);
|
||||
|
||||
if (pw_thread_loop_start(p->loop) < 0)
|
||||
goto error;
|
||||
|
||||
context = pw_context_new(
|
||||
pw_thread_loop_get_loop(p->loop),
|
||||
pw_properties_new(PW_KEY_CONFIG_NAME, "client-rt.conf", NULL),
|
||||
0);
|
||||
if (!context)
|
||||
goto error;
|
||||
|
||||
p->core = pw_context_connect(
|
||||
context,
|
||||
pw_properties_new(PW_KEY_REMOTE_NAME, p->options.remote, NULL),
|
||||
0);
|
||||
if (!p->core) {
|
||||
MP_MSG(ao, ao->probing ? MSGL_V : MSGL_ERR,
|
||||
"Could not connect to context '%s': %s\n",
|
||||
p->options.remote, strerror(errno));
|
||||
pw_context_destroy(context);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (pw_core_add_listener(p->core, &p->core_listener, &core_events, ao) < 0)
|
||||
goto error;
|
||||
|
||||
pw_thread_loop_unlock(p->loop);
|
||||
|
||||
if (!session_has_sinks(ao)) {
|
||||
MP_VERBOSE(ao, "PipeWire does not have any audio sinks, skipping\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
pw_thread_loop_unlock(p->loop);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void wait_for_init_done(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
struct timespec abstime;
|
||||
int r;
|
||||
|
||||
r = pw_thread_loop_get_time(p->loop, &abstime, 50 * SPA_NSEC_PER_MSEC);
|
||||
if (r < 0) {
|
||||
MP_WARN(ao, "Could not get timeout for initialization: %s\n", spa_strerror(r));
|
||||
return;
|
||||
}
|
||||
|
||||
while (p->init_state == INIT_STATE_NONE) {
|
||||
r = pw_thread_loop_timed_wait_full(p->loop, &abstime);
|
||||
if (r < 0) {
|
||||
MP_WARN(ao, "Could not wait for initialization: %s\n", spa_strerror(r));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int init(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
uint8_t buffer[1024];
|
||||
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
|
||||
const struct spa_pod *params[1];
|
||||
struct pw_properties *props = pw_properties_new(
|
||||
PW_KEY_MEDIA_TYPE, "Audio",
|
||||
PW_KEY_MEDIA_CATEGORY, "Playback",
|
||||
PW_KEY_MEDIA_ROLE, ao->init_flags & AO_INIT_MEDIA_ROLE_MUSIC ? "Music" : "Movie",
|
||||
PW_KEY_NODE_NAME, ao->client_name,
|
||||
PW_KEY_NODE_DESCRIPTION, ao->client_name,
|
||||
PW_KEY_APP_NAME, ao->client_name,
|
||||
PW_KEY_APP_ID, ao->client_name,
|
||||
PW_KEY_APP_ICON_NAME, ao->client_name,
|
||||
PW_KEY_NODE_ALWAYS_PROCESS, "true",
|
||||
PW_KEY_TARGET_OBJECT, ao->device,
|
||||
NULL
|
||||
);
|
||||
|
||||
if (pipewire_init_boilerplate(ao) < 0)
|
||||
goto error_props;
|
||||
|
||||
if (p->options.buffer_msec) {
|
||||
ao->device_buffer = p->options.buffer_msec * ao->samplerate / 1000;
|
||||
|
||||
pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%d/%d", ao->device_buffer, ao->samplerate);
|
||||
}
|
||||
|
||||
pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%d", ao->samplerate);
|
||||
|
||||
enum spa_audio_format spa_format = af_fmt_to_pw(ao, ao->format);
|
||||
if (spa_format == SPA_AUDIO_FORMAT_UNKNOWN) {
|
||||
ao->format = AF_FORMAT_FLOATP;
|
||||
spa_format = SPA_AUDIO_FORMAT_F32P;
|
||||
}
|
||||
|
||||
struct spa_audio_info_raw audio_info = {
|
||||
.format = spa_format,
|
||||
.rate = ao->samplerate,
|
||||
.channels = ao->channels.num,
|
||||
};
|
||||
|
||||
for (int i = 0; i < ao->channels.num; i++)
|
||||
audio_info.position[i] = mp_speaker_id_to_spa(ao, ao->channels.speaker[i]);
|
||||
|
||||
params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &audio_info);
|
||||
if (!params[0])
|
||||
goto error_props;
|
||||
|
||||
if (af_fmt_is_planar(ao->format)) {
|
||||
ao->num_planes = ao->channels.num;
|
||||
ao->sstride = af_fmt_to_bytes(ao->format);
|
||||
} else {
|
||||
ao->num_planes = 1;
|
||||
ao->sstride = ao->channels.num * af_fmt_to_bytes(ao->format);
|
||||
}
|
||||
|
||||
pw_thread_loop_lock(p->loop);
|
||||
|
||||
p->stream = pw_stream_new(p->core, "audio-src", props);
|
||||
if (p->stream == NULL) {
|
||||
pw_thread_loop_unlock(p->loop);
|
||||
goto error;
|
||||
}
|
||||
|
||||
pw_stream_add_listener(p->stream, &p->stream_listener, &stream_events, ao);
|
||||
|
||||
enum pw_stream_flags flags = PW_STREAM_FLAG_AUTOCONNECT |
|
||||
PW_STREAM_FLAG_INACTIVE |
|
||||
PW_STREAM_FLAG_MAP_BUFFERS |
|
||||
PW_STREAM_FLAG_RT_PROCESS;
|
||||
|
||||
if (ao->init_flags & AO_INIT_EXCLUSIVE)
|
||||
flags |= PW_STREAM_FLAG_EXCLUSIVE;
|
||||
|
||||
if (pw_stream_connect(p->stream,
|
||||
PW_DIRECTION_OUTPUT, PW_ID_ANY, flags, params, 1) < 0) {
|
||||
pw_thread_loop_unlock(p->loop);
|
||||
goto error;
|
||||
}
|
||||
|
||||
wait_for_init_done(ao);
|
||||
|
||||
pw_thread_loop_unlock(p->loop);
|
||||
|
||||
if (p->init_state == INIT_STATE_ERROR)
|
||||
goto error;
|
||||
|
||||
return 0;
|
||||
|
||||
error_props:
|
||||
pw_properties_free(props);
|
||||
error:
|
||||
uninit(ao);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void reset(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
pw_thread_loop_lock(p->loop);
|
||||
pw_stream_set_active(p->stream, false);
|
||||
pw_stream_flush(p->stream, false);
|
||||
pw_thread_loop_unlock(p->loop);
|
||||
}
|
||||
|
||||
static void start(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
pw_thread_loop_lock(p->loop);
|
||||
pw_stream_set_active(p->stream, true);
|
||||
pw_thread_loop_unlock(p->loop);
|
||||
}
|
||||
|
||||
#define CONTROL_RET(r) (!r ? CONTROL_OK : CONTROL_ERROR)
|
||||
|
||||
static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
switch (cmd) {
|
||||
case AOCONTROL_GET_VOLUME: {
|
||||
float *vol = arg;
|
||||
*vol = spa_volume_to_mp_volume(p->volume);
|
||||
return CONTROL_OK;
|
||||
}
|
||||
case AOCONTROL_GET_MUTE: {
|
||||
bool *muted = arg;
|
||||
*muted = p->muted;
|
||||
return CONTROL_OK;
|
||||
}
|
||||
case AOCONTROL_SET_VOLUME:
|
||||
case AOCONTROL_SET_MUTE:
|
||||
case AOCONTROL_UPDATE_STREAM_TITLE: {
|
||||
int ret;
|
||||
|
||||
pw_thread_loop_lock(p->loop);
|
||||
switch (cmd) {
|
||||
case AOCONTROL_SET_VOLUME: {
|
||||
float *vol = arg;
|
||||
uint8_t n = ao->channels.num;
|
||||
if (p->options.volume_mode == VOLUME_MODE_CHANNEL) {
|
||||
float values[MP_NUM_CHANNELS] = {0};
|
||||
for (int i = 0; i < n; i++)
|
||||
values[i] = mp_volume_to_spa_volume(*vol);
|
||||
ret = CONTROL_RET(pw_stream_set_control(
|
||||
p->stream, SPA_PROP_channelVolumes, n, values, 0));
|
||||
} else {
|
||||
float value = mp_volume_to_spa_volume(*vol);
|
||||
ret = CONTROL_RET(pw_stream_set_control(
|
||||
p->stream, SPA_PROP_volume, 1, &value, 0));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AOCONTROL_SET_MUTE: {
|
||||
bool *muted = arg;
|
||||
float value = *muted ? 1.f : 0.f;
|
||||
ret = CONTROL_RET(pw_stream_set_control(p->stream, SPA_PROP_mute, 1, &value, 0));
|
||||
break;
|
||||
}
|
||||
case AOCONTROL_UPDATE_STREAM_TITLE: {
|
||||
char *title = arg;
|
||||
struct spa_dict_item items[1];
|
||||
items[0] = SPA_DICT_ITEM_INIT(PW_KEY_MEDIA_NAME, title);
|
||||
ret = CONTROL_RET(pw_stream_update_properties(p->stream, &SPA_DICT_INIT(items, MP_ARRAY_SIZE(items))));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ret = CONTROL_NA;
|
||||
}
|
||||
pw_thread_loop_unlock(p->loop);
|
||||
return ret;
|
||||
}
|
||||
default:
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
static void add_device_to_list(struct ao *ao, uint32_t id, const struct spa_dict *props, void *ctx)
|
||||
{
|
||||
struct ao_device_list *list = ctx;
|
||||
const char *name = spa_dict_lookup(props, PW_KEY_NODE_NAME);
|
||||
|
||||
if (!name)
|
||||
return;
|
||||
|
||||
const char *description = spa_dict_lookup(props, PW_KEY_NODE_DESCRIPTION);
|
||||
|
||||
ao_device_list_add(list, ao, &(struct ao_device_desc){name, description});
|
||||
}
|
||||
|
||||
static void hotplug_registry_global_cb(void *data, uint32_t id,
|
||||
uint32_t permissions, const char *type,
|
||||
uint32_t version, const struct spa_dict *props)
|
||||
{
|
||||
struct ao *ao = data;
|
||||
struct priv *priv = ao->priv;
|
||||
|
||||
if (!is_sink_node(type, props))
|
||||
return;
|
||||
|
||||
pw_thread_loop_lock(priv->loop);
|
||||
struct id_list *item = talloc(ao, struct id_list);
|
||||
item->id = id;
|
||||
spa_list_init(&item->node);
|
||||
spa_list_append(&priv->hotplug.sinks, &item->node);
|
||||
pw_thread_loop_unlock(priv->loop);
|
||||
|
||||
ao_hotplug_event(ao);
|
||||
}
|
||||
|
||||
static void hotplug_registry_global_remove_cb(void *data, uint32_t id)
|
||||
{
|
||||
struct ao *ao = data;
|
||||
struct priv *priv = ao->priv;
|
||||
bool removed_sink = false;
|
||||
|
||||
struct id_list *e;
|
||||
|
||||
pw_thread_loop_lock(priv->loop);
|
||||
spa_list_for_each(e, &priv->hotplug.sinks, node) {
|
||||
if (e->id == id) {
|
||||
removed_sink = true;
|
||||
spa_list_remove(&e->node);
|
||||
talloc_free(e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pw_thread_loop_unlock(priv->loop);
|
||||
|
||||
if (removed_sink)
|
||||
ao_hotplug_event(ao);
|
||||
}
|
||||
|
||||
static const struct pw_registry_events hotplug_registry_events = {
|
||||
.version = PW_VERSION_REGISTRY_EVENTS,
|
||||
.global = hotplug_registry_global_cb,
|
||||
.global_remove = hotplug_registry_global_remove_cb,
|
||||
};
|
||||
|
||||
static int hotplug_init(struct ao *ao)
|
||||
{
|
||||
struct priv *priv = ao->priv;
|
||||
|
||||
int res = pipewire_init_boilerplate(ao);
|
||||
if (res)
|
||||
goto error_no_unlock;
|
||||
|
||||
pw_thread_loop_lock(priv->loop);
|
||||
|
||||
spa_zero(priv->hotplug);
|
||||
spa_list_init(&priv->hotplug.sinks);
|
||||
|
||||
priv->hotplug.registry = pw_core_get_registry(priv->core, PW_VERSION_REGISTRY, 0);
|
||||
if (!priv->hotplug.registry)
|
||||
goto error;
|
||||
|
||||
if (pw_registry_add_listener(priv->hotplug.registry, &priv->hotplug.registry_listener, &hotplug_registry_events, ao) < 0) {
|
||||
pw_proxy_destroy((struct pw_proxy *)priv->hotplug.registry);
|
||||
goto error;
|
||||
}
|
||||
|
||||
pw_thread_loop_unlock(priv->loop);
|
||||
|
||||
return res;
|
||||
|
||||
error:
|
||||
pw_thread_loop_unlock(priv->loop);
|
||||
error_no_unlock:
|
||||
uninit(ao);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void hotplug_uninit(struct ao *ao)
|
||||
{
|
||||
struct priv *priv = ao->priv;
|
||||
|
||||
pw_thread_loop_lock(priv->loop);
|
||||
|
||||
spa_hook_remove(&priv->hotplug.registry_listener);
|
||||
pw_proxy_destroy((struct pw_proxy *)priv->hotplug.registry);
|
||||
|
||||
pw_thread_loop_unlock(priv->loop);
|
||||
uninit(ao);
|
||||
}
|
||||
|
||||
static void list_devs(struct ao *ao, struct ao_device_list *list)
|
||||
{
|
||||
ao_device_list_add(list, ao, &(struct ao_device_desc){});
|
||||
|
||||
if (for_each_sink(ao, add_device_to_list, list) < 0)
|
||||
MP_WARN(ao, "Could not list devices, list may be incomplete\n");
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
const struct ao_driver audio_out_pipewire = {
|
||||
.description = "PipeWire audio output",
|
||||
.name = "pipewire",
|
||||
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.reset = reset,
|
||||
.start = start,
|
||||
|
||||
.control = control,
|
||||
|
||||
.hotplug_init = hotplug_init,
|
||||
.hotplug_uninit = hotplug_uninit,
|
||||
.list_devs = list_devs,
|
||||
|
||||
.priv_size = sizeof(struct priv),
|
||||
.priv_defaults = &(const struct priv)
|
||||
{
|
||||
.loop = NULL,
|
||||
.stream = NULL,
|
||||
.init_state = INIT_STATE_NONE,
|
||||
.options.buffer_msec = 0,
|
||||
.options.volume_mode = VOLUME_MODE_CHANNEL,
|
||||
},
|
||||
.options_prefix = "pipewire",
|
||||
.options = (const struct m_option[]) {
|
||||
{"buffer", OPT_CHOICE(options.buffer_msec, {"native", 0}),
|
||||
M_RANGE(1, 2000)},
|
||||
{"remote", OPT_STRING(options.remote) },
|
||||
{"volume-mode", OPT_CHOICE(options.volume_mode,
|
||||
{"channel", VOLUME_MODE_CHANNEL}, {"global", VOLUME_MODE_GLOBAL})},
|
||||
{0}
|
||||
},
|
||||
};
|
||||
+23
-11
@@ -24,9 +24,11 @@
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <math.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include <pulse/pulseaudio.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "audio/format.h"
|
||||
#include "common/msg.h"
|
||||
#include "options/m_option.h"
|
||||
@@ -55,8 +57,8 @@ struct priv {
|
||||
|
||||
char *cfg_host;
|
||||
int cfg_buffer;
|
||||
bool cfg_latency_hacks;
|
||||
bool cfg_allow_suspended;
|
||||
int cfg_latency_hacks;
|
||||
int cfg_allow_suspended;
|
||||
};
|
||||
|
||||
#define GENERIC_ERR_MSG(str) \
|
||||
@@ -678,8 +680,14 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
// we naively copied the struct, without updating pointers etc.
|
||||
// Pointers might point to invalid data, accessors might fail.
|
||||
if (cmd == AOCONTROL_GET_VOLUME) {
|
||||
float *vol = arg;
|
||||
*vol = VOL_PA2MP(pa_cvolume_avg(&priv->pi.volume));
|
||||
ao_control_vol_t *vol = arg;
|
||||
if (priv->pi.volume.channels != 2)
|
||||
vol->left = vol->right =
|
||||
VOL_PA2MP(pa_cvolume_avg(&priv->pi.volume));
|
||||
else {
|
||||
vol->left = VOL_PA2MP(priv->pi.volume.values[0]);
|
||||
vol->right = VOL_PA2MP(priv->pi.volume.values[1]);
|
||||
}
|
||||
} else if (cmd == AOCONTROL_GET_MUTE) {
|
||||
bool *mute = arg;
|
||||
*mute = priv->pi.mute;
|
||||
@@ -693,11 +701,16 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
priv->retval = 0;
|
||||
uint32_t stream_index = pa_stream_get_index(priv->stream);
|
||||
if (cmd == AOCONTROL_SET_VOLUME) {
|
||||
const float *vol = arg;
|
||||
const ao_control_vol_t *vol = arg;
|
||||
struct pa_cvolume volume;
|
||||
|
||||
pa_cvolume_reset(&volume, ao->channels.num);
|
||||
pa_cvolume_set(&volume, volume.channels, VOL_MP2PA(*vol));
|
||||
if (volume.channels != 2)
|
||||
pa_cvolume_set(&volume, volume.channels, VOL_MP2PA(vol->left));
|
||||
else {
|
||||
volume.values[0] = VOL_MP2PA(vol->left);
|
||||
volume.values[1] = VOL_MP2PA(vol->right);
|
||||
}
|
||||
if (!waitop(priv, pa_context_set_sink_input_volume(priv->context,
|
||||
stream_index,
|
||||
&volume,
|
||||
@@ -716,9 +729,8 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
GENERIC_ERR_MSG("pa_context_set_sink_input_mute() failed");
|
||||
return CONTROL_ERROR;
|
||||
}
|
||||
} else {
|
||||
MP_ASSERT_UNREACHABLE();
|
||||
}
|
||||
} else
|
||||
abort();
|
||||
return CONTROL_OK;
|
||||
}
|
||||
|
||||
@@ -809,8 +821,8 @@ const struct ao_driver audio_out_pulse = {
|
||||
{"host", OPT_STRING(cfg_host)},
|
||||
{"buffer", OPT_CHOICE(cfg_buffer, {"native", 0}),
|
||||
M_RANGE(1, 2000)},
|
||||
{"latency-hacks", OPT_BOOL(cfg_latency_hacks)},
|
||||
{"allow-suspended", OPT_BOOL(cfg_allow_suspended)},
|
||||
{"latency-hacks", OPT_FLAG(cfg_latency_hacks)},
|
||||
{"allow-suspended", OPT_FLAG(cfg_allow_suspended)},
|
||||
{0}
|
||||
},
|
||||
.options_prefix = "pulse",
|
||||
|
||||
+2
-1
@@ -18,6 +18,7 @@
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "audio/format.h"
|
||||
#include "mpv_talloc.h"
|
||||
#include "ao.h"
|
||||
@@ -61,7 +62,7 @@ static void audio_callback(void *userdata, Uint8 *stream, int len)
|
||||
// fixed latency.
|
||||
double delay = 2 * len / (double)ao->bps;
|
||||
|
||||
ao_read_data(ao, data, len / ao->sstride, mp_time_ns() + MP_TIME_S_TO_NS(delay));
|
||||
ao_read_data(ao, data, len / ao->sstride, mp_time_us() + 1000000LL * delay);
|
||||
}
|
||||
|
||||
static void uninit(struct ao *ao)
|
||||
|
||||
@@ -1,321 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
|
||||
* Copyright (c) 2013 Christian Neukirchen <chneukirchen@gmail.com>
|
||||
* Copyright (c) 2020 Rozhuk Ivan <rozhuk.im@gmail.com>
|
||||
* Copyright (c) 2021 Andrew Krasavin <noiseless-ak@yandex.ru>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <poll.h>
|
||||
#include <errno.h>
|
||||
#include <sndio.h>
|
||||
|
||||
#include "options/m_option.h"
|
||||
#include "common/msg.h"
|
||||
|
||||
#include "audio/format.h"
|
||||
#include "ao.h"
|
||||
#include "internal.h"
|
||||
|
||||
struct priv {
|
||||
struct sio_hdl *hdl;
|
||||
struct sio_par par;
|
||||
int delay;
|
||||
bool playing;
|
||||
int vol;
|
||||
int havevol;
|
||||
struct pollfd *pfd;
|
||||
};
|
||||
|
||||
|
||||
static const struct mp_chmap sndio_layouts[] = {
|
||||
{0}, /* empty */
|
||||
{1, {MP_SPEAKER_ID_FL}}, /* mono */
|
||||
MP_CHMAP2(FL, FR), /* stereo */
|
||||
{0}, /* 2.1 */
|
||||
MP_CHMAP4(FL, FR, BL, BR), /* 4.0 */
|
||||
{0}, /* 5.0 */
|
||||
MP_CHMAP6(FL, FR, BL, BR, FC, LFE), /* 5.1 */
|
||||
{0}, /* 6.1 */
|
||||
MP_CHMAP8(FL, FR, BL, BR, FC, LFE, SL, SR), /* 7.1 */
|
||||
/* Above is the fixed channel assignment for sndio, since we need to
|
||||
* fill all channels and cannot insert silence, not all layouts are
|
||||
* supported.
|
||||
* NOTE: MP_SPEAKER_ID_NA could be used to add padding channels. */
|
||||
};
|
||||
|
||||
static void uninit(struct ao *ao);
|
||||
|
||||
|
||||
/* Make libsndio call movecb(). */
|
||||
static void process_events(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
int n = sio_pollfd(p->hdl, p->pfd, POLLOUT);
|
||||
while (poll(p->pfd, n, 0) < 0 && errno == EINTR);
|
||||
|
||||
sio_revents(p->hdl, p->pfd);
|
||||
}
|
||||
|
||||
/* Call-back invoked to notify of the hardware position. */
|
||||
static void movecb(void *addr, int delta)
|
||||
{
|
||||
struct ao *ao = addr;
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
p->delay -= delta;
|
||||
}
|
||||
|
||||
/* Call-back invoked to notify about volume changes. */
|
||||
static void volcb(void *addr, unsigned newvol)
|
||||
{
|
||||
struct ao *ao = addr;
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
p->vol = newvol;
|
||||
}
|
||||
|
||||
static int init(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
struct mp_chmap_sel sel = {0};
|
||||
size_t i;
|
||||
struct af_to_par {
|
||||
int format, bits, sig;
|
||||
};
|
||||
static const struct af_to_par af_to_par[] = {
|
||||
{AF_FORMAT_U8, 8, 0},
|
||||
{AF_FORMAT_S16, 16, 1},
|
||||
{AF_FORMAT_S32, 32, 1},
|
||||
};
|
||||
const struct af_to_par *ap;
|
||||
const char *device = ((ao->device) ? ao->device : SIO_DEVANY);
|
||||
|
||||
/* Opening device. */
|
||||
MP_VERBOSE(ao, "Using '%s' audio device.\n", device);
|
||||
p->hdl = sio_open(device, SIO_PLAY, 0);
|
||||
if (p->hdl == NULL) {
|
||||
MP_ERR(ao, "Can't open audio device %s.\n", device);
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
sio_initpar(&p->par);
|
||||
|
||||
/* Selecting sound format. */
|
||||
ao->format = af_fmt_from_planar(ao->format);
|
||||
|
||||
p->par.bits = 16;
|
||||
p->par.sig = 1;
|
||||
p->par.le = SIO_LE_NATIVE;
|
||||
for (i = 0; i < MP_ARRAY_SIZE(af_to_par); i++) {
|
||||
ap = &af_to_par[i];
|
||||
if (ap->format == ao->format) {
|
||||
p->par.bits = ap->bits;
|
||||
p->par.sig = ap->sig;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
p->par.rate = ao->samplerate;
|
||||
|
||||
/* Channels count. */
|
||||
for (i = 0; i < MP_ARRAY_SIZE(sndio_layouts); i++) {
|
||||
mp_chmap_sel_add_map(&sel, &sndio_layouts[i]);
|
||||
}
|
||||
if (!ao_chmap_sel_adjust(ao, &sel, &ao->channels))
|
||||
goto err_out;
|
||||
|
||||
p->par.pchan = ao->channels.num;
|
||||
p->par.appbufsz = p->par.rate * 250 / 1000; /* 250ms buffer */
|
||||
p->par.round = p->par.rate * 10 / 1000; /* 10ms block size */
|
||||
|
||||
if (!sio_setpar(p->hdl, &p->par)) {
|
||||
MP_ERR(ao, "couldn't set params\n");
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
/* Get current sound params. */
|
||||
if (!sio_getpar(p->hdl, &p->par)) {
|
||||
MP_ERR(ao, "couldn't get params\n");
|
||||
goto err_out;
|
||||
}
|
||||
if (p->par.bps > 1 && p->par.le != SIO_LE_NATIVE) {
|
||||
MP_ERR(ao, "swapped endian output not supported\n");
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
/* Update sound params. */
|
||||
if (p->par.bits == 8 && p->par.bps == 1 && !p->par.sig) {
|
||||
ao->format = AF_FORMAT_U8;
|
||||
} else if (p->par.bits == 16 && p->par.bps == 2 && p->par.sig) {
|
||||
ao->format = AF_FORMAT_S16;
|
||||
} else if ((p->par.bits == 32 || p->par.msb) && p->par.bps == 4 && p->par.sig) {
|
||||
ao->format = AF_FORMAT_S32;
|
||||
} else {
|
||||
MP_ERR(ao, "couldn't set format\n");
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
p->havevol = sio_onvol(p->hdl, volcb, ao);
|
||||
sio_onmove(p->hdl, movecb, ao);
|
||||
|
||||
p->pfd = talloc_array_ptrtype(p, p->pfd, sio_nfds(p->hdl));
|
||||
if (!p->pfd)
|
||||
goto err_out;
|
||||
|
||||
ao->device_buffer = p->par.bufsz;
|
||||
MP_VERBOSE(ao, "bufsz = %i, appbufsz = %i, round = %i\n",
|
||||
p->par.bufsz, p->par.appbufsz, p->par.round);
|
||||
|
||||
p->delay = 0;
|
||||
p->playing = false;
|
||||
if (!sio_start(p->hdl)) {
|
||||
MP_ERR(ao, "start: sio_start() fail.\n");
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_out:
|
||||
uninit(ao);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void uninit(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
if (p->hdl) {
|
||||
sio_close(p->hdl);
|
||||
p->hdl = NULL;
|
||||
}
|
||||
p->pfd = NULL;
|
||||
p->playing = false;
|
||||
}
|
||||
|
||||
static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
float *vol = arg;
|
||||
|
||||
switch (cmd) {
|
||||
case AOCONTROL_GET_VOLUME:
|
||||
if (!p->havevol)
|
||||
return CONTROL_FALSE;
|
||||
*vol = p->vol * 100 / SIO_MAXVOL;
|
||||
break;
|
||||
case AOCONTROL_SET_VOLUME:
|
||||
if (!p->havevol)
|
||||
return CONTROL_FALSE;
|
||||
sio_setvol(p->hdl, *vol * SIO_MAXVOL / 100);
|
||||
break;
|
||||
default:
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
return CONTROL_OK;
|
||||
}
|
||||
|
||||
static void reset(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
if (p->playing) {
|
||||
p->playing = false;
|
||||
|
||||
#if HAVE_SNDIO_1_9
|
||||
if (!sio_flush(p->hdl)) {
|
||||
MP_ERR(ao, "reset: couldn't sio_flush()\n");
|
||||
#else
|
||||
if (!sio_stop(p->hdl)) {
|
||||
MP_ERR(ao, "reset: couldn't sio_stop()\n");
|
||||
#endif
|
||||
}
|
||||
p->delay = 0;
|
||||
if (!sio_start(p->hdl)) {
|
||||
MP_ERR(ao, "reset: sio_start() fail.\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void start(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
p->playing = true;
|
||||
process_events(ao);
|
||||
}
|
||||
|
||||
static bool audio_write(struct ao *ao, void **data, int samples)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
const size_t size = (samples * ao->sstride);
|
||||
size_t rc;
|
||||
|
||||
rc = sio_write(p->hdl, data[0], size);
|
||||
if (rc != size) {
|
||||
MP_WARN(ao, "audio_write: unexpected partial write: required: %zu, written: %zu.\n",
|
||||
size, rc);
|
||||
reset(ao);
|
||||
p->playing = false;
|
||||
return false;
|
||||
}
|
||||
p->delay += samples;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void get_state(struct ao *ao, struct mp_pcm_state *state)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
process_events(ao);
|
||||
|
||||
/* how many samples we can play without blocking */
|
||||
state->free_samples = ao->device_buffer - p->delay;
|
||||
state->free_samples = state->free_samples / p->par.round * p->par.round;
|
||||
/* how many samples are already in the buffer to be played */
|
||||
state->queued_samples = p->delay;
|
||||
/* delay in seconds between first and last sample in buffer */
|
||||
state->delay = p->delay / (double)p->par.rate;
|
||||
|
||||
/* report unexpected EOF / underrun */
|
||||
if ((state->queued_samples && state->queued_samples &&
|
||||
(state->queued_samples < state->free_samples) &&
|
||||
p->playing) || sio_eof(p->hdl))
|
||||
{
|
||||
MP_VERBOSE(ao, "get_state: EOF/underrun detected.\n");
|
||||
MP_VERBOSE(ao, "get_state: free: %d, queued: %d, delay: %lf\n", \
|
||||
state->free_samples, state->queued_samples, state->delay);
|
||||
p->playing = false;
|
||||
state->playing = p->playing;
|
||||
ao_wakeup_playthread(ao);
|
||||
} else {
|
||||
state->playing = p->playing;
|
||||
}
|
||||
}
|
||||
|
||||
const struct ao_driver audio_out_sndio = {
|
||||
.name = "sndio",
|
||||
.description = "sndio audio output",
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.control = control,
|
||||
.reset = reset,
|
||||
.start = start,
|
||||
.write = audio_write,
|
||||
.get_state = get_state,
|
||||
.priv_size = sizeof(struct priv),
|
||||
};
|
||||
+34
-37
@@ -36,8 +36,7 @@ static UINT64 uint64_scale(UINT64 x, UINT64 num, UINT64 den)
|
||||
+ ((x % den) * (num % den)) / den;
|
||||
}
|
||||
|
||||
static HRESULT get_device_delay(struct wasapi_state *state, double *delay_ns)
|
||||
{
|
||||
static HRESULT get_device_delay(struct wasapi_state *state, double *delay_us) {
|
||||
UINT64 sample_count = atomic_load(&state->sample_count);
|
||||
UINT64 position, qpc_position;
|
||||
HRESULT hr;
|
||||
@@ -55,7 +54,7 @@ static HRESULT get_device_delay(struct wasapi_state *state, double *delay_ns)
|
||||
state->format.Format.nSamplesPerSec,
|
||||
state->clock_frequency);
|
||||
INT64 diff = sample_count - sample_position;
|
||||
*delay_ns = diff * 1e9 / state->format.Format.nSamplesPerSec;
|
||||
*delay_us = diff * 1e6 / state->format.Format.nSamplesPerSec;
|
||||
|
||||
// Correct for any delay in IAudioClock_GetPosition above.
|
||||
// This should normally be very small (<1 us), but just in case. . .
|
||||
@@ -66,16 +65,16 @@ static HRESULT get_device_delay(struct wasapi_state *state, double *delay_ns)
|
||||
// ignore the above calculation if it yields more than 10 seconds (due to
|
||||
// possible overflow inside IAudioClock_GetPosition)
|
||||
if (qpc_diff < 10 * 10000000) {
|
||||
*delay_ns -= qpc_diff * 100.0; // convert to ns
|
||||
*delay_us -= qpc_diff / 10.0; // convert to us
|
||||
} else {
|
||||
MP_VERBOSE(state, "Insane qpc delay correction of %g seconds. "
|
||||
"Ignoring it.\n", qpc_diff / 10000000.0);
|
||||
}
|
||||
|
||||
if (sample_count > 0 && *delay_ns <= 0) {
|
||||
MP_WARN(state, "Under-run: Device delay: %g ns\n", *delay_ns);
|
||||
if (sample_count > 0 && *delay_us <= 0) {
|
||||
MP_WARN(state, "Under-run: Device delay: %g us\n", *delay_us);
|
||||
} else {
|
||||
MP_TRACE(state, "Device delay: %g ns\n", *delay_ns);
|
||||
MP_TRACE(state, "Device delay: %g us\n", *delay_us);
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
@@ -117,11 +116,11 @@ static bool thread_feed(struct ao *ao)
|
||||
MP_TRACE(ao, "Frame to fill: %"PRIu32". Padding: %"PRIu32"\n",
|
||||
frame_count, padding);
|
||||
|
||||
double delay_ns;
|
||||
hr = get_device_delay(state, &delay_ns);
|
||||
double delay_us;
|
||||
hr = get_device_delay(state, &delay_us);
|
||||
EXIT_ON_ERROR(hr);
|
||||
// add the buffer delay
|
||||
delay_ns += frame_count * 1e9 / state->format.Format.nSamplesPerSec;
|
||||
delay_us += frame_count * 1e6 / state->format.Format.nSamplesPerSec;
|
||||
|
||||
BYTE *pData;
|
||||
hr = IAudioRenderClient_GetBuffer(state->pRenderClient,
|
||||
@@ -132,7 +131,7 @@ static bool thread_feed(struct ao *ao)
|
||||
|
||||
ao_read_data_converted(ao, &state->convert_format,
|
||||
(void **)data, frame_count,
|
||||
mp_time_ns() + (int64_t)llrint(delay_ns));
|
||||
mp_time_us() + (int64_t)llrint(delay_us));
|
||||
|
||||
// note, we can't use ao_read_data return value here since we already
|
||||
// committed to frame_count above in the GetBuffer call
|
||||
@@ -199,7 +198,7 @@ static DWORD __stdcall AudioThread(void *lpParameter)
|
||||
{
|
||||
struct ao *ao = lpParameter;
|
||||
struct wasapi_state *state = ao->priv;
|
||||
mp_thread_set_name("ao/wasapi");
|
||||
mpthread_set_name("wasapi event");
|
||||
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
|
||||
|
||||
state->init_ok = wasapi_thread_init(ao);
|
||||
@@ -349,10 +348,13 @@ static int thread_control_exclusive(struct ao *ao, enum aocontrol cmd, void *arg
|
||||
case AOCONTROL_GET_VOLUME:
|
||||
IAudioEndpointVolume_GetMasterVolumeLevelScalar(
|
||||
state->pEndpointVolume, &volume);
|
||||
*(float *)arg = volume;
|
||||
*(ao_control_vol_t *)arg = (ao_control_vol_t){
|
||||
.left = 100.0f * volume,
|
||||
.right = 100.0f * volume,
|
||||
};
|
||||
return CONTROL_OK;
|
||||
case AOCONTROL_SET_VOLUME:
|
||||
volume = (*(float *)arg) / 100.f;
|
||||
volume = ((ao_control_vol_t *)arg)->left / 100.f;
|
||||
IAudioEndpointVolume_SetMasterVolumeLevelScalar(
|
||||
state->pEndpointVolume, volume, NULL);
|
||||
return CONTROL_OK;
|
||||
@@ -379,10 +381,13 @@ static int thread_control_shared(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
switch(cmd) {
|
||||
case AOCONTROL_GET_VOLUME:
|
||||
ISimpleAudioVolume_GetMasterVolume(state->pAudioVolume, &volume);
|
||||
*(float *)arg = volume;
|
||||
*(ao_control_vol_t *)arg = (ao_control_vol_t){
|
||||
.left = 100.0f * volume,
|
||||
.right = 100.0f * volume,
|
||||
};
|
||||
return CONTROL_OK;
|
||||
case AOCONTROL_SET_VOLUME:
|
||||
volume = (*(float *)arg) / 100.f;
|
||||
volume = ((ao_control_vol_t *)arg)->left / 100.f;
|
||||
ISimpleAudioVolume_SetMasterVolume(state->pAudioVolume, volume, NULL);
|
||||
return CONTROL_OK;
|
||||
case AOCONTROL_GET_MUTE:
|
||||
@@ -407,28 +412,20 @@ static int thread_control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
if (!state->pSessionControl)
|
||||
return CONTROL_FALSE;
|
||||
|
||||
wchar_t *title = mp_from_utf8(NULL, (const char *)arg);
|
||||
HRESULT hr = IAudioSessionControl_SetDisplayName(state->pSessionControl,
|
||||
title,NULL);
|
||||
wchar_t *title = mp_from_utf8(NULL, (char*)arg);
|
||||
wchar_t *tmp = NULL;
|
||||
// There is a weird race condition in the IAudioSessionControl itself --
|
||||
// it seems that *sometimes* the SetDisplayName does not take effect and
|
||||
// it still shows the old title. Use this loop to insist until it works.
|
||||
do {
|
||||
IAudioSessionControl_SetDisplayName(state->pSessionControl, title, NULL);
|
||||
|
||||
SAFE_DESTROY(tmp, CoTaskMemFree(tmp));
|
||||
IAudioSessionControl_GetDisplayName(state->pSessionControl, &tmp);
|
||||
} while (wcscmp(title, tmp));
|
||||
SAFE_DESTROY(tmp, CoTaskMemFree(tmp));
|
||||
talloc_free(title);
|
||||
|
||||
if (SUCCEEDED(hr))
|
||||
return CONTROL_OK;
|
||||
|
||||
MP_WARN(ao, "Error setting audio session name: %s\n",
|
||||
mp_HRESULT_to_str(hr));
|
||||
|
||||
assert(ao->client_name);
|
||||
if (!ao->client_name)
|
||||
return CONTROL_ERROR;
|
||||
|
||||
// Fallback to client name
|
||||
title = mp_from_utf8(NULL, ao->client_name);
|
||||
IAudioSessionControl_SetDisplayName(state->pSessionControl,
|
||||
title, NULL);
|
||||
talloc_free(title);
|
||||
|
||||
return CONTROL_ERROR;
|
||||
return CONTROL_OK;
|
||||
}
|
||||
|
||||
return state->share_mode == AUDCLNT_SHAREMODE_EXCLUSIVE ?
|
||||
|
||||
@@ -20,10 +20,8 @@
|
||||
#ifndef MP_AO_WASAPI_H_
|
||||
#define MP_AO_WASAPI_H_
|
||||
|
||||
#include <stdatomic.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <windows.h>
|
||||
#include <mmdeviceapi.h>
|
||||
#include <audioclient.h>
|
||||
@@ -31,6 +29,7 @@
|
||||
#include <endpointvolume.h>
|
||||
|
||||
#include "common/msg.h"
|
||||
#include "osdep/atomic.h"
|
||||
#include "osdep/windows_utils.h"
|
||||
#include "internal.h"
|
||||
#include "ao.h"
|
||||
|
||||
+14
-31
@@ -31,6 +31,8 @@
|
||||
#include "osdep/strnlen.h"
|
||||
#include "ao_wasapi.h"
|
||||
|
||||
#define MIXER_DEFAULT_LABEL L"mpv - video player"
|
||||
|
||||
DEFINE_PROPERTYKEY(mp_PKEY_Device_FriendlyName,
|
||||
0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20,
|
||||
0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14);
|
||||
@@ -143,19 +145,7 @@ static void set_waveformat(WAVEFORMATEXTENSIBLE *wformat,
|
||||
|
||||
wformat->SubFormat = *format_to_subtype(format->mp_format);
|
||||
wformat->Samples.wValidBitsPerSample = format->used_msb;
|
||||
|
||||
uint64_t chans = mp_chmap_to_waveext(channels);
|
||||
wformat->dwChannelMask = chans;
|
||||
|
||||
if (wformat->Format.nChannels > 8 || wformat->dwChannelMask != chans) {
|
||||
// IAudioClient::IsFormatSupported tend to fallback to stereo for closest
|
||||
// format match when there are more channels. Remix to standard layout.
|
||||
// Also if input channel mask has channels outside 32-bits override it
|
||||
// and hope for the best...
|
||||
wformat->dwChannelMask = KSAUDIO_SPEAKER_7POINT1_SURROUND;
|
||||
wformat->Format.nChannels = 8;
|
||||
}
|
||||
|
||||
wformat->dwChannelMask = mp_chmap_to_waveext(channels);
|
||||
update_waveformat_datarate(wformat);
|
||||
}
|
||||
|
||||
@@ -248,8 +238,7 @@ static char *waveformat_to_str_buf(char *buf, size_t buf_size, WAVEFORMATEX *wf)
|
||||
(unsigned) wf->nSamplesPerSec);
|
||||
return buf;
|
||||
}
|
||||
#define waveformat_to_str_(wf, sz) waveformat_to_str_buf((char[sz]){0}, sz, (wf))
|
||||
#define waveformat_to_str(wf) waveformat_to_str_(wf, MP_NUM_CHANNELS * 4 + 42)
|
||||
#define waveformat_to_str(wf) waveformat_to_str_buf((char[64]){0}, 64, (wf))
|
||||
|
||||
static void waveformat_copy(WAVEFORMATEXTENSIBLE* dst, WAVEFORMATEX* src)
|
||||
{
|
||||
@@ -385,7 +374,7 @@ static bool search_channels(struct ao *ao, WAVEFORMATEXTENSIBLE *wformat)
|
||||
"3.0", "3.0(back)",
|
||||
"quad", "quad(side)", "3.1",
|
||||
"5.0(side)", "4.1",
|
||||
"5.1(side)", "6.0", "6.0(front)", "hexagonal",
|
||||
"5.1(side)", "6.0", "6.0(front)", "hexagonal"
|
||||
"6.1(back)", "6.1(front)", "7.0", "7.0(front)",
|
||||
"7.1(wide)", "7.1(wide-side)", "7.1(rear)", "octagonal", NULL};
|
||||
|
||||
@@ -556,7 +545,7 @@ exit_label:
|
||||
return hr;
|
||||
}
|
||||
|
||||
static void init_session_display(struct wasapi_state *state, const char *name) {
|
||||
static void init_session_display(struct wasapi_state *state) {
|
||||
HRESULT hr = IAudioClient_GetService(state->pAudioClient,
|
||||
&IID_IAudioSessionControl,
|
||||
(void **)&state->pSessionControl);
|
||||
@@ -571,20 +560,14 @@ static void init_session_display(struct wasapi_state *state, const char *name) {
|
||||
mp_HRESULT_to_str(hr));
|
||||
}
|
||||
|
||||
assert(name);
|
||||
if (!name)
|
||||
return;
|
||||
|
||||
wchar_t *title = mp_from_utf8(NULL, name);
|
||||
hr = IAudioSessionControl_SetDisplayName(state->pSessionControl, title, NULL);
|
||||
talloc_free(title);
|
||||
|
||||
hr = IAudioSessionControl_SetDisplayName(state->pSessionControl,
|
||||
MIXER_DEFAULT_LABEL, NULL);
|
||||
EXIT_ON_ERROR(hr);
|
||||
return;
|
||||
exit_label:
|
||||
// if we got here then the session control is useless - release it
|
||||
SAFE_RELEASE(state->pSessionControl);
|
||||
MP_WARN(state, "Error setting audio session name: %s\n",
|
||||
MP_WARN(state, "Error setting audio session display name: %s\n",
|
||||
mp_HRESULT_to_str(hr));
|
||||
return;
|
||||
}
|
||||
@@ -685,7 +668,7 @@ static HRESULT fix_format(struct ao *ao, bool align_hack)
|
||||
hr = init_clock(state);
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
init_session_display(state, ao->client_name);
|
||||
init_session_display(state);
|
||||
init_volume_control(state);
|
||||
|
||||
#if !HAVE_UWP
|
||||
@@ -945,7 +928,7 @@ bool wasapi_thread_init(struct ao *ao)
|
||||
{
|
||||
struct wasapi_state *state = ao->priv;
|
||||
MP_DBG(ao, "Init wasapi thread\n");
|
||||
int64_t retry_wait = MP_TIME_US_TO_NS(1);
|
||||
int64_t retry_wait = 1;
|
||||
bool align_hack = false;
|
||||
HRESULT hr;
|
||||
|
||||
@@ -1028,13 +1011,13 @@ retry:
|
||||
goto retry;
|
||||
case AUDCLNT_E_DEVICE_IN_USE:
|
||||
case AUDCLNT_E_DEVICE_INVALIDATED:
|
||||
if (retry_wait > MP_TIME_US_TO_NS(8)) {
|
||||
if (retry_wait > 8) {
|
||||
MP_FATAL(ao, "Bad device retry failed\n");
|
||||
return false;
|
||||
}
|
||||
wasapi_thread_uninit(ao);
|
||||
MP_WARN(ao, "Retrying in %"PRId64" ns\n", retry_wait);
|
||||
mp_sleep_ns(retry_wait);
|
||||
MP_WARN(ao, "Retrying in %"PRId64" us\n", retry_wait);
|
||||
mp_sleep_us(retry_wait);
|
||||
retry_wait *= 2;
|
||||
goto retry;
|
||||
}
|
||||
|
||||
+95
-131
@@ -16,6 +16,7 @@
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <pthread.h>
|
||||
#include <inttypes.h>
|
||||
#include <math.h>
|
||||
#include <unistd.h>
|
||||
@@ -38,12 +39,12 @@
|
||||
|
||||
struct buffer_state {
|
||||
// Buffer and AO
|
||||
mp_mutex lock;
|
||||
mp_cond wakeup;
|
||||
pthread_mutex_t lock;
|
||||
pthread_cond_t wakeup;
|
||||
|
||||
// Playthread sleep
|
||||
mp_mutex pt_lock;
|
||||
mp_cond pt_wakeup;
|
||||
pthread_mutex_t pt_lock;
|
||||
pthread_cond_t pt_wakeup;
|
||||
|
||||
// Access from AO driver's thread only.
|
||||
char *convert_buffer;
|
||||
@@ -61,7 +62,7 @@ struct buffer_state {
|
||||
bool playing; // logically playing audio from buffer
|
||||
bool paused; // logically paused
|
||||
|
||||
int64_t end_time_ns; // absolute output time of last played sample
|
||||
int64_t end_time_us; // absolute output time of last played sample
|
||||
|
||||
bool initial_unblocked;
|
||||
|
||||
@@ -69,7 +70,7 @@ struct buffer_state {
|
||||
bool hw_paused; // driver->set_pause() was used successfully
|
||||
bool recover_pause; // non-hw_paused: needs to recover delay
|
||||
struct mp_pcm_state prepause_state;
|
||||
mp_thread thread; // thread shoveling data to AO
|
||||
pthread_t thread; // thread shoveling data to AO
|
||||
bool thread_valid; // thread is running
|
||||
struct mp_aframe *temp_buf;
|
||||
|
||||
@@ -78,15 +79,15 @@ struct buffer_state {
|
||||
bool terminate; // exit thread
|
||||
};
|
||||
|
||||
static MP_THREAD_VOID playthread(void *arg);
|
||||
static void *playthread(void *arg);
|
||||
|
||||
void ao_wakeup_playthread(struct ao *ao)
|
||||
{
|
||||
struct buffer_state *p = ao->buffer_state;
|
||||
mp_mutex_lock(&p->pt_lock);
|
||||
pthread_mutex_lock(&p->pt_lock);
|
||||
p->need_wakeup = true;
|
||||
mp_cond_broadcast(&p->pt_wakeup);
|
||||
mp_mutex_unlock(&p->pt_lock);
|
||||
pthread_cond_broadcast(&p->pt_wakeup);
|
||||
pthread_mutex_unlock(&p->pt_lock);
|
||||
}
|
||||
|
||||
// called locked
|
||||
@@ -114,8 +115,7 @@ struct mp_async_queue *ao_get_queue(struct ao *ao)
|
||||
}
|
||||
|
||||
// Special behavior with data==NULL: caller uses p->pending.
|
||||
static int read_buffer(struct ao *ao, void **data, int samples, bool *eof,
|
||||
bool pad_silence)
|
||||
static int read_buffer(struct ao *ao, void **data, int samples, bool *eof)
|
||||
{
|
||||
struct buffer_state *p = ao->buffer_state;
|
||||
int pos = 0;
|
||||
@@ -161,72 +161,43 @@ static int read_buffer(struct ao *ao, void **data, int samples, bool *eof,
|
||||
}
|
||||
|
||||
// pad with silence (underflow/paused/eof)
|
||||
if (pad_silence) {
|
||||
for (int n = 0; n < ao->num_planes; n++) {
|
||||
af_fill_silence((char *)data[n] + pos * ao->sstride,
|
||||
(samples - pos) * ao->sstride,
|
||||
ao->format);
|
||||
}
|
||||
for (int n = 0; n < ao->num_planes; n++) {
|
||||
af_fill_silence((char *)data[n] + pos * ao->sstride,
|
||||
(samples - pos) * ao->sstride,
|
||||
ao->format);
|
||||
}
|
||||
|
||||
ao_post_process_data(ao, data, pos);
|
||||
return pos;
|
||||
}
|
||||
|
||||
static int ao_read_data_unlocked(struct ao *ao, void **data, int samples,
|
||||
int64_t out_time_ns, bool pad_silence)
|
||||
{
|
||||
struct buffer_state *p = ao->buffer_state;
|
||||
assert(!ao->driver->write);
|
||||
|
||||
int pos = read_buffer(ao, data, samples, &(bool){0}, pad_silence);
|
||||
|
||||
if (pos > 0)
|
||||
p->end_time_ns = out_time_ns;
|
||||
|
||||
if (pos < samples && p->playing && !p->paused) {
|
||||
p->playing = false;
|
||||
ao->wakeup_cb(ao->wakeup_ctx);
|
||||
// For ao_drain().
|
||||
mp_cond_broadcast(&p->wakeup);
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
// Read the given amount of samples in the user-provided data buffer. Returns
|
||||
// the number of samples copied. If there is not enough data (buffer underrun
|
||||
// or EOF), return the number of samples that could be copied, and fill the
|
||||
// rest of the user-provided buffer with silence.
|
||||
// This basically assumes that the audio device doesn't care about underruns.
|
||||
// If this is called in paused mode, it will always return 0.
|
||||
// The caller should set out_time_ns to the expected delay until the last sample
|
||||
// reaches the speakers, in nanoseconds, using mp_time_ns() as reference.
|
||||
int ao_read_data(struct ao *ao, void **data, int samples, int64_t out_time_ns)
|
||||
// The caller should set out_time_us to the expected delay until the last sample
|
||||
// reaches the speakers, in microseconds, using mp_time_us() as reference.
|
||||
int ao_read_data(struct ao *ao, void **data, int samples, int64_t out_time_us)
|
||||
{
|
||||
struct buffer_state *p = ao->buffer_state;
|
||||
assert(!ao->driver->write);
|
||||
|
||||
mp_mutex_lock(&p->lock);
|
||||
pthread_mutex_lock(&p->lock);
|
||||
|
||||
int pos = ao_read_data_unlocked(ao, data, samples, out_time_ns, true);
|
||||
int pos = read_buffer(ao, data, samples, &(bool){0});
|
||||
|
||||
mp_mutex_unlock(&p->lock);
|
||||
if (pos > 0)
|
||||
p->end_time_us = out_time_us;
|
||||
|
||||
return pos;
|
||||
}
|
||||
if (pos < samples && p->playing && !p->paused) {
|
||||
p->playing = false;
|
||||
// For ao_drain().
|
||||
pthread_cond_broadcast(&p->wakeup);
|
||||
}
|
||||
|
||||
// Like ao_read_data() but does not block and also may return partial data.
|
||||
// Callers have to check the return value.
|
||||
int ao_read_data_nonblocking(struct ao *ao, void **data, int samples, int64_t out_time_ns)
|
||||
{
|
||||
struct buffer_state *p = ao->buffer_state;
|
||||
|
||||
if (mp_mutex_trylock(&p->lock))
|
||||
return 0;
|
||||
|
||||
int pos = ao_read_data_unlocked(ao, data, samples, out_time_ns, false);
|
||||
|
||||
mp_mutex_unlock(&p->lock);
|
||||
pthread_mutex_unlock(&p->lock);
|
||||
|
||||
return pos;
|
||||
}
|
||||
@@ -234,13 +205,13 @@ int ao_read_data_nonblocking(struct ao *ao, void **data, int samples, int64_t ou
|
||||
// Same as ao_read_data(), but convert data according to *fmt.
|
||||
// fmt->src_fmt and fmt->channels must be the same as the AO parameters.
|
||||
int ao_read_data_converted(struct ao *ao, struct ao_convert_fmt *fmt,
|
||||
void **data, int samples, int64_t out_time_ns)
|
||||
void **data, int samples, int64_t out_time_us)
|
||||
{
|
||||
struct buffer_state *p = ao->buffer_state;
|
||||
void *ndata[MP_NUM_CHANNELS] = {0};
|
||||
|
||||
if (!ao_need_conversion(fmt))
|
||||
return ao_read_data(ao, data, samples, out_time_ns);
|
||||
return ao_read_data(ao, data, samples, out_time_us);
|
||||
|
||||
assert(ao->format == fmt->src_fmt);
|
||||
assert(ao->channels.num == fmt->channels);
|
||||
@@ -260,7 +231,7 @@ int ao_read_data_converted(struct ao *ao, struct ao_convert_fmt *fmt,
|
||||
for (int n = 0; n < planes; n++)
|
||||
ndata[n] = p->convert_buffer + n * src_plane_size;
|
||||
|
||||
int res = ao_read_data(ao, ndata, samples, out_time_ns);
|
||||
int res = ao_read_data(ao, ndata, samples, out_time_us);
|
||||
|
||||
ao_convert_inplace(fmt, ndata, samples);
|
||||
for (int n = 0; n < planes; n++)
|
||||
@@ -276,12 +247,12 @@ int ao_control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
if (ao->driver->control) {
|
||||
// Only need to lock in push mode.
|
||||
if (ao->driver->write)
|
||||
mp_mutex_lock(&p->lock);
|
||||
pthread_mutex_lock(&p->lock);
|
||||
|
||||
r = ao->driver->control(ao, cmd, arg);
|
||||
|
||||
if (ao->driver->write)
|
||||
mp_mutex_unlock(&p->lock);
|
||||
pthread_mutex_unlock(&p->lock);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
@@ -290,7 +261,7 @@ double ao_get_delay(struct ao *ao)
|
||||
{
|
||||
struct buffer_state *p = ao->buffer_state;
|
||||
|
||||
mp_mutex_lock(&p->lock);
|
||||
pthread_mutex_lock(&p->lock);
|
||||
|
||||
double driver_delay;
|
||||
if (ao->driver->write) {
|
||||
@@ -298,16 +269,16 @@ double ao_get_delay(struct ao *ao)
|
||||
get_dev_state(ao, &state);
|
||||
driver_delay = state.delay;
|
||||
} else {
|
||||
int64_t end = p->end_time_ns;
|
||||
int64_t now = mp_time_ns();
|
||||
driver_delay = MPMAX(0, MP_TIME_NS_TO_S(end - now));
|
||||
int64_t end = p->end_time_us;
|
||||
int64_t now = mp_time_us();
|
||||
driver_delay = MPMAX(0, (end - now) / (1000.0 * 1000.0));
|
||||
}
|
||||
|
||||
int pending = mp_async_queue_get_samples(p->queue);
|
||||
if (p->pending)
|
||||
pending += mp_aframe_get_size(p->pending);
|
||||
|
||||
mp_mutex_unlock(&p->lock);
|
||||
pthread_mutex_unlock(&p->lock);
|
||||
return driver_delay + pending / (double)ao->samplerate;
|
||||
}
|
||||
|
||||
@@ -318,7 +289,7 @@ void ao_reset(struct ao *ao)
|
||||
bool wakeup = false;
|
||||
bool do_reset = false;
|
||||
|
||||
mp_mutex_lock(&p->lock);
|
||||
pthread_mutex_lock(&p->lock);
|
||||
|
||||
TA_FREEP(&p->pending);
|
||||
mp_async_queue_reset(p->queue);
|
||||
@@ -339,9 +310,9 @@ void ao_reset(struct ao *ao)
|
||||
p->playing = false;
|
||||
p->recover_pause = false;
|
||||
p->hw_paused = false;
|
||||
p->end_time_ns = 0;
|
||||
p->end_time_us = 0;
|
||||
|
||||
mp_mutex_unlock(&p->lock);
|
||||
pthread_mutex_unlock(&p->lock);
|
||||
|
||||
if (do_reset)
|
||||
ao->driver->reset(ao);
|
||||
@@ -359,7 +330,7 @@ void ao_start(struct ao *ao)
|
||||
struct buffer_state *p = ao->buffer_state;
|
||||
bool do_start = false;
|
||||
|
||||
mp_mutex_lock(&p->lock);
|
||||
pthread_mutex_lock(&p->lock);
|
||||
|
||||
p->playing = true;
|
||||
|
||||
@@ -368,7 +339,7 @@ void ao_start(struct ao *ao)
|
||||
do_start = true;
|
||||
}
|
||||
|
||||
mp_mutex_unlock(&p->lock);
|
||||
pthread_mutex_unlock(&p->lock);
|
||||
|
||||
// Pull AOs might call ao_read_data() so do this outside the lock.
|
||||
if (do_start)
|
||||
@@ -377,20 +348,15 @@ void ao_start(struct ao *ao)
|
||||
ao_wakeup_playthread(ao);
|
||||
}
|
||||
|
||||
void ao_set_paused(struct ao *ao, bool paused, bool eof)
|
||||
void ao_set_paused(struct ao *ao, bool paused)
|
||||
{
|
||||
struct buffer_state *p = ao->buffer_state;
|
||||
bool wakeup = false;
|
||||
bool do_reset = false, do_start = false;
|
||||
|
||||
// If we are going to pause on eof and ao is still playing,
|
||||
// be sure to drain the ao first for gapless.
|
||||
if (eof && paused && ao_is_playing(ao))
|
||||
ao_drain(ao);
|
||||
pthread_mutex_lock(&p->lock);
|
||||
|
||||
mp_mutex_lock(&p->lock);
|
||||
|
||||
if ((p->playing || !ao->driver->write) && !p->paused && paused) {
|
||||
if (p->playing && !p->paused && paused) {
|
||||
if (p->streaming && !ao->stream_silence) {
|
||||
if (ao->driver->write) {
|
||||
if (!p->recover_pause)
|
||||
@@ -423,7 +389,7 @@ void ao_set_paused(struct ao *ao, bool paused, bool eof)
|
||||
}
|
||||
p->paused = paused;
|
||||
|
||||
mp_mutex_unlock(&p->lock);
|
||||
pthread_mutex_unlock(&p->lock);
|
||||
|
||||
if (do_reset)
|
||||
ao->driver->reset(ao);
|
||||
@@ -442,9 +408,9 @@ bool ao_is_playing(struct ao *ao)
|
||||
{
|
||||
struct buffer_state *p = ao->buffer_state;
|
||||
|
||||
mp_mutex_lock(&p->lock);
|
||||
pthread_mutex_lock(&p->lock);
|
||||
bool playing = p->playing;
|
||||
mp_mutex_unlock(&p->lock);
|
||||
pthread_mutex_unlock(&p->lock);
|
||||
|
||||
return playing;
|
||||
}
|
||||
@@ -454,31 +420,30 @@ void ao_drain(struct ao *ao)
|
||||
{
|
||||
struct buffer_state *p = ao->buffer_state;
|
||||
|
||||
mp_mutex_lock(&p->lock);
|
||||
pthread_mutex_lock(&p->lock);
|
||||
while (!p->paused && p->playing) {
|
||||
mp_mutex_unlock(&p->lock);
|
||||
pthread_mutex_unlock(&p->lock);
|
||||
double delay = ao_get_delay(ao);
|
||||
mp_mutex_lock(&p->lock);
|
||||
pthread_mutex_lock(&p->lock);
|
||||
|
||||
// Limit to buffer + arbitrary ~250ms max. waiting for robustness.
|
||||
delay += mp_async_queue_get_samples(p->queue) / (double)ao->samplerate;
|
||||
struct timespec ts = mp_rel_time_to_timespec(MPMAX(delay, 0) + 0.25);
|
||||
|
||||
// Wait for EOF signal from AO.
|
||||
if (mp_cond_timedwait(&p->wakeup, &p->lock,
|
||||
MP_TIME_S_TO_NS(MPMAX(delay, 0) + 0.25)))
|
||||
{
|
||||
if (pthread_cond_timedwait(&p->wakeup, &p->lock, &ts)) {
|
||||
MP_VERBOSE(ao, "drain timeout\n");
|
||||
break;
|
||||
}
|
||||
|
||||
if (!p->playing && mp_async_queue_get_samples(p->queue)) {
|
||||
MP_WARN(ao, "underrun during draining\n");
|
||||
mp_mutex_unlock(&p->lock);
|
||||
pthread_mutex_unlock(&p->lock);
|
||||
ao_start(ao);
|
||||
mp_mutex_lock(&p->lock);
|
||||
pthread_mutex_lock(&p->lock);
|
||||
}
|
||||
}
|
||||
mp_mutex_unlock(&p->lock);
|
||||
pthread_mutex_unlock(&p->lock);
|
||||
|
||||
ao_reset(ao);
|
||||
}
|
||||
@@ -493,32 +458,30 @@ void ao_uninit(struct ao *ao)
|
||||
{
|
||||
struct buffer_state *p = ao->buffer_state;
|
||||
|
||||
if (p && p->thread_valid) {
|
||||
mp_mutex_lock(&p->pt_lock);
|
||||
if (p->thread_valid) {
|
||||
pthread_mutex_lock(&p->pt_lock);
|
||||
p->terminate = true;
|
||||
mp_cond_broadcast(&p->pt_wakeup);
|
||||
mp_mutex_unlock(&p->pt_lock);
|
||||
pthread_cond_broadcast(&p->pt_wakeup);
|
||||
pthread_mutex_unlock(&p->pt_lock);
|
||||
|
||||
mp_thread_join(p->thread);
|
||||
pthread_join(p->thread, NULL);
|
||||
p->thread_valid = false;
|
||||
}
|
||||
|
||||
if (ao->driver_initialized)
|
||||
ao->driver->uninit(ao);
|
||||
|
||||
if (p) {
|
||||
talloc_free(p->filter_root);
|
||||
talloc_free(p->queue);
|
||||
talloc_free(p->pending);
|
||||
talloc_free(p->convert_buffer);
|
||||
talloc_free(p->temp_buf);
|
||||
talloc_free(p->filter_root);
|
||||
talloc_free(p->queue);
|
||||
talloc_free(p->pending);
|
||||
talloc_free(p->convert_buffer);
|
||||
talloc_free(p->temp_buf);
|
||||
|
||||
mp_cond_destroy(&p->wakeup);
|
||||
mp_mutex_destroy(&p->lock);
|
||||
pthread_cond_destroy(&p->wakeup);
|
||||
pthread_mutex_destroy(&p->lock);
|
||||
|
||||
mp_cond_destroy(&p->pt_wakeup);
|
||||
mp_mutex_destroy(&p->pt_lock);
|
||||
}
|
||||
pthread_cond_destroy(&p->pt_wakeup);
|
||||
pthread_mutex_destroy(&p->pt_lock);
|
||||
|
||||
talloc_free(ao);
|
||||
}
|
||||
@@ -538,11 +501,11 @@ bool init_buffer_post(struct ao *ao)
|
||||
assert(ao->driver->get_state);
|
||||
}
|
||||
|
||||
mp_mutex_init(&p->lock);
|
||||
mp_cond_init(&p->wakeup);
|
||||
pthread_mutex_init(&p->lock, NULL);
|
||||
pthread_cond_init(&p->wakeup, NULL);
|
||||
|
||||
mp_mutex_init(&p->pt_lock);
|
||||
mp_cond_init(&p->pt_wakeup);
|
||||
pthread_mutex_init(&p->pt_lock, NULL);
|
||||
pthread_cond_init(&p->pt_wakeup, NULL);
|
||||
|
||||
p->queue = mp_async_queue_create();
|
||||
p->filter_root = mp_filter_create_root(ao->global);
|
||||
@@ -561,7 +524,7 @@ bool init_buffer_post(struct ao *ao)
|
||||
mp_filter_graph_set_wakeup_cb(p->filter_root, wakeup_filters, ao);
|
||||
|
||||
p->thread_valid = true;
|
||||
if (mp_thread_create(&p->thread, playthread, ao)) {
|
||||
if (pthread_create(&p->thread, NULL, playthread, ao)) {
|
||||
p->thread_valid = false;
|
||||
return false;
|
||||
}
|
||||
@@ -626,7 +589,7 @@ static bool ao_play_data(struct ao *ao)
|
||||
bool got_eof = false;
|
||||
if (ao->driver->write_frames) {
|
||||
TA_FREEP(&p->pending);
|
||||
samples = read_buffer(ao, NULL, 1, &got_eof, false);
|
||||
samples = read_buffer(ao, NULL, 1, &got_eof);
|
||||
planes = (void **)&p->pending;
|
||||
} else {
|
||||
if (!realloc_buf(ao, space)) {
|
||||
@@ -643,7 +606,7 @@ static bool ao_play_data(struct ao *ao)
|
||||
}
|
||||
|
||||
if (!samples) {
|
||||
samples = read_buffer(ao, planes, space, &got_eof, true);
|
||||
samples = read_buffer(ao, planes, space, &got_eof);
|
||||
if (p->paused || (ao->stream_silence && !p->playing))
|
||||
samples = space; // read_buffer() sets remainder to silent
|
||||
}
|
||||
@@ -680,17 +643,17 @@ eof:
|
||||
}
|
||||
ao->wakeup_cb(ao->wakeup_ctx);
|
||||
// For ao_drain().
|
||||
mp_cond_broadcast(&p->wakeup);
|
||||
pthread_cond_broadcast(&p->wakeup);
|
||||
return true;
|
||||
}
|
||||
|
||||
static MP_THREAD_VOID playthread(void *arg)
|
||||
static void *playthread(void *arg)
|
||||
{
|
||||
struct ao *ao = arg;
|
||||
struct buffer_state *p = ao->buffer_state;
|
||||
mp_thread_set_name("ao");
|
||||
mpthread_set_name("ao");
|
||||
while (1) {
|
||||
mp_mutex_lock(&p->lock);
|
||||
pthread_mutex_lock(&p->lock);
|
||||
|
||||
bool retry = false;
|
||||
if (!ao->driver->initially_blocked || p->initial_unblocked)
|
||||
@@ -698,39 +661,40 @@ static MP_THREAD_VOID playthread(void *arg)
|
||||
|
||||
// Wait until the device wants us to write more data to it.
|
||||
// Fallback to guessing.
|
||||
int64_t timeout = INT64_MAX;
|
||||
double timeout = INFINITY;
|
||||
if (p->streaming && !retry && (!p->paused || ao->stream_silence)) {
|
||||
// Wake up again if half of the audio buffer has been played.
|
||||
// Since audio could play at a faster or slower pace, wake up twice
|
||||
// as often as ideally needed.
|
||||
timeout = MP_TIME_S_TO_NS(ao->device_buffer / (double)ao->samplerate * 0.25);
|
||||
timeout = ao->device_buffer / (double)ao->samplerate * 0.25;
|
||||
}
|
||||
|
||||
mp_mutex_unlock(&p->lock);
|
||||
pthread_mutex_unlock(&p->lock);
|
||||
|
||||
mp_mutex_lock(&p->pt_lock);
|
||||
pthread_mutex_lock(&p->pt_lock);
|
||||
if (p->terminate) {
|
||||
mp_mutex_unlock(&p->pt_lock);
|
||||
pthread_mutex_unlock(&p->pt_lock);
|
||||
break;
|
||||
}
|
||||
if (!p->need_wakeup && !retry) {
|
||||
MP_STATS(ao, "start audio wait");
|
||||
mp_cond_timedwait(&p->pt_wakeup, &p->pt_lock, timeout);
|
||||
struct timespec ts = mp_rel_time_to_timespec(timeout);
|
||||
pthread_cond_timedwait(&p->pt_wakeup, &p->pt_lock, &ts);
|
||||
MP_STATS(ao, "end audio wait");
|
||||
}
|
||||
p->need_wakeup = false;
|
||||
mp_mutex_unlock(&p->pt_lock);
|
||||
pthread_mutex_unlock(&p->pt_lock);
|
||||
}
|
||||
MP_THREAD_RETURN();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void ao_unblock(struct ao *ao)
|
||||
{
|
||||
if (ao->driver->write) {
|
||||
struct buffer_state *p = ao->buffer_state;
|
||||
mp_mutex_lock(&p->lock);
|
||||
pthread_mutex_lock(&p->lock);
|
||||
p->initial_unblocked = true;
|
||||
mp_mutex_unlock(&p->lock);
|
||||
pthread_mutex_unlock(&p->lock);
|
||||
ao_wakeup_playthread(ao);
|
||||
}
|
||||
}
|
||||
@@ -18,9 +18,10 @@
|
||||
#ifndef MP_AO_INTERNAL_H_
|
||||
#define MP_AO_INTERNAL_H_
|
||||
|
||||
#include <stdatomic.h>
|
||||
#include <stdbool.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include "osdep/atomic.h"
|
||||
#include "audio/out/ao.h"
|
||||
|
||||
/* global data used by ao.c and ao drivers */
|
||||
@@ -62,7 +63,7 @@ struct ao {
|
||||
atomic_uint events_;
|
||||
|
||||
// Float gain multiplicator
|
||||
_Atomic float gain;
|
||||
mp_atomic_float gain;
|
||||
|
||||
int buffer;
|
||||
double def_buffer;
|
||||
@@ -199,9 +200,7 @@ struct ao_driver {
|
||||
|
||||
// These functions can be called by AOs.
|
||||
|
||||
int ao_read_data(struct ao *ao, void **data, int samples, int64_t out_time_ns);
|
||||
MP_WARN_UNUSED_RESULT
|
||||
int ao_read_data_nonblocking(struct ao *ao, void **data, int samples, int64_t out_time_ns);
|
||||
int ao_read_data(struct ao *ao, void **data, int samples, int64_t out_time_us);
|
||||
|
||||
bool ao_chmap_sel_adjust(struct ao *ao, const struct mp_chmap_sel *s,
|
||||
struct mp_chmap *map);
|
||||
@@ -232,6 +231,6 @@ void ao_convert_inplace(struct ao_convert_fmt *fmt, void **data, int num_samples
|
||||
void ao_wakeup_playthread(struct ao *ao);
|
||||
|
||||
int ao_read_data_converted(struct ao *ao, struct ao_convert_fmt *fmt,
|
||||
void **data, int samples, int64_t out_time_ns);
|
||||
void **data, int samples, int64_t out_time_us);
|
||||
|
||||
#endif
|
||||
Executable
+56
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# This script simply downloads waf to the current directory
|
||||
|
||||
import os, sys, stat, hashlib, subprocess
|
||||
from urllib.request import urlopen, URLError
|
||||
|
||||
WAFRELEASE = "waf-2.0.20"
|
||||
WAFURLS = ["https://waf.io/" + WAFRELEASE,
|
||||
"http://www.freehackers.org/~tnagy/release/" + WAFRELEASE]
|
||||
SHA256HASH = "bf971e98edc2414968a262c6aa6b88541a26c3cd248689c89f4c57370955ee7f"
|
||||
|
||||
if os.path.exists("waf"):
|
||||
wafver = subprocess.check_output([sys.executable, './waf', '--version']).decode()
|
||||
if WAFRELEASE.split('-')[1] == wafver.split(' ')[1]:
|
||||
print("Found 'waf', skipping download.")
|
||||
sys.exit(0)
|
||||
|
||||
if "--no-download" in sys.argv[1:]:
|
||||
print("Did not find {} and no download was requested.".format(WAFRELEASE))
|
||||
sys.exit(1)
|
||||
|
||||
waf = None
|
||||
|
||||
for WAFURL in WAFURLS:
|
||||
try:
|
||||
print("Downloading {}...".format(WAFURL))
|
||||
waf = urlopen(WAFURL).read()
|
||||
break
|
||||
except URLError as err:
|
||||
print("Download failed! ({})".format(err))
|
||||
|
||||
if not waf:
|
||||
print("Could not download {}.".format(WAFRELEASE))
|
||||
|
||||
sys.exit(1)
|
||||
|
||||
if SHA256HASH == hashlib.sha256(waf).hexdigest():
|
||||
# Upstream waf is not changing the default interpreter during
|
||||
# 2.0.x line due to compatibility reasons apparently. So manually
|
||||
# convert it to use python3 (the script works with both).
|
||||
expected = b"#!/usr/bin/env python\n"
|
||||
assert waf.startswith(expected)
|
||||
waf = b"#!/usr/bin/env python3\n" + waf[len(expected):]
|
||||
with open("waf", "wb") as wf:
|
||||
wf.write(waf)
|
||||
|
||||
os.chmod("waf", os.stat("waf").st_mode | stat.S_IXUSR)
|
||||
print("Checksum verified.")
|
||||
else:
|
||||
print("The checksum of the downloaded file does not match!")
|
||||
print(" - got: {}".format(hashlib.sha256(waf).hexdigest()))
|
||||
print(" - expected: {}".format(SHA256HASH))
|
||||
print("Please download and verify the file manually.")
|
||||
|
||||
sys.exit(1)
|
||||
Loaded 100 of 583 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user