Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7214f1f076 | ||
|
|
789e04e9a0 | ||
|
|
2ae812ec6d | ||
|
|
417fe22f67 | ||
|
|
b889623768 | ||
|
|
db0af67e7e |
No files matched your search
@@ -11,13 +11,13 @@ Releases are listed here: https://github.com/mpv-player/mpv/releases
|
||||
|
||||
### Log file
|
||||
|
||||
Make a log file made with -v -v or --log-file=output.txt, paste it to
|
||||
https://0x0.st/ or attach it to the github issue, and replace this text with a
|
||||
link to it. If the issue is a build failure, upload build/config.log instead.
|
||||
Make a log file made with -v or --log-file=output.txt, paste it to
|
||||
http://sprunge.us or a similar site, and replace this text with a link to it.
|
||||
|
||||
If this is a normal runtime bug, and no log is provided, the issue will be
|
||||
closed for ignoring the issue template. This is because analyzing a bug without
|
||||
a log file is harder than necessary. Low quality bug reports are noise.
|
||||
Providing a log file is strongly encouraged. It is very helpful for reproducing
|
||||
bugs or getting important technical context. Without a log it might not be
|
||||
possible to analyze and fix certain bugs, and your bug report will be closed
|
||||
or ignored.
|
||||
|
||||
### Sample files
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
Read this before you submit this pull request:
|
||||
https://github.com/mpv-player/mpv/blob/master/DOCS/contribute.md
|
||||
Ideally, you shouldn't need enter any text here, and your commit messages should
|
||||
explain your changes sufficiently (especially why they are needed). Read
|
||||
https://github.com/mpv-player/mpv/blob/master/DOCS/contribute.md for coding
|
||||
style and development conventions. Remove this text block, but if you haven't
|
||||
agreed to it before, leave the following sentence in place:
|
||||
|
||||
Reading this link and following the rules will get your pull request reviewed
|
||||
and merged faster. Nobody wants lazy pull requests.
|
||||
I agree that my changes can be relicensed to LGPL 2.1 or later.
|
||||
+23
-49
@@ -1,56 +1,43 @@
|
||||
language: c
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- os: osx
|
||||
osx_image: xcode11
|
||||
compiler: clang
|
||||
- os: osx
|
||||
osx_image: xcode10.1
|
||||
compiler: clang
|
||||
- os: osx
|
||||
osx_image: xcode9.2
|
||||
compiler: clang
|
||||
- os: linux
|
||||
compiler: gcc
|
||||
env: CONTAINER=registry.cirno.systems/kiwi/containers/mpv-ci:stable-deps CI_SCRIPT=ci/build-tumbleweed.sh
|
||||
- os: linux
|
||||
compiler: clang
|
||||
env: CONTAINER=registry.cirno.systems/kiwi/containers/mpv-ci:stable-deps CI_SCRIPT=ci/build-tumbleweed.sh
|
||||
- os: linux
|
||||
env: CONTAINER=registry.cirno.systems/kiwi/containers/mpv-ci-mingw64:i686 CI_SCRIPT=ci/build-mingw64.sh TARGET=i686-w64-mingw32
|
||||
- os: linux
|
||||
env: CONTAINER=registry.cirno.systems/kiwi/containers/mpv-ci-mingw64:x86_64 CI_SCRIPT=ci/build-mingw64.sh TARGET=x86_64-w64-mingw32
|
||||
|
||||
dist: bionic
|
||||
services:
|
||||
- docker
|
||||
|
||||
sudo: required
|
||||
|
||||
os:
|
||||
- linux
|
||||
#- osx
|
||||
env:
|
||||
matrix:
|
||||
- LIBAV=ffmpeg-git
|
||||
- LIBAV=libav-git
|
||||
global:
|
||||
# Coverity token
|
||||
- secure: "H21mSRlMhk4BKS0xHZvCFGJxteCP0hRVUxTuNfM2Z9HBsyutuLEYMtViLO86VtM+Tqla3xXPzUdS4ozLwI72Ax/5ZUDXACROj73yW6QhFB5D6rLut12+FjqC7M33Qv2hl0xwgNBmR5dsm1ToP37+Wn+ecJQNvN8fkTXF+HVzOEw="
|
||||
# Travis token for mpv.io
|
||||
- secure: "nlTVLJK6kRhtXvhKCoJ3YdFGHuKaq/eHowfPw25hqRWuBOZd+HjHY5KIYjV7SxuKFDpJE4GpNcvA3Q31nsqomxpkLYgrwjg6TSazN7ZP+x85ZgV1QGFebrPfGm2n5UR5CAPAwFoeF3pZheLi4bajVzwq1fWW+x3grS188P9OZso="
|
||||
compiler:
|
||||
- clang
|
||||
- gcc
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- ci
|
||||
- coverity_scan
|
||||
- /release\/.*$/
|
||||
|
||||
before_install:
|
||||
- if [ "$TRAVIS_COMPILER" = "clang" ]; then export CXX="clang++"; fi
|
||||
- if [ "$TRAVIS_COMPILER" = "gcc" ]; then export CXX="g++"; fi
|
||||
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then docker pull $CONTAINER; fi
|
||||
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then "$TRAVIS_BUILD_DIR/ci/get_ffmpeg.sh"; fi
|
||||
matrix:
|
||||
exclude:
|
||||
- os: osx
|
||||
env: LIBAV=libav-git
|
||||
- os: osx
|
||||
env: LIBAV=ffmpeg-git
|
||||
- os: osx
|
||||
compiler: gcc
|
||||
- os: linux
|
||||
compiler: clang
|
||||
|
||||
before_install: TOOLS/travis-deps libass-stable $LIBAV
|
||||
script:
|
||||
- ./bootstrap.py
|
||||
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then docker run --env CC --env TARGET -v $TRAVIS_BUILD_DIR:/build $CONTAINER /bin/sh -c "cd /build && $CI_SCRIPT"; fi
|
||||
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then ./ci/build-macos.sh; fi
|
||||
- ./waf configure
|
||||
- ./waf build
|
||||
after_failure: cat ./build/config.log
|
||||
after_script: TOOLS/travis-rebuild-website
|
||||
|
||||
@@ -71,16 +58,3 @@ addons:
|
||||
build_command_prepend: "./bootstrap.py && ./waf configure"
|
||||
build_command: "./waf build"
|
||||
branch_pattern: coverity_scan
|
||||
homebrew:
|
||||
packages:
|
||||
- autoconf
|
||||
- automake
|
||||
- freetype
|
||||
- fribidi
|
||||
- libass
|
||||
- libtool
|
||||
- little-cms2
|
||||
- luajit
|
||||
- nasm
|
||||
- pkg-config
|
||||
update: true
|
||||
@@ -16,13 +16,11 @@ licensed as LGPLv2.1+. Also see the list of files with specific licenses below
|
||||
All new contributions must be LGPLv2.1+ licensed. Using a more liberal license
|
||||
compatible to LGPLv2.1+ is also ok.
|
||||
|
||||
Changes done to GPL code must come with the implicit/explicit agreement that the
|
||||
If changes are done on GPL code, must come with the implicit agreement that the
|
||||
project can relicense the changes to LGPLv2.1+ at a later point without asking
|
||||
the contributor. This is a safeguard for making potential relicensing of
|
||||
remaining GPL code to LGPLv2.1+ easier.
|
||||
|
||||
See DOCS/contribute.md for binding rules wrt. licensing for contributions.
|
||||
|
||||
For information about authors and contributors, consult the git log, which
|
||||
contains the complete SVN and CVS history as well.
|
||||
|
||||
@@ -36,6 +34,8 @@ a LGPL mode to mpv, MPlayer code had to be relicensed from GPLv2+ to LGPLv2.1+
|
||||
by asking the MPlayer authors for permission. Since permission could not be
|
||||
obtained from everyone, LGPL mode disables the following features, some of
|
||||
them quite central:
|
||||
- no audio filtering, which breaks: --af, pitch correction, fine control over
|
||||
downmix/upmix/resampling behavior
|
||||
- Linux X11 video output
|
||||
- BSD audio output via OSS
|
||||
- NVIDIA/Linux hardware decoding (vdpau, although nvdec usually works)
|
||||
@@ -47,13 +47,28 @@ at all.
|
||||
|
||||
The following files are still GPL only (--enable-lgpl disables them):
|
||||
|
||||
audio/filter/* will be replaced with new filter chain
|
||||
audio/filter/af_format.c mostly LGPL (except af glue code)
|
||||
audio/filter/af_lavc3enc.c as above
|
||||
audio/filter/af_lavfi.c as above
|
||||
audio/filter/af_scaletempo.c as above
|
||||
audio/filter/af_rubberband.c as above
|
||||
audio/out/ao_jack.c will stay GPL
|
||||
audio/out/ao_oss.c will stay GPL
|
||||
audio/audio.* needed by af code only
|
||||
demux/demux_tv.c will stay GPL
|
||||
stream/ai_* will stay GPL (TV code)
|
||||
stream/audio_in.* will stay GPL (TV code)
|
||||
stream/dvb* must stay GPL
|
||||
stream/frequencies.* must stay GPL
|
||||
stream/stream_cdda.c unknown
|
||||
stream/stream_dvb.* must stay GPL
|
||||
stream/stream_dvd.c unknown
|
||||
stream/stream_dvd_common.* unknown
|
||||
stream/stream_dvdnav.c unknown
|
||||
stream/stream_smb.c will stay GPLv3
|
||||
stream/stream_tv.c will stay GPL
|
||||
stream/tv* will stay GPL
|
||||
video/out/vo_caca.c unknown
|
||||
video/out/vo_direct3d.c unknown
|
||||
video/out/vo_vaapi.c probably impossible (some company's code)
|
||||
@@ -70,6 +85,11 @@ The following files are still GPL only (--enable-lgpl disables them):
|
||||
mpv.desktop unknown license, probably GPLv2+
|
||||
etc/restore-old-bindings.conf unknown license, probably GPLv2+
|
||||
|
||||
None of the cases listed above affect the final binary if it's built as
|
||||
The following files contain some optional GPL code (--enable-lgpl disables it):
|
||||
|
||||
options/parse_commandline.c dvd:// expansion
|
||||
player/audio.c libaf glue code
|
||||
|
||||
None of the exceptions listed above affect the final binary if it's built as
|
||||
LGPL. Linked libraries still can affect the final license (for example if
|
||||
FFmpeg was built as GPL).
|
||||
@@ -16,7 +16,7 @@ The version number is the same as used for MPV_CLIENT_API_VERSION (see
|
||||
``client.h`` how to convert between major/minor version numbers and the flat
|
||||
32 bit integer).
|
||||
|
||||
Also, read the section ``Compatibility`` in ``client.h``, and compatibility.rst.
|
||||
Also, read the section ``Compatibility`` in ``client.h``.
|
||||
|
||||
Options, commands, properties
|
||||
=============================
|
||||
@@ -32,83 +32,7 @@ API changes
|
||||
|
||||
::
|
||||
|
||||
--- mpv 0.31.0 ---
|
||||
1.107 - Deprecate MPV_EVENT_TICK
|
||||
|
||||
--- mpv 0.30.0 ---
|
||||
1.106 - Add cancel_fn to mpv_stream_cb_info
|
||||
1.105 - Fix deadlock problems with MPV_RENDER_PARAM_ADVANCED_CONTROL and if
|
||||
the "vd-lavc-dr" option is enabled (which it is by default).
|
||||
There were no actual API changes.
|
||||
API users on older API versions and mpv releases should set
|
||||
"vd-lavc-dr" to "no" to avoid these issues.
|
||||
API users must still adhere to the tricky rules documented in render.h
|
||||
to avoid other deadlocks.
|
||||
1.104 - Deprecate struct mpv_opengl_drm_params. Replaced by mpv_opengl_drm_params_v2
|
||||
- Deprecate MPV_RENDER_PARAM_DRM_DISPLAY. Replaced by MPV_RENDER_PARAM_DRM_DISPLAY_V2.
|
||||
1.103 - redo handling of async commands
|
||||
- add mpv_event_command and make it possible to return values from
|
||||
commands issued with mpv_command_async() or mpv_command_node_async()
|
||||
- add mpv_abort_async_command()
|
||||
1.102 - rename struct mpv_opengl_drm_osd_size to mpv_opengl_drm_draw_surface_size
|
||||
- rename MPV_RENDER_PARAM_DRM_OSD_SIZE to MPV_RENDER_PARAM_DRM_DRAW_SURFACE_SIZE
|
||||
|
||||
--- mpv 0.29.0 ---
|
||||
1.101 - add MPV_RENDER_PARAM_ADVANCED_CONTROL and related API
|
||||
- add MPV_RENDER_PARAM_NEXT_FRAME_INFO and related symbols
|
||||
- add MPV_RENDER_PARAM_BLOCK_FOR_TARGET_TIME
|
||||
- add MPV_RENDER_PARAM_SKIP_RENDERING
|
||||
- add mpv_render_context_get_info()
|
||||
1.100 - bump API number to avoid confusion with mpv release versions
|
||||
- actually apply the GL_MP_MPGetNativeDisplay change for the new render
|
||||
API. This also means compatibility for anything but x11 and wayland
|
||||
through the old opengl-cb GL_MP_MPGetNativeDisplay method is now
|
||||
unsupported.
|
||||
- deprecate mpv_get_wakeup_pipe(). It's complex, but easy to replace
|
||||
using normal API (just set a wakeup callback to a function which
|
||||
writes to a pipe).
|
||||
- add a 1st class hook API, which replaces the hacky mpv_command()
|
||||
based one. The old API is deprecated and will be removed soon. The
|
||||
old API was never meant to be stable, while the new API is.
|
||||
1.29 - the behavior of mpv_terminate_destroy() and mpv_detach_destroy()
|
||||
changes subtly (see documentation in the header file). In particular,
|
||||
mpv_detach_destroy() will not leave the player running in all
|
||||
situations anymore (it gets closer to refcounting).
|
||||
- rename mpv_detach_destroy() to mpv_destroy() (the old function will
|
||||
remain valid as deprecated alias)
|
||||
- add mpv_create_weak_client(), which makes use of above changes
|
||||
- MPV_EVENT_SHUTDOWN is now returned exactly once if a mpv_handle
|
||||
should terminate, instead of spamming the event queue with this event
|
||||
1.28 - deprecate the render opengl_cb API, and replace it with render.h
|
||||
and render_gl.h. The goal is allowing support for APIs other than
|
||||
OpenGL. The old API is emulated with the new API.
|
||||
Likewise, the "opengl-cb" VO is renamed to "libmpv".
|
||||
mpv_get_sub_api() is deprecated along the opengl_cb API.
|
||||
The new API is relatively similar, but not the same. The rough
|
||||
equivalents are:
|
||||
mpv_opengl_cb_init_gl => mpv_render_context_create
|
||||
mpv_opengl_cb_set_update_callback => mpv_render_context_set_update_callback
|
||||
mpv_opengl_cb_draw => mpv_render_context_render
|
||||
mpv_opengl_cb_report_flip => mpv_render_context_report_swap
|
||||
mpv_opengl_cb_uninit_gl => mpv_render_context_free
|
||||
The VO opengl-cb is also renamed to "libmpv".
|
||||
Also, the GL_MP_MPGetNativeDisplay pseudo extension is not used by the
|
||||
render API anymore, and the old opengl-cb API only handles the "x11"
|
||||
and "wl" names anymore. Support for everything else has been removed.
|
||||
The new render API uses proper API parameters, e.g. for X11 you pass
|
||||
MPV_RENDER_PARAM_X11_DISPLAY directly.
|
||||
- deprecate the qthelper.hpp header file. This provided some C++ helper
|
||||
utility functions for Qt with use of libmpv. There is no reason to
|
||||
keep this in the mpv git repository, nor to make it part of the libmpv
|
||||
API. If you're using this header, you can safely copy it into your
|
||||
project - it uses only libmpv public API. Alternatively, it could be
|
||||
maintained in a separate repository by interested parties.
|
||||
1.27 - make opengl-cb the default VO. This causes a subtle behavior change
|
||||
if the API user called mpv_opengl_cb_init_gl(), but does not set
|
||||
the "vo" option. Before, it would still have used another VO (like
|
||||
on the CLI, e.g. vo=gpu). Now it'll behave as if vo=opengl-cb was
|
||||
used.
|
||||
--- mpv 0.28.0 ---
|
||||
1.26 - remove glMPGetNativeDisplay("drm") support
|
||||
- add mpv_opengl_cb_window_pos and mpv_opengl_cb_drm_params and
|
||||
support via glMPGetNativeDisplay() for using it
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
CLI and API compatibility policy
|
||||
================================
|
||||
|
||||
Human users and API users rely on the mpv/libmpv/scripting/IPC interface not
|
||||
breaking their use case. On the other hand, active development occasionally
|
||||
requires breaking things, such as removing old options or changing options in
|
||||
a way that may break.
|
||||
|
||||
This document lists rules when, what, and how incompatible changes can be made.
|
||||
It's interesting both for mpv developers, who want to change user-visible parts,
|
||||
and mpv users, who want to know what is guaranteed to remain stable.
|
||||
|
||||
Any of the rules below may be overridden by statements in more specific
|
||||
documentation (for example, if the manpage says that a particular option may be
|
||||
removed any time, it means that, and the option probably won't even go through
|
||||
deprecation).
|
||||
|
||||
Additions
|
||||
---------
|
||||
|
||||
Additions are basically always allowed. API users etc. are supposed to deal with
|
||||
the possibility that for example new API functions are added. Some parts of the
|
||||
API may document how they are extended.
|
||||
|
||||
Options, commands, properties, events, hooks (command interface)
|
||||
----------------------------------------------------------------
|
||||
|
||||
All of these are important for interfacing both with end users and API users
|
||||
(which include Lua scripts, libmpv, and the JSON IPC). As such, they constitute
|
||||
a large part of the user interface and APIs.
|
||||
|
||||
All incompatible changes to this must be declared in interface-changes.rst.
|
||||
(This may also list compatible additions, but it's not a requirement.)
|
||||
|
||||
Degrees of importance and compatibility preservation
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Critical and central parts of the command interface have the strictest
|
||||
requirements. It may not be reasonable to break them, and other means to achieve
|
||||
some change have to be found. For example, the "seek" command is a bit of a
|
||||
mess, but since changing it would likely affect almost every user, it may be
|
||||
impossible to break at least the commonly used syntax. If changed anyway, there
|
||||
should be a deprecation period of at least 1 year, during which the command
|
||||
still works, and possibly a warning should remain even after this.
|
||||
|
||||
Important/often used parts must be deprecated for at least 2 releases before
|
||||
they can be broken. There must be at least 1 release where the deprecated
|
||||
functionality still works, and a replacement is available (if technically
|
||||
reasonable). For example, a feature deprecated in mpv 0.30.0 may be removed in
|
||||
mpv 0.32.0. Minor releases do not count towards this.
|
||||
|
||||
Less useful parts can be broken immediately, but must come with some sort of
|
||||
removal warning-
|
||||
|
||||
Parts for debugging and testing may be removed any time, potentially even
|
||||
without any sort of documentation.
|
||||
|
||||
Currently, the importance of a part is not documented and not even well-defined,
|
||||
which is probably a mistake.
|
||||
|
||||
Renaming or removing options
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Typically, renaming an option can be done in a compatible way with OPT_REPLACED.
|
||||
You may need to check whether the corresponding properties still work (including
|
||||
messy details like observing properties for changes).
|
||||
|
||||
OPT_REMOVED can be used to inform the user of alternatives or reasons for the
|
||||
removal, which is better than an option not found error. Likewise,
|
||||
m_option.deprecation_message should be set to something helpful.
|
||||
|
||||
Both OPT_REPLACED and OPT_REMOVED can remain in the code for a long time, since
|
||||
they're unintrusive and hopefully make incompatible changes less painful for
|
||||
users.
|
||||
|
||||
Scripting APIs
|
||||
--------------
|
||||
|
||||
This affects internal scripting APIs (currently Lua and JavaScript).
|
||||
|
||||
Vaguely the same rules as with the command interface apply. Since there is a
|
||||
large number of scripts, an effort should be made to provide compatibility
|
||||
for old scripts, but it does not need to be stronger than that of the command
|
||||
interface.
|
||||
|
||||
Undocumented parts of the scripting APIs are _not_ guaranteed for compatibility.
|
||||
This applies especially for internals. Languages like Lua do not have strict
|
||||
access control (nor does the mpv code try to emulate any), so if a script
|
||||
accesses private parts, and breaks on the next mpv release, it's not mpv's
|
||||
problem.
|
||||
|
||||
JSON IPC
|
||||
--------
|
||||
|
||||
The JSON IPC is a thin protocol wrapping the libmpv API and the command
|
||||
interface. Compatibility-wise, it's about the same as the scripting APIs.
|
||||
The JSON protocol commands should remain as compatible as possible, and it
|
||||
should probably accept the current way commands are delimited (line breaks)
|
||||
forever.
|
||||
|
||||
The protocol may accept non-standard JSON extensions, but only standard JSON
|
||||
(possibly with restrictions) is guaranteed for compatibility. Clients which want
|
||||
to remain compatible should not use any extensions.
|
||||
|
||||
CLI
|
||||
---
|
||||
|
||||
Things such as default key bindings do not necessarily require compatibility.
|
||||
However, the release notes should be extremely clear on changes to "important"
|
||||
key bindings. Bindings which restore the old behavior should be added to
|
||||
restore-old-bindings.conf.
|
||||
|
||||
Some option parsing is CLI-only and not available from libmpv or scripting. No
|
||||
compatibility guarantees come with them. However, the rules which mpv uses to
|
||||
distinguish between options and filenames must remain consistent (if the
|
||||
non-deprecated options syntax is used).
|
||||
|
||||
Terminal and log output
|
||||
-----------------------
|
||||
|
||||
There are no compatibility guarantees for the terminal output, or the text
|
||||
logged via ``MPV_EVENT_LOG_MESSAGE`` and similar APIs. In particular, scripts
|
||||
invoking mpv CLI are extremely discouraged from trying to parse text output,
|
||||
and should use other mechanisms such as the JSON IPC.
|
||||
|
||||
Protocols, filters, demuxers, etc.
|
||||
----------------------------------
|
||||
|
||||
Which of these are present is generally not guaranteed, and can even depend
|
||||
on compile time settings.
|
||||
|
||||
The filter list and their sub-options are considered part of the
|
||||
command-interface.
|
||||
|
||||
libmpv C API
|
||||
------------
|
||||
|
||||
The libmpv client API (such as ``<libmpv/client.h>``) mostly gives access to
|
||||
the command interface. The API itself (if looked at as a component separate
|
||||
from the command interface) is intended to be extremely stable.
|
||||
|
||||
All API changes are documented in client-api-changes.rst.
|
||||
|
||||
API compatibility
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
The API is *always* compatible. Incompatible changes are only allowed on major
|
||||
API version changes (see ``MPV_CLIENT_API_VERSION``). A major version change is
|
||||
an extremely rare event, which means usually no API symbols are ever removed.
|
||||
|
||||
Essentially removing API functions by making them always return an error, or
|
||||
making it do nothing is allowed in cases where it is unlikely to break most
|
||||
clients, but requires a deprecation period of 2 releases. (This has happened to
|
||||
``mpv_suspend()`` for example.)
|
||||
|
||||
API symbols can be deprecated. This should be clearly marked in the doxygen
|
||||
with ``@deprecated``, and if possible, the affected API symbols should not be
|
||||
visible if the API user defines ``MPV_ENABLE_DEPRECATED`` to 0.
|
||||
|
||||
ABI compatibility
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
The ABI must never be broken, except on major API version changes. For example,
|
||||
constants don't change their values.
|
||||
|
||||
Structs are tricky. If a struct can be allocated by a user (such as ``mpv_node``),
|
||||
no fields can be added. (Unless it's an union, and the addition does not change
|
||||
the offset or alignment of any of the fields or the struct itself. This has
|
||||
happened to ``mpv_node`` in the past.) If a struct is allocated by libmpv only,
|
||||
new fields can be appended to the end (for example ``mpv_event``).
|
||||
|
||||
The ABI is only backward compatible. This means if a host application is linked
|
||||
to an older libmpv, and libmpv is updated to a newer version, it will still
|
||||
work (as in not causing any undefined behavior).
|
||||
|
||||
Forward compatibility (an application would work with an older libmpv than it
|
||||
was linked to) is not required.
|
||||
@@ -136,7 +136,7 @@ pacman -S git python $MINGW_PACKAGE_PREFIX-{pkg-config,gcc}
|
||||
|
||||
# Install the most important MinGW-w64 dependencies. libass and lcms2 are also
|
||||
# pulled in as dependencies of ffmpeg.
|
||||
pacman -S $MINGW_PACKAGE_PREFIX-{ffmpeg,libjpeg-turbo,lua51}
|
||||
pacman -S $MINGW_PACKAGE_PREFIX-{ffmpeg,libjpeg-turbo,lua51,angleproject-git}
|
||||
```
|
||||
|
||||
Building mpv
|
||||
@@ -168,12 +168,8 @@ Or, compile and install both libmpv and mpv:
|
||||
Linking libmpv with MSVC programs
|
||||
---------------------------------
|
||||
|
||||
mpv/libmpv cannot be built with Visual Studio (Microsoft is too incompetent to
|
||||
support C99/C11 properly and/or hates open source and Linux too much to
|
||||
seriously do it). But you can build C++ programs in Visual Studio and link them
|
||||
with a libmpv built with MinGW.
|
||||
|
||||
To do this, you need a Visual Studio which supports ``stdint.h`` (recent ones do),
|
||||
You can build C++ programs in Visual Studio and link them with libmpv. To do
|
||||
this, you need a Visual Studio which supports ``stdint.h`` (recent ones do),
|
||||
and you need to create a import library for the mpv DLL:
|
||||
|
||||
```bash
|
||||
|
||||
+30
-94
@@ -5,60 +5,35 @@ General
|
||||
-------
|
||||
|
||||
The main contact for mpv development is IRC, specifically #mpv
|
||||
and #mpv-devel on Freenode. Github is used for code review and
|
||||
long term discussions.
|
||||
and #mpv-devel on Freenode.
|
||||
|
||||
Sending patches
|
||||
---------------
|
||||
|
||||
- Make a github pull request, or send a link to a plaintext patch created with
|
||||
``git format-patch``.
|
||||
- Plain diffs posted as pastebins are not acceptable! (Especially if the http
|
||||
link returns HTML.) They only cause extra work for everyone, because they lack
|
||||
commit message and authorship information.
|
||||
- Never send patches to any of the developers email addresses.
|
||||
- If your changes are not supposed to be merged immediately, mark them as
|
||||
"[RFC]" in the commit message or the pull request title.
|
||||
- Be sure to test your changes. If you didn't, please say so in the commit
|
||||
message and the pull request text.
|
||||
|
||||
Copyright of contributions
|
||||
--------------------------
|
||||
|
||||
- The copyright belongs to contributors. The project is a collaborative work. By
|
||||
sending your changes, you agree to license your contributions according to the
|
||||
requirements of this project.
|
||||
``git format-patch``. diffs posted as pastebins (especially if the http link
|
||||
returns HTML) just cause extra work for everyone, because they lack commit
|
||||
message and authorship information.
|
||||
- All new code must be LGPLv2.1+ licensed, or come with the implicit agreement
|
||||
that it will be relicensed to LGPLv2.1+ later (see ``Copyright`` in the
|
||||
repository root directory).
|
||||
- 100% compatible licenses are allowed too.
|
||||
- Changes in files with more liberal licenses (such as BSD, MIT, or ISC) are
|
||||
assumed to be dual-licensed under LGPLv2.1+ and the license indicated in the
|
||||
file header.
|
||||
- You must be either the exclusive author of the patch, or acknowledge all
|
||||
authors involved in the commit message. If you take 3rd party code, authorship
|
||||
and copyright must be properly acknowledged. If you're making changes on
|
||||
behalf of your employer, and the employer owns the copyright, you must mention
|
||||
this. If the license of the code is not LGPLv2.1+, you must mention this.
|
||||
- These license statements are legally binding.
|
||||
- Don't use fake names (something that looks like an actual name, and may be
|
||||
and copyright must be properly acknowledged. If the license of the code is not
|
||||
LGPLv2.1+, this must be mentioned.
|
||||
- Don't use fake names (something that looks like an actual names, and may be
|
||||
someone else's name, but is not your legal name). Using a pseudonyms is
|
||||
allowed if it can be used to identify or contact you, even if whatever
|
||||
account you used to submit the patch dies.
|
||||
- Do not add your name to the license header. This convention is not used by
|
||||
this project, and neither copyright law nor any of the used licenses require
|
||||
it.
|
||||
|
||||
Write good commit messages
|
||||
--------------------------
|
||||
|
||||
- When creating pull requests, be sure to test your changes. If you didn't,
|
||||
please say so in the pull request message.
|
||||
- Write informative commit messages. Use present tense to describe the
|
||||
situation with the patch applied, and past tense for the situation before
|
||||
the change.
|
||||
- The subject line (the first line in a commit message) must contain a
|
||||
- The subject line (the first line in a commit message) should contain a
|
||||
prefix identifying the sub system, followed by a short description what
|
||||
impact this commit has. This subject line and the commit message body
|
||||
must not be longer than 72 characters per line, because it messes up the
|
||||
shouldn't be longer than 72 characters per line, because it messes up the
|
||||
output of many git tools otherwise.
|
||||
|
||||
For example, you fixed a crash in af_volume.c:
|
||||
@@ -68,11 +43,7 @@ Write good commit messages
|
||||
|
||||
Having a prefix gives context, and is especially useful when trying to find
|
||||
a specific change by looking at the history, or when running ``git blame``.
|
||||
- The first word after the ``:`` is lower case.
|
||||
- Don't end the subject line with a ``.``.
|
||||
- Put an empty line between the subject line and the commit message.
|
||||
If this is missing, it will break display in common git tools.
|
||||
- The body of the commit message (everything else after the subject line) must
|
||||
- The body of the commit message (everything else after the subject line) should
|
||||
be as informative as possible and contain everything that isn't obvious. Don't
|
||||
hesitate to dump as much information as you can - it doesn't cost you
|
||||
anything. Put some effort into it. If someone finds a bug months or years
|
||||
@@ -81,40 +52,25 @@ Write good commit messages
|
||||
information to test the original bug. The old bug might be reintroduced while
|
||||
fixing the new bug.
|
||||
|
||||
The commit message must be wrapped on 72 characters per line, because git
|
||||
The commit message should be wrapped on 72 characters per line, because git
|
||||
tools usually do not break text automatically. On the other hand, you do not
|
||||
need to break text that would be unnatural to break (like data for test cases,
|
||||
or long URLs).
|
||||
- Another summary of good conventions: https://chris.beams.io/posts/git-commit/
|
||||
|
||||
Split changes into multiple commits
|
||||
-----------------------------------
|
||||
|
||||
- Follow git good practices, and split independent changes into several commits.
|
||||
It's usually OK to put them into a single pull request.
|
||||
Important: put an empty line between the subject line and the commit message.
|
||||
If this is missing, it will break display in common git tools.
|
||||
- Try to separate cosmetic and functional changes. It's ok to make a few
|
||||
additional cosmetic changes in the same file you're working on. But don't do
|
||||
something like reformatting a whole file, and hiding an actual functional
|
||||
change in the same commit.
|
||||
- Splitting changes does _not_ mean that you should make them as fine-grained
|
||||
as possible. Commits should form logical steps in development. The way you
|
||||
split changes is important for code review and analyzing bugs.
|
||||
|
||||
Touching user-visible parts may require updating the mpv docs
|
||||
-------------------------------------------------------------
|
||||
|
||||
- Most user-visible things are normally documented in DOCS/man/. If your commit
|
||||
touches documented behavior, list of sub-options, etc., you need to adjust the
|
||||
documentation.
|
||||
- These changes usually go into the same commit that changes the code.
|
||||
- Changes to command line options (addition/modification/removal) must be
|
||||
documented in options.rst.
|
||||
- Changes to input properties or input commands must be documented in input.rst.
|
||||
- All incompatible changes to the user interface (options, properties, commands)
|
||||
must be documented with a small note in interface-changes.rst. (Additions may
|
||||
be documented there as well, but this isn't required.)
|
||||
- Changes to the libmpv API must be reflected in the libmpv's headers doxygen,
|
||||
and in client-api-changes.rst.
|
||||
documented in options.rst. Changes to input properties or input commands must
|
||||
be documented in input.rst. All changes to the user interface (options,
|
||||
properties, commands) must be documented with a small note in
|
||||
interface-changes.rst (although documenting additions is optional, and
|
||||
obscure corner cases can potentially be skipped too). Changes to the libmpv
|
||||
API must be reflected in the libmpv's headers doxygen, and should be
|
||||
documented in client-api-changes.rst.
|
||||
|
||||
Code formatting
|
||||
---------------
|
||||
@@ -124,7 +80,7 @@ mpv uses C99 with K&R formatting, with some exceptions.
|
||||
- Use the K&R indent style.
|
||||
- Use 4 spaces of indentation, never use tabs (except in Makefiles).
|
||||
- Add a single space between keywords and binary operators. There are some other
|
||||
cases where spaces must be added. Example:
|
||||
cases where spaces should be added. Example:
|
||||
|
||||
```C
|
||||
if ((a * b) > c) {
|
||||
@@ -157,7 +113,7 @@ mpv uses C99 with K&R formatting, with some exceptions.
|
||||
do_something();
|
||||
}
|
||||
```
|
||||
- If the if has an else branch, both branches must use braces, even if they're
|
||||
- If the if has an else branch, both branches should use braces, even if they're
|
||||
technically redundant.
|
||||
|
||||
Example:
|
||||
@@ -187,31 +143,18 @@ mpv uses C99 with K&R formatting, with some exceptions.
|
||||
|
||||
(If the if body is simple enough, this rule can be skipped.)
|
||||
- Remove any trailing whitespace.
|
||||
- Do not make stray whitespaces changes.
|
||||
|
||||
Header #include statement order
|
||||
-------------------------------
|
||||
|
||||
The order of ``#include`` statements in the source code is not very consistent.
|
||||
New code must follow the following conventions:
|
||||
|
||||
- Put standard includes (``#include <stdlib.h>`` etc.) on the top,
|
||||
- then after a blank line, add library includes (``#include <zlib.h>`` etc.)
|
||||
- then after a blank line, add internal includes (``#include "player/core.h"``)
|
||||
- sort them alphabetically within these sections
|
||||
|
||||
General coding
|
||||
--------------
|
||||
|
||||
- Use C99. Also freely make use of C99 features if it's appropriate, such as
|
||||
stdbool.h. (Except VLA and complex number types.)
|
||||
- Don't use non-standard language (such as GNU C-only features). In some cases
|
||||
they may be warranted, if they are optional (such as attributes enabling
|
||||
printf-like format string checks). "#pragma once" is allowed as an exception.
|
||||
But in general, standard C99 must be used.
|
||||
stdbool.h.
|
||||
- Don't use GNU-only features. In some cases they may be warranted, if they
|
||||
are optional (such as attributes enabling printf-like format string checks).
|
||||
But in general, standard C99 should be used.
|
||||
- The same applies to libc functions. We have to be Windows-compatible too. Use
|
||||
functions guaranteed by C99 or POSIX only, unless your use is guarded by a
|
||||
configure check. There is some restricted use of C11 (ask on IRC for details).
|
||||
configure check.
|
||||
- Prefer fusing declaration and initialization, rather than putting declarations
|
||||
on the top of a block. Obvious data flow is more important than avoiding
|
||||
mixing declarations and statements, which is just a C90 artifact.
|
||||
@@ -219,11 +162,6 @@ General coding
|
||||
channel first. There might be a better way to add a feature and it can avoid
|
||||
wasted work.
|
||||
|
||||
Code of Conduct
|
||||
---------------
|
||||
|
||||
We have one, but the document describing it got lost accidentally.
|
||||
|
||||
Rules for git push access
|
||||
-------------------------
|
||||
|
||||
@@ -242,9 +180,7 @@ you got access, the following rules must be followed:
|
||||
major bug fixes, or you're the original author of the code). If there is more
|
||||
than one maintainer, you may need to come to an agreement with the others how
|
||||
to handle this to avoid conflict.
|
||||
- If you make a pull requests (especially if it's to code you maintain), and you
|
||||
want reviews, explicitly ping the people from which you expect reviews.
|
||||
- As a maintainer, you can approve pull requests by others to "your" code.
|
||||
- As a maintainer, you can approve pushes by others to "your" code.
|
||||
- If you approve or merge 3rd party changes, make sure they follow the general
|
||||
development rules.
|
||||
- Changes to user interface and public API must always be approved by the
|
||||
|
||||
@@ -100,18 +100,6 @@ entries affect all other file entries in the EDL file. Their format is highly
|
||||
implementation specific. They should generally follow the file header, and come
|
||||
before any file entries.
|
||||
|
||||
Disabling chapter generation and copying
|
||||
========================================
|
||||
|
||||
By default, chapters from the source ranges are copied to the virtual file's
|
||||
chapters. Also, a chapter is inserted after each range. This can be disabled
|
||||
with the ``no_chapters`` header.
|
||||
|
||||
Example::
|
||||
|
||||
!no_chapters
|
||||
|
||||
|
||||
MP4 DASH
|
||||
========
|
||||
|
||||
@@ -143,45 +131,6 @@ The current implementation will
|
||||
- not add segment boundaries as chapter points
|
||||
- require full compatibility between all segments (same codec etc.)
|
||||
|
||||
Another header part of this mechanism is ``no_clip``. This header is similar
|
||||
to ``mp4_dash``, but does not include on-demand opening/closing of segments,
|
||||
and does not support init segments. It also exists solely to support internal
|
||||
ytdl requirements.
|
||||
|
||||
The ``mp4_dash`` and ``no_clip`` headers are not part of the core EDL format.
|
||||
They may be changed or removed at any time, depending on mpv's internal
|
||||
requirements.
|
||||
|
||||
Separate files for tracks
|
||||
=========================
|
||||
|
||||
The special ``new_stream`` header lets you specify separate parts and time
|
||||
offsets for separate tracks. This can for example be used to source audio and
|
||||
video track from separate files.
|
||||
|
||||
Example::
|
||||
|
||||
# mpv EDL v0
|
||||
video.mkv
|
||||
!new_stream
|
||||
audio.mkv
|
||||
|
||||
This adds all tracks from both files to the virtual track list. Upon playback,
|
||||
the tracks will be played at the same time, instead of appending them. The files
|
||||
can contain more than 1 stream; the apparent effect is the same as if the second
|
||||
part after the ``!new_stream`` part were in a separate ``.edl`` file and added
|
||||
with ``--external-file``.
|
||||
|
||||
Note that all metadata between the stream sets created by ``new_stream`` is
|
||||
disjoint. Global metadata is taken from the first part only.
|
||||
|
||||
In context of mpv, this is redundant to the ``--audio-file`` and
|
||||
``--external-file`` options, but (as of this writing) has the advantage that
|
||||
this will use a unified cache for all streams.
|
||||
|
||||
The ``new_stream`` header is not part of the core EDL format. It may be changed
|
||||
or removed at any time, depending on mpv's internal requirements.
|
||||
|
||||
Timestamp format
|
||||
================
|
||||
|
||||
@@ -198,54 +147,6 @@ Example::
|
||||
|
||||
Plays chapter 3 and ends with the start of chapter 7 (4 chapters later).
|
||||
|
||||
Implicit chapters
|
||||
=================
|
||||
|
||||
mpv will add one chapter per segment entry to the virtual timeline.
|
||||
|
||||
By default, the chapter's titles will match the entries' filenames.
|
||||
You can override set the ``title`` option to override the chapter title for
|
||||
that segment.
|
||||
|
||||
Example::
|
||||
|
||||
# mpv EDL v0
|
||||
cap.ts,5,240
|
||||
OP.mkv,0,90,title=Show Opening
|
||||
|
||||
The virtual timeline will have two chapters, one called "cap.ts" from 0-240s
|
||||
and a second one called "Show Opening" from 240-330s.
|
||||
|
||||
Entry which defines the track layout
|
||||
====================================
|
||||
|
||||
Normally, you're supposed to put only files with compatible layouts into an EDL
|
||||
file. However, at least the mpv implementation accepts entries that use
|
||||
different codecs, or even have a different number of audio/video/subtitle
|
||||
tracks. In this case, it's not obvious, which virtual tracks the EDL show should
|
||||
expose when being played.
|
||||
|
||||
Currently, mpv will apply an arbitrary heuristic which tracks the EDL file
|
||||
should expose. (Before mpv 0.30.0, it always used the first source file in the
|
||||
segment list.)
|
||||
|
||||
You can set the ``layout`` option to ``this`` to make a specific entry define
|
||||
the track layout.
|
||||
|
||||
Example::
|
||||
|
||||
# mpv EDL v0
|
||||
file_with_2_streams.ts,5,240
|
||||
file_with_5_streams.mkv,0,90,layout=this
|
||||
|
||||
The way the different virtual EDL tracks are associated with the per-segment
|
||||
ones is highly implementation-defined, and uses a heuristic. If a segment is
|
||||
missing a track, there will be a "hole", and bad behavior may result. Improving
|
||||
this is subject to further development (due to being fringe cases, they don't
|
||||
have a high priority).
|
||||
|
||||
If future versions of mpv change this again, this option may be ignored.
|
||||
|
||||
Syntax of EDL URIs
|
||||
==================
|
||||
|
||||
|
||||
+7
-6
@@ -3,7 +3,8 @@ General usage
|
||||
|
||||
::
|
||||
|
||||
mpv infile -o outfile [-of outfileformat] [-ofopts formatoptions] [-orawts] \
|
||||
mpv infile -o outfile [-of outfileformat] [-ofopts formatoptions] \
|
||||
[-ofps outfps | -oautofps] [-oharddup] [-ocopyts | -orawts] [-oneverdrop] \
|
||||
[(any other mpv options)] \
|
||||
-ovc outvideocodec [-ovcopts outvideocodecoptions] \
|
||||
-oac outaudiocodec [-oacopts outaudiocodecoptions]
|
||||
@@ -59,13 +60,13 @@ for.
|
||||
Typical MPEG-4 Part 2 ("ASP", "DivX") encoding, AVI container::
|
||||
|
||||
mpv infile -o outfile.avi \
|
||||
--vf=fps=25 \
|
||||
-ofps 25 \
|
||||
-ovc mpeg4 -ovcopts qscale=4 \
|
||||
-oac libmp3lame -oacopts ab=128k
|
||||
|
||||
Note: AVI does not support variable frame rate, so the fps filter must be used.
|
||||
The frame rate should ideally match the input (25 for PAL, 24000/1001 or
|
||||
30000/1001 for NTSC)
|
||||
Note: AVI does not support variable frame rate, so -ofps must be used. The
|
||||
frame rate should ideally match the input (25 for PAL, 24000/1001 or 30000/1001
|
||||
for NTSC)
|
||||
|
||||
Typical MPEG-4 Part 10 ("AVC", "H.264") encoding, Matroska (MKV) container::
|
||||
|
||||
@@ -128,7 +129,7 @@ What works
|
||||
==========
|
||||
|
||||
* Encoding at variable frame rate (default)
|
||||
* Encoding at constant frame rate using --vf=fps=RATE
|
||||
* Encoding at constant frame rate using -ofps framerate -oharddup
|
||||
* 2-pass encoding (specify flags=+pass1 in the first pass's -ovcopts, specify
|
||||
flags=+pass2 in the second pass)
|
||||
* Hardcoding subtitles using vobsub, ass or srt subtitle rendering (just
|
||||
|
||||
+2
-236
@@ -7,16 +7,11 @@ mpv provides access to its internals via the following means:
|
||||
- commands
|
||||
- properties
|
||||
- events
|
||||
- hooks
|
||||
|
||||
The sum of these mechanisms is sometimes called command interface.
|
||||
|
||||
All of these are important for interfacing both with end users and API users
|
||||
(which include Lua scripts, libmpv, and the JSON IPC). As such, they constitute
|
||||
a large part of the user interface and APIs.
|
||||
|
||||
Also see compatibility.rst.
|
||||
|
||||
This document lists changes to them. New changes are added to the top.
|
||||
|
||||
Interface changes
|
||||
@@ -24,234 +19,6 @@ Interface changes
|
||||
|
||||
::
|
||||
|
||||
--- mpv 0.31.0 ---
|
||||
- add `--resume-playback-check-mtime` to check consistent mtime when
|
||||
restoring playback state.
|
||||
- add `--d3d11-output-csp` to enable explicit selection of a D3D11
|
||||
swap chain color space.
|
||||
- the --sws- options and similar now affect vo_image and screenshot
|
||||
conversion (does not matter as much for vo_gpu, which does most of this
|
||||
with shaders)
|
||||
- add a builtin "sw-fast" profile, which restores performance settings
|
||||
for software video conversion. These were switched to higher quality since
|
||||
mpv 0.30.0 (related to the previous changelog entry). This affects video
|
||||
outputs like vo_x11 and vo_drm, and screenshots, but not much else.
|
||||
- deprecate --input-file (there are no plans to remove this short-term,
|
||||
but it will probably eventually go away)
|
||||
- deprecate --video-sync=display-adrop (might be removed if it's in the way;
|
||||
undeprecated or readded if it's not too much of a problem)
|
||||
- deprecate all input section commands (these will be changed/removed, as
|
||||
soon as mpv internals do not require them anymore)
|
||||
- remove deprecated --playlist-pos alias (use --playlist-start)
|
||||
- deprecate --display-fps, introduce --override-display-fps. The display-fps
|
||||
property now is unavailable if no VO exists (or the VO did not return a
|
||||
display FPS), instead of returning the option value in this case. The
|
||||
property will keep existing, but writing to it is deprecated.
|
||||
- the vf/af properties now do not reject the set value anymore, even if
|
||||
filter chain initialization fails. Instead, the vf/af options are always
|
||||
set to the user's value, even if it does not reflect the "runtime" vf/af
|
||||
chain.
|
||||
- the vid/aid/sid/secondary-sid properties (and their aliases: "audio",
|
||||
"video", "sub") will now allow setting any track ID; before this change,
|
||||
only IDs of actually existing tracks could be set (the restriction was
|
||||
active the MPV_EVENT_FILE_LOADED/"file-loaded" event was sent). Setting
|
||||
an ID for which no track exists is equivalent to disabling it. Note that
|
||||
setting the properties to non-existing tracks may report it as selected
|
||||
track for a small time window, until it's forced back to "no". The exact
|
||||
details how this is handled may change in the future.
|
||||
- remove old Apple Remote support, including --input-appleremote
|
||||
- add MediaPlayer support and remove the old Media Key event tap on macOS.
|
||||
this possibly also re-adds the Apple Remote support
|
||||
- the "edition" property now strictly returns the value of the option,
|
||||
instead of the runtime value. The new "current-edition" property needs to
|
||||
be queried to read the runtime-chosen edition. This is a breaking change
|
||||
for any users which expected "edition" to return the runtime-chosen
|
||||
edition at default settings (--edition=auto).
|
||||
- the "window-scale" property now strictly returns the value of the option,
|
||||
instead of the actual size of the window. The new "current-window-scale"
|
||||
property needs to be queried to read the value as indicated by the current
|
||||
window size. This is a breaking change.
|
||||
- explicitly deprecate passing more than 1 item to "-add" suffix in key/value
|
||||
options (for example --script-opts-add). This was actually always
|
||||
deprecated, like with other list options, but the option parser did not
|
||||
print a warning in this particular case.
|
||||
- deprecate -del for list options (use -remove instead, which is by content
|
||||
instead of by integer index)
|
||||
- if `--fs` is used but `--fs-screen` is not set, mpv will now use `--screen`
|
||||
instead.
|
||||
- change the default of --hwdec to "no" on RPI. The default used to be "mmal"
|
||||
specifically if 'Raspberry Pi support' was enabled at configure time
|
||||
(equivalent to --enable-rpi). Use --hwdec=mmal to get the old behavior.
|
||||
--- mpv 0.30.0 ---
|
||||
- add `--d3d11-output-format` to enable explicit selection of a D3D11
|
||||
swap chain format.
|
||||
- rewrite DVB channel switching to use an integer value
|
||||
`--dvbin-channel-switch-offset` for switching instead of the old
|
||||
stream controls which are now gone. Cycling this property up or down will
|
||||
change the offset to the channel which was initially tuned to.
|
||||
Example for `input.conf`: `H cycle dvbin-channel-switch-offset up`,
|
||||
`K cycle dvbin-channel-switch-offset down`.
|
||||
- adapt `stream_dvb` to support writing to `dvbin-prog` at runtime
|
||||
and also to consistently use dvbin-configuration over URI parameters
|
||||
when provided
|
||||
- add `--d3d11-adapter` to enable explicit selection of a D3D11 rendering
|
||||
adapter by name.
|
||||
- rename `--drm-osd-plane-id` to `--drm-draw-plane`, `--drm-video-plane-id` to
|
||||
`--drm-drmprime-video-plane` and `--drm-osd-size` to `--drm-draw-surface-size`
|
||||
to better reflect what the options actually control, that the values they
|
||||
accept aren't actually internal DRM ID's (like with similar options in
|
||||
ffmpeg's KMS support), and that the video plane is only used when the drmprime
|
||||
overlay hwdec interop is active, with the video being drawn to the draw plane
|
||||
otherwise.
|
||||
- in addition to the above, the `--drm-draw-plane` and `--drm-drmprime-video-plane`
|
||||
options now accept either an integer index, or the values primary or overlay.
|
||||
`--drm-draw-plane` now defaults to primary and `--drm-drmprime-video-plane`
|
||||
defaults to overlay. This should be similar to previous behavior on most drivers
|
||||
due to how planes are usually sorted.
|
||||
- rename --opensles-frames-per-buffer to --opensles-frames-per-enqueue to
|
||||
better reflect its purpose. In the past it overrides the buffer size the AO
|
||||
requests (but not the default/value of the generic --audio-buffer option).
|
||||
Now it only guarantees that the soft buffer size will not be smaller than
|
||||
itself while setting the size per Enqueue.
|
||||
- add --opensles-buffer-size-in-ms, allowing user to tune the soft buffer size.
|
||||
It overrides the --audio-buffer option unless it's set to 0 (with the default
|
||||
being 250).
|
||||
- remove `--linear-scaling`, replaced by `--linear-upscaling` and
|
||||
`--linear-downscaling`. This means that `--sigmoid-upscaling` no longer
|
||||
implies linear light downscaling as well, which was confusing.
|
||||
- the built-in `gpu-hq` profile now includes` --linear-downscaling`.
|
||||
- support for `--spirv-compiler=nvidia` has been removed, leaving `shaderc`
|
||||
as the only option. The `--spirv-compiler` option itself has been marked
|
||||
as deprecated, and may be removed in the future.
|
||||
- split up `--tone-mapping-desaturate`` into strength + exponent, instead of
|
||||
only using a single value (which previously just controlled the exponent).
|
||||
The strength now linearly blends between the linear and nonlinear tone
|
||||
mapped versions of a color.
|
||||
- add --hdr-peak-decay-rate and --hdr-scene-threshold-low/high
|
||||
- add --tone-mapping-max-boost
|
||||
- ipc: require that "request_id" fields are integers. Other types are still
|
||||
accepted for compatibility, but this will stop in the future. Also, if no
|
||||
request_id is provided, 0 will be assumed.
|
||||
- mpv_command_node() and mp.command_native() now support named arguments
|
||||
(see manpage). If you want to use them, use a new version of the manpage
|
||||
as reference, which lists the definitive names.
|
||||
- edition and disc title switching will now fully reload playback (may have
|
||||
consequences for scripts, client API, or when using file-local options)
|
||||
- with the removal of the stream cache, the following properties and options were
|
||||
dropped: `cache`, `cache-size`, `cache-free`, `cache-used`, `--cache-default`,
|
||||
`--cache-initial`, `--cache-seek-min`, `--cache-backbuffer`, `--cache-file`,
|
||||
`--cache-file-size`
|
||||
- the --cache option does not take a number value anymore
|
||||
- remove async playback abort hack. This may make it impossible to abort
|
||||
playback if --demuxer-thread=no is forced.
|
||||
- remove `--macos-title-bar-style`, replaced by `--macos-title-bar-material`
|
||||
and `--macos-title-bar-appearance`.
|
||||
- The default for `--vulkan-async-compute` has changed to `yes` from `no`
|
||||
with the move to libplacebo as the back-end for vulkan rendering.
|
||||
- Remove "disc-titles", "disc-title", "disc-title-list", and "angle"
|
||||
properties. dvd:// does not support title ranges anymore.
|
||||
- Remove all "tv-..." options and properties, along with the classic Linux
|
||||
analog TV support.
|
||||
- remove "program" property (no replacement)
|
||||
- always prefer EGL over GLX, which helps with AMD/vaapi, but will break
|
||||
vdpau with --vo=gpu - use --gpu-context=x11 to be able to use vdpau. This
|
||||
does not affect --vo=vdpau or --hwdec=vdpau-copy.
|
||||
- remove deprecated --chapter option
|
||||
- deprecate --record-file
|
||||
- add `--demuxer-cue-codepage`
|
||||
- add ``track-list/N/demux-bitrate``, ``track-list/N/demux-rotation`` and
|
||||
``track-list/N/demux-par`` property
|
||||
- Deprecate ``--video-aspect`` and add ``--video-aspect-override`` to
|
||||
replace it. (The `video-aspect` option remains unchanged.)
|
||||
--- mpv 0.29.0 ---
|
||||
- drop --opensles-sample-rate, as --audio-samplerate should be used if desired
|
||||
- drop deprecated --videotoolbox-format, --ff-aid, --ff-vid, --ff-sid,
|
||||
--ad-spdif-dtshd, --softvol options
|
||||
- fix --external-files: strictly never select any tracks from them, unless
|
||||
explicitly selected (this may or may not be expected)
|
||||
- --ytdl is now always enabled, even for libmpv
|
||||
- add a number of --audio-resample-* options, which should from now on be
|
||||
used instead of --af-defaults=lavrresample:...
|
||||
- deprecate --vf-defaults and --af-defaults. These didn't work with the
|
||||
lavfi bridge, so they have very little use left. The only potential use
|
||||
is with af_lavrresample (going to be deprecated, --audio-resample-... set
|
||||
its defaults), and various hw deinterlacing filters (like vf_vavpp), for
|
||||
which you will have to stop using --deinterlace=yes, and instead use the
|
||||
vf toggle commands and the filter enable/disable flag to customize it.
|
||||
- deprecate --af=lavrresample. Use the ``--audio-resample-...`` options to
|
||||
customize resampling, or the libavfilter ``--af=aresample`` filter.
|
||||
- add --osd-on-seek
|
||||
- remove outfmt sub-parameter from "format" video filter (no replacement)
|
||||
- some behavior changes in the video filter chain, including:
|
||||
- before, using an incompatible filter with hwdec would disable hwdec;
|
||||
now it disables the filter at runtime instead
|
||||
- inserting an incompatible filter with hwdec at runtime would refuse
|
||||
to insert the filter; now it will add it successfully, but disables
|
||||
the filter slightly later
|
||||
- some behavior changes in the audio filter chain, including:
|
||||
- a manually inserted lavrresample filter is not necessarily used for
|
||||
sample format conversion anymore, so it's pretty useless
|
||||
- changing playback speed will not respect --af-defaults anymore
|
||||
- having libavfilter based filters after the scaletempo or rubberband
|
||||
filters is not supported anymore, and may desync if playback speed is
|
||||
changed (libavfilter does not support the metadata for playback speed)
|
||||
- the lavcac3enc filter does not auto detach itself anymore; instead it
|
||||
passes through the data after converting it to the sample rate and
|
||||
channel configuration the ac3 encoder expects; also, if the audio
|
||||
format changes midstream in a way that causes the filter to switch
|
||||
between PCM and AC3 output, the audio output won't be reconfigured,
|
||||
and audio playback will fail due to libswresample being unable to
|
||||
convert between PCM and AC3 (Note: the responsible developer didn't
|
||||
give a shit. Later changes might have improved or worsened this.)
|
||||
- inserting a filter that changes the output sample format will not
|
||||
reconfigure the AO - you need to run an additional "ao-reload"
|
||||
command to force this if you want that
|
||||
- using "strong" gapless audio (--gapless-audio=yes) can fail if the
|
||||
audio formats are not convertible (such as switching between PCM and
|
||||
AC3 passthrough)
|
||||
- if filters do not pass through PTS values correctly, A/V sync can
|
||||
result over time. Some libavfilter filters are known to be affected by
|
||||
this, such as af_loudnorm, which can desync over time, depending on
|
||||
how the audio track was muxed (af_lavfi's fix-pts suboption can help).
|
||||
- remove out-format sub-parameter from "format" audio filter (no replacement)
|
||||
- --lavfi-complex now requires uniquely named filter pads. In addition,
|
||||
unconnected filter pads are not allowed anymore (that means every filter
|
||||
pad must be connected either to another filter, or to a video/audio track
|
||||
or video/audio output). If they are disconnected at runtime, the stream
|
||||
will probably stall.
|
||||
- rename --vo=opengl-cb to --vo=libmpv (goes in hand with the opengl-cb
|
||||
API deprecation, see client-api-changes.rst)
|
||||
- deprecate the OpenGL cocoa backend, option choice --gpu-context=cocoa
|
||||
when used with --gpu-api=opengl (use --vo=libmpv)
|
||||
- make --deinterlace=yes always deinterlace, instead of trying to check
|
||||
certain unreliable video metadata. Also flip the defaults of all builtin
|
||||
HW deinterlace filters to always deinterlace.
|
||||
- change vf_vavpp default to use the best deinterlace algorithm by default
|
||||
- remove a compatibility hack that allowed CLI aliases to be set as property
|
||||
(such as "sub-file"), deprecated in mpv 0.26.0
|
||||
- deprecate the old command based hook API, and introduce a proper C API
|
||||
(the high level Lua API for this does not change)
|
||||
- rename the the lua-settings/ config directory to script-opts/
|
||||
- the way the player waits for scripts getting loaded changes slightly. Now
|
||||
scripts are loaded in parallel, and block the player from continuing
|
||||
playback only in the player initialization phase. It could change again in
|
||||
the future. (This kind of waiting was always a feature to prevent that
|
||||
playback is started while scripts are only half-loaded.)
|
||||
- deprecate --ovoffset, --oaoffset, --ovfirst, --oafirst
|
||||
- remove the following encoding options: --ocopyts (now the default, old
|
||||
timestamp handling is gone), --oneverdrop (now default), --oharddup (you
|
||||
need to use --vf=fps=VALUE), --ofps, --oautofps, --omaxfps
|
||||
- remove --video-stereo-mode. This option was broken out of laziness, and
|
||||
nobody wants to fix it. Automatic 3D down-conversion to 2D is also broken,
|
||||
although you can just insert the stereo3d filter manually. The obscurity
|
||||
of 3D content doesn't justify such an option anyway.
|
||||
- change cycle-values command to use the current value, instead of an
|
||||
internal counter that remembered the current position.
|
||||
- remove deprecated ao/vo auto profiles. Consider using scripts like
|
||||
auto-profiles.lua instead.
|
||||
- --[c]scale-[w]param[1|2] and --tone-mapping-param now accept "default",
|
||||
and if set to that value, reading them as property will also return
|
||||
"default", instead of float nan as in previous versions
|
||||
--- mpv 0.28.0 ---
|
||||
- rename --hwdec=mediacodec option to mediacodec-copy, to reflect
|
||||
conventions followed by other hardware video decoding APIs
|
||||
@@ -292,12 +59,11 @@ Interface changes
|
||||
now.
|
||||
- change the --hwdec option from a choice to a plain string (affects
|
||||
introspection of the option/property), also affects some properties
|
||||
- rename --hwdec=rpi to --hwdec=mmal, same for the -copy variant (no
|
||||
- rename --hwdec=rpi to --hwdec=mmal, sane for the -copy variant (no
|
||||
backwards compatibility)
|
||||
- deprecate the --ff-aid, --ff-vid, --ff-sid options and properties (there is
|
||||
- deprecate the --ff-aid, --ff-vid, -ff-sid options and properties (there is
|
||||
no replacement, but you can manually query the track property and use the
|
||||
"ff-index" field to find the mpv track ID to imitate this behavior)
|
||||
- rename --no-ometadata to --no-ocopy-metadata
|
||||
--- mpv 0.27.0 ---
|
||||
- drop previously deprecated --field-dominance option
|
||||
- drop previously deprecated "osd" command
|
||||
|
||||
+35
-22
@@ -5,8 +5,7 @@ Audio filters allow you to modify the audio stream and its properties. The
|
||||
syntax is:
|
||||
|
||||
``--af=...``
|
||||
Setup a chain of audio filters. See ``--vf`` (`VIDEO FILTERS`_) for the
|
||||
full syntax.
|
||||
Setup a chain of audio filters. See ``--vf`` for the syntax.
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -24,6 +23,37 @@ See ``--vf`` group of options for info on how ``--af-defaults``, ``--af-add``,
|
||||
|
||||
Available filters are:
|
||||
|
||||
``lavrresample[=option1:option2:...]``
|
||||
This filter uses libavresample (or libswresample, depending on the build)
|
||||
to change sample rate, sample format, or channel layout of the audio stream.
|
||||
This filter is automatically enabled if the audio output does not support
|
||||
the audio configuration of the file being played.
|
||||
|
||||
It supports only the following sample formats: u8, s16, s32, float.
|
||||
|
||||
``filter-size=<length>``
|
||||
Length of the filter with respect to the lower sampling rate. (default:
|
||||
16)
|
||||
``phase-shift=<count>``
|
||||
Log2 of the number of polyphase entries. (..., 10->1024, 11->2048,
|
||||
12->4096, ...) (default: 10->1024)
|
||||
``cutoff=<cutoff>``
|
||||
Cutoff frequency (0.0-1.0), default set depending upon filter length.
|
||||
``linear``
|
||||
If set then filters will be linearly interpolated between polyphase
|
||||
entries. (default: no)
|
||||
``no-detach``
|
||||
Do not detach if input and output audio format/rate/channels match.
|
||||
(If you just want to set defaults for this filter that will be used
|
||||
even by automatically inserted lavrresample instances, you should
|
||||
prefer setting them with ``--af-defaults=lavrresample:...``.)
|
||||
``normalize=<yes|no|auto>``
|
||||
Whether to normalize when remixing channel layouts (default: auto).
|
||||
``auto`` uses the value set by ``--audio-normalize-downmix``.
|
||||
``o=<string>``
|
||||
Set AVOptions on the SwrContext or AVAudioResampleContext. These should
|
||||
be documented by FFmpeg or Libav.
|
||||
|
||||
``lavcac3enc[=options]``
|
||||
Encode multi-channel audio to AC-3 at runtime using libavcodec. Supports
|
||||
16-bit native-endian input format, maximum 6 channels. The output is
|
||||
@@ -61,7 +91,7 @@ Available filters are:
|
||||
Select the libavcodec encoder used. Currently, this should be an AC-3
|
||||
encoder, and using another codec will fail horribly.
|
||||
|
||||
``format=format:srate:channels:out-srate:out-channels``
|
||||
``format=format:srate:channels:out-format:out-srate:out-channels``
|
||||
Does not do any format conversion itself. Rather, it may cause the
|
||||
filter system to insert necessary conversion filters before or after this
|
||||
filter if needed. It is primarily useful for controlling the audio format
|
||||
@@ -90,6 +120,8 @@ Available filters are:
|
||||
Force mixing to a specific channel layout. See ``--audio-channels`` option
|
||||
for possible values.
|
||||
|
||||
``<out-format>``
|
||||
|
||||
``<out-srate>``
|
||||
|
||||
``<out-channels>``
|
||||
@@ -187,12 +219,6 @@ Available filters are:
|
||||
change the playback pitch at runtime. Note that speed is controlled
|
||||
using the standard ``speed`` property, not ``af-command``.
|
||||
|
||||
``multiply-pitch <factor>``
|
||||
Multiply the current value of ``<pitch-scale>`` dynamically. For
|
||||
example: 0.5 to go down by an octave, 1.5 to go up by a perfect fifth.
|
||||
If you want to go up or down by semi-tones, use 1.059463094352953 and
|
||||
0.9438743126816935
|
||||
|
||||
``lavfi=graph``
|
||||
Filter audio using FFmpeg's libavfilter.
|
||||
|
||||
@@ -207,16 +233,3 @@ Available filters are:
|
||||
|
||||
``o=<string>``
|
||||
AVOptions.
|
||||
|
||||
``fix-pts=<yes|no>``
|
||||
Determine PTS based on sample count (default: no). If this is enabled,
|
||||
the player won't rely on libavfilter passing through PTS accurately.
|
||||
Instead, it pass a sample count as PTS to libavfilter, and compute the
|
||||
PTS used by mpv based on that and the input PTS. This helps with filters
|
||||
which output a recomputed PTS instead of the original PTS (including
|
||||
filters which require the PTS to start at 0). mpv normally expects
|
||||
filters to not touch the PTS (or only to the extent of changing frame
|
||||
boundaries), so this is not the default, but it will be needed to use
|
||||
broken filters. In practice, these broken filters will either cause slow
|
||||
A/V desync over time (with some files), or break playback completely if
|
||||
you seek or start playback from the middle of a file.
|
||||
+5
-23
@@ -27,7 +27,7 @@ Available audio output drivers are:
|
||||
.. warning::
|
||||
|
||||
To get multichannel/surround audio, use ``--audio-channels=auto``. The
|
||||
default for this option is ``auto-safe``, which makes this audio output
|
||||
default for this option is ``auto-safe``, which makes this audio otuput
|
||||
explicitly reject multichannel output, as there is no way to detect
|
||||
whether a certain channel layout is actually supported.
|
||||
|
||||
@@ -110,21 +110,10 @@ Available audio output drivers are:
|
||||
exclusive mode (bypasses the sound server).
|
||||
|
||||
``openal``
|
||||
OpenAL audio output driver
|
||||
Experimental OpenAL audio output driver
|
||||
|
||||
``--openal-num-buffers=<2-128>``
|
||||
Specify the number of audio buffers to use. Lower values are better for
|
||||
lower CPU usage. Default: 4.
|
||||
|
||||
``--openal-num-samples=<256-32768>``
|
||||
Specify the number of complete samples to use for each buffer. Higher
|
||||
values are better for lower CPU usage. Default: 8192.
|
||||
|
||||
``--openal-direct-channels=<yes|no>``
|
||||
Enable OpenAL Soft's direct channel extension when available to avoid
|
||||
tinting the sound with ambisonics or HRTF.
|
||||
Channels are dropped when when they are not available as downmixing
|
||||
will be disabled. Default: no.
|
||||
.. note:: This driver is not very useful. Playing multi-channel audio with
|
||||
it is slow.
|
||||
|
||||
``pulse``
|
||||
PulseAudio audio output driver
|
||||
@@ -139,7 +128,7 @@ Available audio output drivers are:
|
||||
Set the audio buffer size in milliseconds. A higher value buffers
|
||||
more data, and has a lower probability of buffer underruns. A smaller
|
||||
value makes the audio stream react faster, e.g. to playback speed
|
||||
changes.
|
||||
changes. Default: 250.
|
||||
|
||||
``--pulse-latency-hacks=<yes|no>``
|
||||
Enable hacks to workaround PulseAudio timing bugs (default: no). If
|
||||
@@ -152,10 +141,6 @@ Available audio output drivers are:
|
||||
If you have stuttering video when using pulse, try to enable this
|
||||
option. (Or try to update PulseAudio.)
|
||||
|
||||
``--pulse-allow-suspended=<yes|no>``
|
||||
Allow mpv to use PulseAudio even if the sink is suspended (default: no).
|
||||
Can be useful if PulseAudio is running as a bridge to jack and mpv has its sink-input set to the one jack is using.
|
||||
|
||||
``sdl``
|
||||
SDL 1.2+ audio output driver. Should work on any platform supported by SDL
|
||||
1.2, but may require the ``SDL_AUDIODRIVER`` environment variable to be set
|
||||
@@ -213,9 +198,6 @@ Available audio output drivers are:
|
||||
If not empty, this is a ``,`` separated list of channel layouts the
|
||||
AO allows. This can be used to test channel layout selection.
|
||||
|
||||
``--ao-null-format``
|
||||
Force the audio output format the AO will accept. If unset accepts any.
|
||||
|
||||
``pcm``
|
||||
Raw PCM/WAVE file writer audio output
|
||||
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
CONSOLE
|
||||
=======
|
||||
|
||||
The console is a REPL for mpv input commands. It is displayed on the video
|
||||
window. It also shows log messages. It can be disabled entirely using the
|
||||
``--load-osd-console=no`` option.
|
||||
|
||||
Keybindings
|
||||
-----------
|
||||
|
||||
\`
|
||||
Show the console.
|
||||
|
||||
ESC
|
||||
Hide the console.
|
||||
|
||||
ENTER
|
||||
Run the typed command.
|
||||
|
||||
Shift+ENTER
|
||||
Type a literal newline character.
|
||||
|
||||
Ctrl+LEFT and Ctrl+RIGHT
|
||||
Move cursor to previous/next word.
|
||||
|
||||
UP and DOWN
|
||||
Navigate command history.
|
||||
|
||||
PGUP
|
||||
Go to the first command in the history.
|
||||
|
||||
PGDN
|
||||
Stop navigating command history.
|
||||
|
||||
INSERT
|
||||
Toggle insert mode.
|
||||
|
||||
Shift+INSERT
|
||||
Paste text (uses the primary selection on X11.)
|
||||
|
||||
TAB
|
||||
Complete the command or property name at the cursor.
|
||||
|
||||
Ctrl+C
|
||||
Clear current line.
|
||||
|
||||
Ctrl+K.
|
||||
Delete text from the cursor to the end of the line.
|
||||
|
||||
Ctrl+L
|
||||
Clear all log messages from the console.
|
||||
|
||||
Ctrl+U
|
||||
Delete text from the cursor to the beginning of the line.
|
||||
|
||||
Ctrl+V
|
||||
Paste text (uses the clipboard on X11.)
|
||||
|
||||
Ctrl+W
|
||||
Delete text from the cursor to the beginning of the current word.
|
||||
|
||||
Commands
|
||||
--------
|
||||
|
||||
``script-message-to console type <text>``
|
||||
Show the console and pre-fill it with the provided text.
|
||||
|
||||
Known issues
|
||||
------------
|
||||
|
||||
- Pasting text is slow on Windows
|
||||
- Non-ASCII keyboard input has restrictions
|
||||
- The cursor keys move between Unicode code-points, not grapheme clusters
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
This script can be customized through a config file ``script-opts/console.conf``
|
||||
placed in mpv's user directory and through the ``--script-opts`` command-line
|
||||
option. The configuration syntax is described in `ON SCREEN CONTROLLER`_.
|
||||
|
||||
Key bindings can be changed in a standard way, see for example stats.lua
|
||||
documentation.
|
||||
|
||||
Configurable Options
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
``scale``
|
||||
Default: 1
|
||||
|
||||
All drawing is scaled by this value, including the text borders and the
|
||||
cursor.
|
||||
|
||||
If the VO backend in use has HiDPI scale reporting implemented, the option
|
||||
value is scaled with the reported HiDPI scale.
|
||||
|
||||
``font``
|
||||
Default: unset (picks a hardcoded font depending on detected platform)
|
||||
|
||||
Set the font used for the REPL and the console. This probably doesn't
|
||||
have to be a monospaced font.
|
||||
|
||||
``font_size``
|
||||
Default: 16
|
||||
|
||||
Set the font size used for the REPL and the console. This will be
|
||||
multiplied by "scale."
|
||||
+78
-43
@@ -8,28 +8,64 @@ You can encode files from one format/codec to another using this facility.
|
||||
|
||||
``--of=<format>``
|
||||
Specifies the output format (overrides autodetection by the file name
|
||||
extension of the file specified by ``-o``). See ``--of=help`` for a full
|
||||
list of supported formats.
|
||||
extension of the file specified by ``-o``). This can be a comma separated
|
||||
list of possible formats to try. See ``--of=help`` for a full list of
|
||||
supported formats.
|
||||
|
||||
``--ofopts=<options>``
|
||||
Specifies the output format options for libavformat.
|
||||
See ``--ofopts=help`` for a full list of supported options.
|
||||
|
||||
This is a key/value list option. See `List Options`_ for details.
|
||||
Options are managed in lists. There are a few commands to manage the
|
||||
options list.
|
||||
|
||||
``--ofopts-add=<options1[,options2,...]>``
|
||||
Appends the options given as arguments to the options list.
|
||||
|
||||
``--ofopts=""``
|
||||
``--ofopts-pre=<options1[,options2,...]>``
|
||||
Prepends the options given as arguments to the options list.
|
||||
|
||||
``--ofopts-del=<index1[,index2,...]>``
|
||||
Deletes the options at the given indexes. Index numbers start at 0,
|
||||
negative numbers address the end of the list (-1 is the last).
|
||||
|
||||
``--ofopts-clr``
|
||||
Completely empties the options list.
|
||||
|
||||
``--ofps=<float value>``
|
||||
Specifies the output format time base (default: 24000). Low values like 25
|
||||
limit video fps by dropping frames.
|
||||
|
||||
``--oautofps``
|
||||
Sets the output format time base to the guessed frame rate of the input
|
||||
video (simulates MEncoder behavior, useful for AVI; may cause frame drops).
|
||||
Note that not all codecs and not all formats support VFR encoding, and some
|
||||
which do have bugs when a target bitrate is specified - use ``--ofps`` or
|
||||
``--oautofps`` to force CFR encoding in these cases.
|
||||
|
||||
``--omaxfps=<float value>``
|
||||
Specifies the minimum distance of adjacent frames (default: 0, which means
|
||||
unset). Content of lower frame rate is not readjusted to this frame rate;
|
||||
content of higher frame rate is decimated to this frame rate.
|
||||
|
||||
``--oharddup``
|
||||
If set, the frame rate given by ``--ofps`` is attained not by skipping time
|
||||
codes, but by duplicating frames (constant frame rate mode).
|
||||
|
||||
``--oneverdrop``
|
||||
If set, frames are never dropped. Instead, time codes of video are
|
||||
readjusted to always increase. This may cause AV desync, though; to work
|
||||
around this, use a high-fps time base using ``--ofps`` and absolutely
|
||||
avoid ``--oautofps``.
|
||||
|
||||
``--oac=<codec>``
|
||||
Specifies the output audio codec. See ``--oac=help`` for a full list of
|
||||
supported codecs.
|
||||
Specifies the output audio codec. This can be a comma separated list of
|
||||
possible codecs to try. See ``--oac=help`` for a full list of supported
|
||||
codecs.
|
||||
|
||||
``--oaoffset=<value>``
|
||||
Shifts audio data by the given time (in seconds) by adding/removing
|
||||
samples at the start. Deprecated.
|
||||
samples at the start.
|
||||
|
||||
``--oacopts=<options>``
|
||||
Specifies the output audio codec options for libavcodec.
|
||||
@@ -40,27 +76,36 @@ You can encode files from one format/codec to another using this facility.
|
||||
"``--oac=libmp3lame --oacopts=b=128000``"
|
||||
selects 128 kbps MP3 encoding.
|
||||
|
||||
This is a key/value list option. See `List Options`_ for details.
|
||||
Options are managed in lists. There are a few commands to manage the
|
||||
options list.
|
||||
|
||||
``--oacopts-add=<options1[,options2,...]>``
|
||||
Appends the options given as arguments to the options list.
|
||||
|
||||
``--oacopts=""``
|
||||
``--oacopts-pre=<options1[,options2,...]>``
|
||||
Prepends the options given as arguments to the options list.
|
||||
|
||||
``--oacopts-del=<index1[,index2,...]>``
|
||||
Deletes the options at the given indexes. Index numbers start at 0,
|
||||
negative numbers address the end of the list (-1 is the last).
|
||||
|
||||
``--oacopts-clr``
|
||||
Completely empties the options list.
|
||||
|
||||
``--oafirst``
|
||||
Force the audio stream to become the first stream in the output.
|
||||
By default, the order is unspecified. Deprecated.
|
||||
By default, the order is unspecified.
|
||||
|
||||
``--ovc=<codec>``
|
||||
Specifies the output video codec. See ``--ovc=help`` for a full list of
|
||||
supported codecs.
|
||||
Specifies the output video codec. This can be a comma separated list of
|
||||
possible codecs to try. See ``--ovc=help`` for a full list of supported
|
||||
codecs.
|
||||
|
||||
``--ovoffset=<value>``
|
||||
Shifts video data by the given time (in seconds) by shifting the pts
|
||||
values. Deprecated.
|
||||
values.
|
||||
|
||||
``--ovcopts=<options>``
|
||||
``--ovcopts <options>``
|
||||
Specifies the output video codec options for libavcodec.
|
||||
See --ovcopts=help for a full list of supported options.
|
||||
|
||||
@@ -72,17 +117,32 @@ You can encode files from one format/codec to another using this facility.
|
||||
``"--ovc=libx264 --ovcopts=crf=23"``
|
||||
selects VBR quality factor 23 for H.264 encoding.
|
||||
|
||||
This is a key/value list option. See `List Options`_ for details.
|
||||
Options are managed in lists. There are a few commands to manage the
|
||||
options list.
|
||||
|
||||
``--ovcopts-add=<options1[,options2,...]>``
|
||||
Appends the options given as arguments to the options list.
|
||||
|
||||
``--ovcopts=""``
|
||||
``--ovcopts-pre=<options1[,options2,...]>``
|
||||
Prepends the options given as arguments to the options list.
|
||||
|
||||
``--ovcopts-del=<index1[,index2,...]>``
|
||||
Deletes the options at the given indexes. Index numbers start at 0,
|
||||
negative numbers address the end of the list (-1 is the last).
|
||||
|
||||
``--ovcopts-clr``
|
||||
Completely empties the options list.
|
||||
|
||||
``--ovfirst``
|
||||
Force the video stream to become the first stream in the output.
|
||||
By default, the order is unspecified. Deprecated.
|
||||
By default, the order is unspecified.
|
||||
|
||||
``--ocopyts``
|
||||
Copies input pts to the output video (not supported by some output
|
||||
container formats, e.g. AVI). Discontinuities are still fixed.
|
||||
By default, audio pts are set to playback time and video pts are
|
||||
synchronized to match audio pts, as some output formats do not support
|
||||
anything else.
|
||||
|
||||
``--orawts``
|
||||
Copies input pts to the output video (not supported by some output
|
||||
@@ -90,31 +150,6 @@ You can encode files from one format/codec to another using this facility.
|
||||
and all pts are passed through as-is. Never seek backwards or use multiple
|
||||
input files in this mode!
|
||||
|
||||
``--no-ocopy-metadata``
|
||||
``--no-ometadata``
|
||||
Turns off copying of metadata from input files to output files when
|
||||
encoding (which is enabled by default).
|
||||
|
||||
``--oset-metadata=<metadata-tag[,metadata-tag,...]>``
|
||||
Specifies metadata to include in the output file.
|
||||
Supported keys vary between output formats. For example, Matroska (MKV) and
|
||||
FLAC allow almost arbitrary keys, while support in MP4 and MP3 is more
|
||||
limited.
|
||||
|
||||
This is a key/value list option. See `List Options`_ for details.
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
"``--oset-metadata=title="Output title",comment="Another tag"``"
|
||||
adds a title and a comment to the output file.
|
||||
|
||||
``--oremove-metadata=<metadata-tag[,metadata-tag,...]>``
|
||||
Specifies metadata to exclude from the output file when copying from the
|
||||
input file.
|
||||
|
||||
This is a string list option. See `List Options`_ for details.
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
"``--oremove-metadata=comment,genre``"
|
||||
excludes copying of the the comment and genre tags to the output
|
||||
file.
|
||||
+279
-876
File diff suppressed because it is too large.
Load diff
+10
-41
@@ -74,12 +74,6 @@ some wrapper like .NET's NamedPipeClientStream.)
|
||||
Protocol
|
||||
--------
|
||||
|
||||
The protocol uses UTF-8-only JSON as defined by RFC-8259. Unlike standard JSON,
|
||||
"\u" escape sequences are not allowed to construct surrogate pairs. To avoid
|
||||
getting conflicts, encode all text characters including and above codepoint
|
||||
U+0020 as UTF-8. mpv might output broken UTF-8 in corner cases (see "UTF-8"
|
||||
section below).
|
||||
|
||||
Clients can execute commands on the player by sending JSON messages of the
|
||||
following form:
|
||||
|
||||
@@ -114,10 +108,7 @@ Because events can occur at any time, it may be difficult at times to determine
|
||||
which response goes with which command. Commands may optionally include a
|
||||
``request_id`` which, if provided in the command request, will be copied
|
||||
verbatim into the response. mpv does not intrepret the ``request_id`` in any
|
||||
way; it is solely for the use of the requester. The only requirement is that
|
||||
the ``request_id`` field must be an integer (a number without fractional parts
|
||||
in the range ``-2^63..2^63-1``). Using other types is deprecated and will
|
||||
currently show a warning. In the future, this will raise an error.
|
||||
way; it is solely for the use of the requester.
|
||||
|
||||
For example, this request:
|
||||
|
||||
@@ -131,11 +122,6 @@ Would generate this response:
|
||||
|
||||
{ "error": "success", "data": 1.468135, "request_id": 100 }
|
||||
|
||||
If you don't specify a ``request_id``, command replies will set it to 0.
|
||||
|
||||
Commands may run asynchronously in the future, instead of blocking the socket
|
||||
until a reply is sent.
|
||||
|
||||
All commands, replies, and events are separated from each other with a line
|
||||
break character (``\n``).
|
||||
|
||||
@@ -194,7 +180,14 @@ extra commands can also be used as part of the protocol:
|
||||
{ "error": "success" }
|
||||
|
||||
``set_property_string``
|
||||
Alias for ``set_property``. Both commands accept native values and strings.
|
||||
Like ``set_property``, but the argument value must be passed as string.
|
||||
|
||||
Example:
|
||||
|
||||
::
|
||||
|
||||
{ "command": ["set_property_string", "pause", "yes"] }
|
||||
{ "error": "success" }
|
||||
|
||||
``observe_property``
|
||||
Watch a property for changes. If the given property is changed, then an
|
||||
@@ -272,28 +265,4 @@ sometimes sends invalid JSON. If that is a problem for the client application's
|
||||
parser, it should filter the raw data for invalid UTF-8 sequences and perform
|
||||
the desired replacement, before feeding the data to its JSON parser.
|
||||
|
||||
mpv will not attempt to construct invalid UTF-8 with broken "\u" escape
|
||||
sequences. This includes surrogate pairs.
|
||||
|
||||
JSON extensions
|
||||
---------------
|
||||
|
||||
The following non-standard extensions are supported:
|
||||
|
||||
- a list or object item can have a trailing ","
|
||||
- object syntax accepts "=" in addition of ":"
|
||||
- object keys can be unquoted, if they start with a character in "A-Za-z\_"
|
||||
and contain only characters in "A-Za-z0-9\_"
|
||||
- byte escapes with "\xAB" are allowed (with AB being a 2 digit hex number)
|
||||
|
||||
Example:
|
||||
|
||||
::
|
||||
|
||||
{ objkey = "value\x0A" }
|
||||
|
||||
Is equivalent to:
|
||||
|
||||
::
|
||||
|
||||
{ "objkey": "value\n" }
|
||||
mpv will not attempt to construct invalid UTF-8 with broken escape sequences.
|
||||
+21
-55
@@ -27,16 +27,16 @@ otherwise, the documented Lua options, script directories, loading, etc apply to
|
||||
JavaScript files too.
|
||||
|
||||
Script initialization and lifecycle is the same as with Lua, and most of the Lua
|
||||
functions at the modules ``mp``, ``mp.utils``, ``mp.msg`` and ``mp.options`` are
|
||||
available to JavaScript with identical APIs - including running commands,
|
||||
getting/setting properties, registering events/key-bindings/hooks, etc.
|
||||
functions at the modules ``mp``, ``mp.utils`` and ``mp.msg`` are available to
|
||||
JavaScript with identical APIs - including running commands, getting/setting
|
||||
properties, registering events/key-bindings/property-changes/hooks, etc.
|
||||
|
||||
Differences from Lua
|
||||
--------------------
|
||||
|
||||
No need to load modules. ``mp``, ``mp.utils``, ``mp.msg`` and ``mp.options``
|
||||
are preloaded, and you can use e.g. ``var cwd = mp.utils.getcwd();`` without
|
||||
prior setup.
|
||||
No need to load modules. ``mp``, ``mp.utils`` and ``mp.msg`` are preloaded, and
|
||||
you can use e.g. ``var cwd = mp.utils.getcwd();`` without prior setup.
|
||||
``mp.options`` is currently not implemented, but ``mp.get_opt(...)`` is.
|
||||
|
||||
Errors are slightly different. Where the Lua APIs return ``nil`` for error,
|
||||
the JavaScript ones return ``undefined``. Where Lua returns ``something, error``
|
||||
@@ -67,6 +67,10 @@ Unsupported Lua APIs and their JS alternatives
|
||||
|
||||
``mp.add_periodic_timer(seconds, fn)`` JS: ``id = setInterval(fn, ms)``
|
||||
|
||||
``mp.register_idle(fn)`` JS: ``id = setTimeout(fn)``
|
||||
|
||||
``mp.unregister_idle(fn)`` JS: ``clearTimeout(id)``
|
||||
|
||||
``utils.parse_json(str [, trail])`` JS: ``JSON.parse(str)``
|
||||
|
||||
``utils.format_json(v)`` JS: ``JSON.stringify(v)``
|
||||
@@ -83,12 +87,14 @@ Unsupported Lua APIs and their JS alternatives
|
||||
|
||||
``mp.dispatch_events([allow_wait])`` see event loop below.
|
||||
|
||||
``mp.options`` module is not implemented currently for JS.
|
||||
|
||||
Scripting APIs - identical to Lua
|
||||
---------------------------------
|
||||
|
||||
(LE) - Last-Error, indicates that ``mp.last_error()`` can be used after the
|
||||
call to test for success (empty string) or failure (non empty reason string).
|
||||
Where the Lua APIs use ``nil`` to indicate error, JS APIs use ``undefined``.
|
||||
Otherwise, where the Lua APIs return ``nil`` on error, JS returns ``undefined``.
|
||||
|
||||
``mp.command(string)`` (LE)
|
||||
|
||||
@@ -96,11 +102,6 @@ Where the Lua APIs use ``nil`` to indicate error, JS APIs use ``undefined``.
|
||||
|
||||
``mp.command_native(table [,def])`` (LE)
|
||||
|
||||
``id = mp.command_native_async(table [,fn])`` (LE) Notes: ``id`` is true-thy on
|
||||
success, ``fn`` is called always a-sync, ``error`` is empty string on success.
|
||||
|
||||
``mp.abort_async_command(id)``
|
||||
|
||||
``mp.get_property(name [,def])`` (LE)
|
||||
|
||||
``mp.get_property_osd(name [,def])`` (LE)
|
||||
@@ -143,20 +144,12 @@ success, ``fn`` is called always a-sync, ``error`` is empty string on success.
|
||||
|
||||
``mp.get_wakeup_pipe()``
|
||||
|
||||
``mp.register_idle(fn)``
|
||||
|
||||
``mp.unregister_idle(fn)``
|
||||
|
||||
``mp.enable_messages(level)``
|
||||
|
||||
``mp.register_script_message(name, fn)``
|
||||
|
||||
``mp.unregister_script_message(name)``
|
||||
|
||||
``mp.create_osd_overlay(format)``
|
||||
|
||||
``mp.get_osd_size()`` (returned object has properties: width, height, aspect)
|
||||
|
||||
``mp.msg.log(level, ...)``
|
||||
|
||||
``mp.msg.fatal(...)``
|
||||
@@ -187,13 +180,8 @@ success, ``fn`` is called always a-sync, ``error`` is empty string on success.
|
||||
|
||||
``mp.utils.subprocess_detached(t)``
|
||||
|
||||
``mp.utils.getpid()`` (LE)
|
||||
|
||||
``mp.add_hook(type, priority, fn)``
|
||||
|
||||
``mp.options.read_options(obj [, identifier [, on_update]])`` (types:
|
||||
string/boolean/number)
|
||||
|
||||
Additional utilities
|
||||
--------------------
|
||||
|
||||
@@ -245,9 +233,6 @@ Note: ``read_file`` and ``write_file`` throw on errors, allow text content only.
|
||||
anything from the filesystem), and returns it as a function. Very similar
|
||||
to a ``Function`` constructor, but shows at stack traces as ``fname``.
|
||||
|
||||
``mp.module_paths``
|
||||
Global modules search paths array for the ``require`` function (see below).
|
||||
|
||||
Timers (global)
|
||||
---------------
|
||||
|
||||
@@ -280,17 +265,15 @@ event loop iteration or at a later event loop iteration. This is true also for
|
||||
intervals - which also never call back twice at the same event loop iteration.
|
||||
|
||||
Additionally, timers are processed after the event queue is empty, so it's valid
|
||||
to use ``setTimeout(fn)`` as a one-time idle observer.
|
||||
to use ``setTimeout(fn)`` instead of Lua's ``mp.register_idle(fn)``.
|
||||
|
||||
CommonJS modules and ``require(id)``
|
||||
------------------------------------
|
||||
|
||||
CommonJS Modules are a standard system where scripts can export common functions
|
||||
for use by other scripts. Specifically, a module is a script which adds
|
||||
properties (functions, etc) to its pre-existing ``exports`` object, which
|
||||
another script can access with ``require(module-id)``. This runs the module and
|
||||
returns its ``exports`` object. Further calls to ``require`` for the same module
|
||||
will return its cached ``exports`` object without running the module again.
|
||||
for use by other scripts. A module is a script which adds properties (functions,
|
||||
etc) to its invisible ``exports`` object, which another script can access by
|
||||
loading it with ``require(module-id)`` - which returns that ``exports`` object.
|
||||
|
||||
Modules and ``require`` are supported, standard compliant, and generally similar
|
||||
to node.js. However, most node.js modules won't run due to missing modules such
|
||||
@@ -303,16 +286,10 @@ will load the file ``./foo.js`` and return its ``exports`` object.
|
||||
An id is relative (to the script which ``require``'d it) if it starts with
|
||||
``./`` or ``../``. Otherwise, it's considered a "top-level id" (CommonJS term).
|
||||
|
||||
Top level id is evaluated as absolute filesystem path if possible, e.g. ``/x/y``
|
||||
or ``~/x``. Otherwise, it's considered a global module id and searched at
|
||||
``scripts/modules.js/`` in mpv config dirs - in normal config search order. E.g.
|
||||
``require("x")`` is searched as file ``x.js`` at those dirs, and id ``foo/x`` is
|
||||
searched as file ``x.js`` inside dir ``foo`` at those dirs.
|
||||
|
||||
Search paths for global module id's are at the array ``mp.module_paths``, which
|
||||
is searched in order. Initially it contains one item: ``~~/scripts/modules.js``
|
||||
such that it behaves as described above. Modifying it will affect future
|
||||
``require`` calls with global module id's which are not already loaded/cached.
|
||||
Top level id is evaluated as absolute filesystem path if possible (e.g. ``/x/y``
|
||||
or ``~/x``). Otherwise, it's searched at ``scripts/modules.js/`` in mpv config
|
||||
dirs - in normal config search order. E.g. ``require("x")`` is searched as file
|
||||
``x.js`` at those dirs, and id ``foo/x`` is searched as file ``foo/x.js``.
|
||||
|
||||
No ``global`` variable, but a module's ``this`` at its top lexical scope is the
|
||||
global object - also in strict mode. If you have a module which needs ``global``
|
||||
@@ -341,10 +318,6 @@ also print every event which mpv sends to your script:
|
||||
wait = 0;
|
||||
} else {
|
||||
wait = mp.process_timers() / 1000;
|
||||
if (wait != 0) {
|
||||
mp.notify_idle_observers();
|
||||
wait = mp.peek_timers_wait() / 1000;
|
||||
}
|
||||
}
|
||||
} while (mp.keep_running);
|
||||
}
|
||||
@@ -364,12 +337,5 @@ if there are such (event handlers, property observers, script messages, etc).
|
||||
and returns the duration in ms till the next due timer (possibly 0), or -1 if
|
||||
there are no pending timers. Must not be called recursively.
|
||||
|
||||
``mp.notify_idle_observers()`` calls back the idle observers, which we do when
|
||||
we're about to sleep (wait != 0), but the observers may add timers or take
|
||||
non-negligible duration to complete, so we re-calculate ``wait`` afterwards.
|
||||
|
||||
``mp.peek_timers_wait()`` returns the same values as ``mp.process_timers()``
|
||||
but without doing anything. Invalid result if called from a timer callback.
|
||||
|
||||
Note: ``exit()`` is also registered for the ``shutdown`` event, and its
|
||||
implementation is a simple ``mp.keep_running = false``.
|
||||
+50
-142
@@ -63,10 +63,7 @@ the player will more or less hang until the script returns from the main chunk
|
||||
(and ``mp_event_loop`` is called), or the script calls ``mp_event_loop`` or
|
||||
``mp.dispatch_events`` directly. This is done to make it possible for a script
|
||||
to fully setup event handlers etc. before playback actually starts. In older
|
||||
mpv versions, this happened asynchronously. With mpv 0.29.0, this changes
|
||||
slightly, and it merely waits for scripts to be loaded in this manner before
|
||||
starting playback as part of the player initialization phase. Scripts run though
|
||||
initialization in parallel. This might change again.
|
||||
mpv versions, this happened asynchronously.
|
||||
|
||||
mp functions
|
||||
------------
|
||||
@@ -109,43 +106,12 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
``mp.command_native(table [,def])``
|
||||
Similar to ``mp.commandv``, but pass the argument list as table. This has
|
||||
the advantage that in at least some cases, arguments can be passed as
|
||||
native types. It also allows you to use named argument.
|
||||
|
||||
If the table is an array, each array item is like an argument in
|
||||
``mp.commandv()`` (but can be a native type instead of a string).
|
||||
|
||||
If the table contains string keys, it's interpreted as command with named
|
||||
arguments. This requires at least an entry with the key ``name`` to be
|
||||
present, which must be a string, and contains the command name. The special
|
||||
entry ``_flags`` is optional, and if present, must be an array of
|
||||
`Input Command Prefixes`_ to apply. All other entries are interpreted as
|
||||
arguments.
|
||||
native types.
|
||||
|
||||
Returns a result table on success (usually empty), or ``def, error`` on
|
||||
error. ``def`` is the second parameter provided to the function, and is
|
||||
nil if it's missing.
|
||||
|
||||
``mp.command_native_async(table [,fn])``
|
||||
Like ``mp.command_native()``, but the command is ran asynchronously (as far
|
||||
as possible), and upon completion, fn is called. fn has two arguments:
|
||||
``fn(success, result, error)``. ``success`` is always a Boolean and is true
|
||||
if the command was successful, otherwise false. The second parameter is
|
||||
the result value (can be nil) in case of success, nil otherwise (as returned
|
||||
by ``mp.command_native()``). The third parameter is the error string in case
|
||||
of an error, nil otherwise.
|
||||
|
||||
Returns a table with undefined contents, which can be used as argument for
|
||||
``mp.abort_async_command``.
|
||||
|
||||
If starting the command failed for some reason, ``nil, error`` is returned,
|
||||
and ``fn`` is called indicating failure, using the same error value.
|
||||
|
||||
``mp.abort_async_command(t)``
|
||||
Abort a ``mp.command_native_async`` call. The argument is the return value
|
||||
of that command (which starts asynchronous execution of the command).
|
||||
Whether this works and how long it takes depends on the command and the
|
||||
situation. The abort call itself is asynchronous. Does not return anything.
|
||||
|
||||
``mp.get_property(name [,def])``
|
||||
Return the value of the given property as string. These are the same
|
||||
properties as used in input.conf. See `Properties`_ for a list of
|
||||
@@ -240,9 +206,7 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
an example). The name should be unique across other bindings in the same
|
||||
script - if not, the previous binding with the same name will be
|
||||
overwritten. You can omit the name, in which case a random name is generated
|
||||
internally. (Omitting works as follows: either pass ``nil`` for ``name``,
|
||||
or pass the ``fn`` argument in place of the name. The latter is not
|
||||
recommended and is handled for compatibility only.)
|
||||
internally.
|
||||
|
||||
The last argument is used for optional flags. This is a table, which can
|
||||
have the following entries:
|
||||
@@ -253,24 +217,11 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
``complex``
|
||||
If set to ``true``, then ``fn`` is called on both key up and down
|
||||
events (as well as key repeat, if enabled), with the first
|
||||
argument being a table. This table has the following entries (and
|
||||
may contain undocumented ones):
|
||||
|
||||
``event``
|
||||
Set to one of the strings ``down``, ``repeat``, ``up`` or
|
||||
``press`` (the latter if key up/down can't be tracked).
|
||||
|
||||
``is_mouse``
|
||||
Boolean Whether the event was caused by a mouse button.
|
||||
|
||||
``key_name``
|
||||
The name of they key that triggered this, or ``nil`` if invoked
|
||||
artificially. If the key name is unknown, it's an empty string.
|
||||
|
||||
``key_text``
|
||||
Text if triggered by a text key, otherwise ``nil``. See
|
||||
description of ``script-binding`` command for details (this
|
||||
field is equivalent to the 5th argument).
|
||||
argument being a table. This table has an ``event`` entry, which
|
||||
is set to one of the strings ``down``, ``repeat``, ``up`` or
|
||||
``press`` (the latter if key up/down can't be tracked). It further
|
||||
has an ``is_mouse`` entry, which tells whether the event was caused
|
||||
by a mouse button.
|
||||
|
||||
Internally, key bindings are dispatched via the ``script-message-to`` or
|
||||
``script-binding`` input commands and ``mp.register_script_message``.
|
||||
@@ -358,21 +309,13 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
of times in a row, only the last change triggers the change function. (The
|
||||
exact behavior depends on timing and other things.)
|
||||
|
||||
If a property is unavailable, or on error, the value argument to ``fn`` is
|
||||
``nil``. (The ``observe_property()`` call always succeeds, even if a
|
||||
property does not exist.)
|
||||
|
||||
In some cases the function is not called even if the property changes.
|
||||
This depends on the property, and it's a valid feature request to ask for
|
||||
better update handling of a specific property.
|
||||
Whether this can happen depends on the property.
|
||||
|
||||
If the ``type`` is ``none`` or ``nil``, sporadic property change events are
|
||||
possible. This means the change function ``fn`` can be called even if the
|
||||
property doesn't actually change.
|
||||
|
||||
You always get an initial change notification. This is meant to initialize
|
||||
the user's state to the current value of the property.
|
||||
|
||||
``mp.unobserve_property(fn)``
|
||||
Undo ``mp.observe_property(..., fn)``. This removes all property handlers
|
||||
that are equal to the ``fn`` parameter. This uses normal Lua ``==``
|
||||
@@ -427,7 +370,7 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
(``true``). This value is used when the timer expires (but before
|
||||
the timer callback function fn is run).
|
||||
|
||||
Note that these are methods, and you have to call them using ``:`` instead
|
||||
Note that these are method, and you have to call them using ``:`` instead
|
||||
of ``.`` (Refer to http://www.lua.org/manual/5.2/manual.html#3.4.9 .)
|
||||
|
||||
Example:
|
||||
@@ -484,7 +427,7 @@ are useful only in special situations.
|
||||
|
||||
``mp.get_wakeup_pipe()``
|
||||
Calls ``mpv_get_wakeup_pipe()`` and returns the read end of the wakeup
|
||||
pipe. This is deprecated, but still works. (See ``client.h`` for details.)
|
||||
pipe. (See ``client.h`` for details.)
|
||||
|
||||
``mp.get_next_timeout()``
|
||||
Return the relative time in seconds when the next timer (``mp.add_timeout``
|
||||
@@ -536,41 +479,6 @@ are useful only in special situations.
|
||||
Undo a previous registration with ``mp.register_script_message``. Does
|
||||
nothing if the ``name`` wasn't registered.
|
||||
|
||||
``mp.create_osd_overlay(format)``
|
||||
Create an OSD overlay. This is a very thin wrapper around the ``osd-overlay``
|
||||
command. The function returns a table, which mostly contains fields that
|
||||
will be passed to ``osd-overlay``. The ``format`` parameter is used to
|
||||
initialize the ``format`` field. The ``data`` field contains the text to
|
||||
be used as overlay. For details, see the ``osd-overlay`` command.
|
||||
|
||||
In addition, it provides the following methods:
|
||||
|
||||
``update()``
|
||||
Commit the OSD overlay to the screen, or in other words, run the
|
||||
``osd-overlay`` command with the current fields of the overlay table.
|
||||
|
||||
``remove()``
|
||||
Remove the overlay from the screen. A ``update()`` call will add it
|
||||
again.
|
||||
|
||||
Example:
|
||||
|
||||
::
|
||||
|
||||
ov = mp.create_osd_overlay("ass-events")
|
||||
ov.data = "{\\an5}{\\b1}hello world!"
|
||||
ov:update()
|
||||
|
||||
The advantage of using this wrapper (as opposed to running ``osd-overlay``
|
||||
directly) is that the ``id`` field is allocated automatically.
|
||||
|
||||
``mp.get_osd_size()``
|
||||
Returns a tuple of ``osd_width, osd_height, osd_par``. The first two give
|
||||
the size of the OSD in pixels (for video ouputs like ``--vo=xv``, this may
|
||||
be "scaled" pixels). The third is the display pixel aspect ratio.
|
||||
|
||||
May return invalid/nonsense values if OSD is not initialized yet.
|
||||
|
||||
mp.msg functions
|
||||
----------------
|
||||
|
||||
@@ -601,27 +509,14 @@ command-line. All you have to do is to supply a table with default options to
|
||||
the read_options function. The function will overwrite the default values
|
||||
with values found in the config-file and the command-line (in that order).
|
||||
|
||||
``options.read_options(table [, identifier [, on_update]])``
|
||||
``options.read_options(table [, identifier])``
|
||||
A ``table`` with key-value pairs. The type of the default values is
|
||||
important for converting the values read from the config file or
|
||||
command-line back. Do not use ``nil`` as a default value!
|
||||
|
||||
The ``identifier`` is used to identify the config-file and the command-line
|
||||
options. These needs to unique to avoid collisions with other scripts.
|
||||
Defaults to ``mp.get_script_name()`` if the parameter is ``nil`` or missing.
|
||||
|
||||
The ``on_update`` parameter enables run-time updates of all matching option
|
||||
values via the ``script-opts`` option/property. If any of the matching
|
||||
options changes, the values in the ``table`` (which was originally passed to
|
||||
the function) are changed, and ``on_update(list)`` is called. ``list`` is
|
||||
a table where each updated option has a ``list[option_name] = true`` entry.
|
||||
There is no initial ``on_update()`` call. This never re-reads the config file.
|
||||
``script-opts`` is always applied on the original config file, ignoring
|
||||
previous ``script-opts`` values (for example, if an option is removed from
|
||||
``script-opts`` at runtime, the option will have the value in the config
|
||||
file). ``table`` entries are only written for option values whose values
|
||||
effectively change (this is important if the script changes ``table``
|
||||
entries independently).
|
||||
Defaults to ``mp.get_script_name()``.
|
||||
|
||||
|
||||
Example implementation::
|
||||
@@ -636,7 +531,7 @@ Example implementation::
|
||||
print(options.optionA)
|
||||
|
||||
|
||||
The config file will be stored in ``script-opts/identifier.conf`` in mpv's user
|
||||
The config file will be stored in ``lua-settings/identifier.conf`` in mpv's user
|
||||
folder. Comment lines can be started with # and stray spaces are not removed.
|
||||
Boolean values will be represented with yes/no.
|
||||
|
||||
@@ -736,25 +631,45 @@ strictly part of the guaranteed API.
|
||||
|
||||
``utils.subprocess(t)``
|
||||
Runs an external process and waits until it exits. Returns process status
|
||||
and the captured output. This is a legacy wrapper around calling the
|
||||
``subprocess`` command with ``mp.command_native``. It does the following
|
||||
things:
|
||||
and the captured output.
|
||||
|
||||
- copy the table ``t``
|
||||
- rename ``cancellable`` field to ``playback_only``
|
||||
- rename ``max_size`` to ``capture_size``
|
||||
- set ``capture_stdout`` field to ``true`` if unset
|
||||
- set ``name`` field to ``subprocess``
|
||||
- call ``mp.command_native(copied_t)``
|
||||
- if the command failed, create a dummy result table
|
||||
- copy ``error_string`` to ``error`` field if the string is non-empty
|
||||
- return the result table
|
||||
The parameter ``t`` is a table. The function reads the following entries:
|
||||
|
||||
It is recommended to use ``mp.command_native`` or ``mp.command_native_async``
|
||||
directly, instead of calling this legacy wrapper. It is for compatibility
|
||||
only.
|
||||
``args``
|
||||
Array of strings. The first array entry is the executable. This
|
||||
can be either an absolute path, or a filename with no path
|
||||
components, in which case the ``PATH`` environment variable is
|
||||
used to resolve the executable. The other array elements are
|
||||
passed as command line arguments.
|
||||
|
||||
See the ``subprocess`` documentation for semantics and further parameters.
|
||||
``cancellable``
|
||||
Optional. If set to ``true`` (default), then if the user stops
|
||||
playback or goes to the next file while the process is running,
|
||||
the process will be killed.
|
||||
|
||||
``max_size``
|
||||
Optional. The maximum size in bytes of the data that can be captured
|
||||
from stdout. (Default: 16 MB.)
|
||||
|
||||
The function returns a table as result with the following entries:
|
||||
|
||||
``status``
|
||||
The raw exit status of the process. It will be negative on error.
|
||||
|
||||
``stdout``
|
||||
Captured output stream as string, limited to ``max_size``.
|
||||
|
||||
``error``
|
||||
``nil`` on success. The string ``killed`` if the process was
|
||||
terminated in an unusual way. The string ``init`` if the process
|
||||
could not be started.
|
||||
|
||||
On Windows, ``killed`` is only returned when the process has been
|
||||
killed by mpv as a result of ``cancellable`` being set to ``true``.
|
||||
|
||||
``killed_by_us``
|
||||
Set to ``true`` if the process has been killed by mpv as a result
|
||||
of ``cancellable`` being set to ``true``.
|
||||
|
||||
``utils.subprocess_detached(t)``
|
||||
Runs an external process and detaches it from mpv's control.
|
||||
@@ -767,13 +682,6 @@ strictly part of the guaranteed API.
|
||||
|
||||
The function returns ``nil``.
|
||||
|
||||
This is a legacy wrapper around calling the ``run`` command with
|
||||
``mp.commandv`` and other functions.
|
||||
|
||||
``utils.getpid()``
|
||||
Returns the process ID of the running mpv process. This can be used to identify
|
||||
the calling mpv when launching (detached) subprocesses.
|
||||
|
||||
``utils.parse_json(str [, trail])``
|
||||
Parses the given string argument as JSON, and returns it as a Lua table. On
|
||||
error, returns ``nil, error``. (Currently, ``error`` is just a string
|
||||
|
||||
+107
-288
@@ -58,10 +58,6 @@ 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
|
||||
Adjust subtitle delay so that the next or previous subtitle is displayed
|
||||
now. This is especially useful to sync subtitles to audio.
|
||||
|
||||
[ and ]
|
||||
Decrease/increase current playback speed by 10%.
|
||||
|
||||
@@ -71,17 +67,6 @@ Ctrl+Shift+Left and Ctrl+Shift+Right
|
||||
BACKSPACE
|
||||
Reset playback speed to normal.
|
||||
|
||||
Shift+BACKSPACE
|
||||
Undo the last seek. This works only if the playlist entry was not changed.
|
||||
Hitting it a second time will go back to the original position.
|
||||
See ``revert-seek`` command for details.
|
||||
|
||||
Shift+Ctrl+BACKSPACE
|
||||
Mark the current position. This will then be used by ``Shift+BACKSPACE``
|
||||
as revert position (once you seek back, the marker will be reset). You can
|
||||
use this to seek around in the file and then return to the exact position
|
||||
where you left off.
|
||||
|
||||
< and >
|
||||
Go backward/forward in the playlist.
|
||||
|
||||
@@ -130,9 +115,8 @@ ESC
|
||||
T
|
||||
Toggle stay-on-top (see also ``--ontop``).
|
||||
|
||||
w and W
|
||||
Decrease/increase pan-and-scan range. The ``e`` key does the same as
|
||||
``W`` currently, but use is discouraged.
|
||||
w and e
|
||||
Decrease/increase pan-and-scan range.
|
||||
|
||||
o (also P)
|
||||
Show progression bar, elapsed time and total duration on the OSD.
|
||||
@@ -146,9 +130,8 @@ v
|
||||
j and J
|
||||
Cycle through the available subtitles.
|
||||
|
||||
z and Z
|
||||
Adjust subtitle delay by +/- 0.1 seconds. The ``x`` key does the same as
|
||||
``Z`` currently, but use is discouraged.
|
||||
x and z
|
||||
Adjust subtitle delay by +/- 0.1 seconds.
|
||||
|
||||
l
|
||||
Set/clear A-B loop points. See ``ab-loop`` command for details.
|
||||
@@ -168,9 +151,8 @@ V
|
||||
Toggle subtitle VSFilter aspect compatibility mode. See
|
||||
``--sub-ass-vsfilter-aspect-compat`` for more info.
|
||||
|
||||
r and R
|
||||
Move subtitles up/down. The ``t`` key does the same as ``R`` currently, but
|
||||
use is discouraged.
|
||||
r and t
|
||||
Move subtitles up/down.
|
||||
|
||||
s
|
||||
Take a screenshot.
|
||||
@@ -198,39 +180,8 @@ d
|
||||
A
|
||||
Cycle aspect ratio override.
|
||||
|
||||
Ctrl h
|
||||
Toggle hardware video decoding on/off.
|
||||
|
||||
Alt+LEFT, Alt+RIGHT, Alt+UP, Alt+DOWN
|
||||
Move the video rectangle (panning).
|
||||
|
||||
Alt + and Alt -
|
||||
Combining ``Alt`` with the ``+`` or ``-`` keys changes video zoom.
|
||||
|
||||
Alt+BACKSPACE
|
||||
Reset the pan/zoom settings.
|
||||
|
||||
F8
|
||||
Show the playlist and the current position in it (useful only if a UI window
|
||||
is used, broken on the terminal).
|
||||
|
||||
F9
|
||||
Show the list of audio and subtitle streams (useful only if a UI window is
|
||||
used, broken on the terminal).
|
||||
|
||||
i and I
|
||||
Show/toggle an overlay displaying statistics about the currently playing
|
||||
file such as codec, framerate, number of dropped frames and so on. See
|
||||
`STATS`_ for more information.
|
||||
|
||||
del
|
||||
Cycles visibility between never / auto (mouse-move) / always
|
||||
|
||||
\`
|
||||
Show the console. (ESC closes it again. See `CONSOLE`_.)
|
||||
|
||||
(The following keys are valid only when using a video output that supports the
|
||||
corresponding adjustment.)
|
||||
corresponding adjustment, or the software equalizer (``--vf=eq``).)
|
||||
|
||||
1 and 2
|
||||
Adjust contrast.
|
||||
@@ -274,19 +225,10 @@ in the mpv git repository.
|
||||
Mouse Control
|
||||
-------------
|
||||
|
||||
Left double click
|
||||
Toggle fullscreen on/off.
|
||||
button 3 and button 4
|
||||
Seek backward/forward 1 minute.
|
||||
|
||||
Right click
|
||||
Toggle pause on/off.
|
||||
|
||||
Forward/Back button
|
||||
Skip to next/previous entry in playlist.
|
||||
|
||||
Wheel up/down
|
||||
Seek forward/backward 10 seconds.
|
||||
|
||||
Wheel left/right
|
||||
button 5 and button 6
|
||||
Decrease/increase volume.
|
||||
|
||||
|
||||
@@ -478,98 +420,73 @@ List Options
|
||||
------------
|
||||
|
||||
Some options which store lists of option values can have action suffixes. For
|
||||
example, the ``--display-tags`` option takes a ``,``-separated list of tags, but
|
||||
the option also allows you to append a single tag with ``--display-tags-append``,
|
||||
and the tag name can for example contain a literal ``,`` without the need for
|
||||
escaping.
|
||||
example, you can set a ``,``-separated list of filters with ``--vf``, but the
|
||||
option also allows you to append filters with ``--vf-append``.
|
||||
|
||||
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 ``,``.
|
||||
|
||||
They support the following operations:
|
||||
Options for filenames do not use ``,`` as separator, but ``:`` (Unix) or ``;``
|
||||
(Windows).
|
||||
|
||||
============= ===============================================
|
||||
Suffix Meaning
|
||||
============= ===============================================
|
||||
-set Set a list of items (using the list separator, interprets escapes)
|
||||
-append Append single item (does not interpret escapes)
|
||||
-add Append 1 or more items (same syntax as -set)
|
||||
-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)
|
||||
-add Append 1 or more items (may become alias for -append)
|
||||
-append Append single item (avoids need for escaping)
|
||||
-clr Clear the option
|
||||
-del Delete an existing item by integer index
|
||||
-pre Prepend 1 or more items
|
||||
-set Set a list of items
|
||||
============= ===============================================
|
||||
|
||||
``-append`` is meant as a simple way to append a single item without having
|
||||
to escape the argument (you may still need to escape on the shell level).
|
||||
Although some operations allow specifying multiple ``,``-separated items, using
|
||||
this is strongly discouraged and deprecated, except for ``-set``.
|
||||
|
||||
Key/value list options
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A key/value list is a list of key/value string pairs. In programming languages,
|
||||
this type of data structure is often called a map or a dictionary. The order
|
||||
normally does not matter, although in some cases the order might matter.
|
||||
|
||||
They support the following operations:
|
||||
|
||||
============= ===============================================
|
||||
Suffix Meaning
|
||||
============= ===============================================
|
||||
-set Set a list of items (using ``,`` as separator)
|
||||
-append Append a single item (escapes for the key, no escapes for the value)
|
||||
-add Append 1 or more items (same syntax as -set)
|
||||
-remove Delete item by key if present (does not interpret escapes)
|
||||
============= ===============================================
|
||||
|
||||
Keys are unique within the list. If an already present key is set, the existing
|
||||
key is removed before the new value is appended.
|
||||
|
||||
Filter options
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
This is a very complex option type for the ``--af`` and ``--vf`` options only.
|
||||
They often require complicated escaping. See `VIDEO FILTERS`_ for details. They
|
||||
support the following operations:
|
||||
|
||||
============= ===============================================
|
||||
Suffix Meaning
|
||||
============= ===============================================
|
||||
-set Set a list of filters (using ``,`` as separator)
|
||||
-append Append single filter
|
||||
-add Append 1 or more filters (same syntax as -set)
|
||||
-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
|
||||
============= ===============================================
|
||||
|
||||
General
|
||||
~~~~~~~
|
||||
|
||||
Without suffix, the operation used is normally ``-set``.
|
||||
|
||||
Although some operations allow specifying multiple items, using this is strongly
|
||||
discouraged and deprecated, except for ``-set``. There is a chance that
|
||||
operations like ``-add`` and ``-pre`` will work like ``-append`` and accept a
|
||||
single, unescaped item only (so the ``,`` separator will not be interpreted and
|
||||
is passed on as part of the value).
|
||||
Without suffix, the action taken is normally ``-set``.
|
||||
|
||||
Some options (like ``--sub-file``, ``--audio-file``, ``--glsl-shader``) are
|
||||
aliases for the proper option with ``-append`` action. For example,
|
||||
``--sub-file`` is an alias for ``--sub-files-append``.
|
||||
|
||||
Options of this type can be changed at runtime using the ``change-list``
|
||||
command, which takes the suffix (without the ``-``) as separate operation
|
||||
parameter.
|
||||
Playing DVDs
|
||||
------------
|
||||
|
||||
DVDs can be played with the ``dvd://[title]`` syntax. The optional
|
||||
title specifier is a number which selects between separate video
|
||||
streams on the DVD. If no title is given (``dvd://``) then the longest
|
||||
title is selected automatically by the library. This is usually what
|
||||
you want. mpv does not support DVD menus.
|
||||
|
||||
DVDs which have been copied on to a hard drive or other mounted
|
||||
filesystem (by e.g. the ``dvdbackup`` tool) are accommodated by
|
||||
specifying the path to the local copy: ``--dvd-device=PATH``.
|
||||
Alternatively, running ``mpv PATH`` should auto-detect a DVD directory
|
||||
tree and play the longest title.
|
||||
|
||||
.. note:: DVD library choices
|
||||
|
||||
mpv uses a different default DVD library than MPlayer. MPlayer
|
||||
uses libdvdread by default, and mpv uses libdvdnav by default.
|
||||
Both libraries are developed in parallel, but libdvdnav is
|
||||
intended to support more sophisticated DVD features such as menus
|
||||
and multi-angle playback. mpv uses libdvdnav for files specified
|
||||
as either ``dvd://...`` or ``dvdnav://...``. To use libdvdread,
|
||||
which will produce behavior more like MPlayer, specify
|
||||
``dvdread://...`` instead. Some users have experienced problems
|
||||
when using libdvdnav, in which playback gets stuck in a DVD menu
|
||||
stream. These problems are reported to go away when auto-selecting
|
||||
the title (``dvd://`` rather than ``dvd://1``) or when using
|
||||
libdvdread (e.g. ``dvdread://0``). There are also outstanding bugs
|
||||
in libdvdnav with seeking backwards and forwards in a video
|
||||
stream. Specify ``dvdread://...`` to fix such problems.
|
||||
|
||||
.. note:: DVD subtitles
|
||||
|
||||
DVDs use image-based subtitles. Image subtitles are implemented as
|
||||
a bitmap video stream which can be superimposed over the main
|
||||
movie. mpv's subtitle styling and positioning options and keyboard
|
||||
shortcuts generally do not work with image-based subtitles.
|
||||
Exceptions include options like ``--stretch-dvd-subs`` and
|
||||
``--stretch-image-subs-to-screen``.
|
||||
|
||||
|
||||
CONFIGURATION FILES
|
||||
===================
|
||||
@@ -650,11 +567,6 @@ description (shown by ``--profile=help``) can be defined with the
|
||||
``profile-desc`` option. To end the profile, start another one or use the
|
||||
profile name ``default`` to continue with normal options.
|
||||
|
||||
You can list profiles with ``--profile=help``, and show the contents of a
|
||||
profile with ``--show-profile=<name>`` (replace ``<name>`` with the profile
|
||||
name). You can apply profiles on start with the ``--profile=<name>`` option,
|
||||
or at runtime with the ``apply-profile <name>`` command.
|
||||
|
||||
.. admonition:: Example mpv config file with profiles
|
||||
|
||||
::
|
||||
@@ -664,8 +576,7 @@ or at runtime with the ``apply-profile <name>`` command.
|
||||
|
||||
# a profile that can be enabled with --profile=big-cache
|
||||
[big-cache]
|
||||
cache=yes
|
||||
demuxer-max-bytes=123400KiB
|
||||
cache=123400
|
||||
demuxer-readahead-secs=20
|
||||
|
||||
[slow]
|
||||
@@ -692,8 +603,12 @@ Some profiles are loaded automatically. The following example demonstrates this:
|
||||
|
||||
::
|
||||
|
||||
[extension.mkv]
|
||||
profile-desc="profile for .mkv files"
|
||||
[protocol.dvd]
|
||||
profile-desc="profile for dvd:// streams"
|
||||
alang=en
|
||||
|
||||
[extension.flv]
|
||||
profile-desc="profile for .flv files"
|
||||
vf=flip
|
||||
|
||||
The profile name follows the schema ``type.name``, where type can be
|
||||
@@ -703,51 +618,6 @@ and ``extension`` for the extension of the path of the currently played file
|
||||
|
||||
This feature is very limited, and there are no other auto profiles.
|
||||
|
||||
Using mpv from other programs or scripts
|
||||
========================================
|
||||
|
||||
There are three choices for using mpv from other programs or scripts:
|
||||
|
||||
1. Calling it as UNIX process. If you do this, *do not parse terminal output*.
|
||||
The terminal output is intended for humans, and may change any time. In
|
||||
addition, terminal behavior itself may change any time. Compatibility
|
||||
cannot be guaranteed.
|
||||
|
||||
Your code should work even if you pass ``--no-terminal``. Do not attempt
|
||||
to simulate user input by sending terminal control codes to mpv's stdin.
|
||||
If you need interactive control, using ``--input-ipc-server`` is
|
||||
recommended. This gives you access to the `JSON IPC`_ over unix domain
|
||||
sockets (or named pipes on Windows).
|
||||
|
||||
Depending on what you do, passing ``--no-config`` or ``--config-dir`` may
|
||||
be a good idea to avoid conflicts with the normal mpv user configuration
|
||||
intended for CLI playback.
|
||||
|
||||
Using ``--input-ipc-server`` is also suitable for purposes like remote
|
||||
control (however, the IPC protocol itself is not "secure" and not
|
||||
intended to be so).
|
||||
|
||||
2. Using libmpv. This is generally recommended when mpv is used as playback
|
||||
backend for a completely different application. The provided C API is
|
||||
very close to CLI mechanisms and the scripting API.
|
||||
|
||||
Note that even though libmpv has different defaults, it can be configured
|
||||
to work exactly like the CLI player (except command line parsing is
|
||||
unavailable).
|
||||
|
||||
See `EMBEDDING INTO OTHER PROGRAMS (LIBMPV)`_.
|
||||
|
||||
3. As a user script (`LUA SCRIPTING`_, `JAVASCRIPT`_, `C PLUGINS`_). This is
|
||||
recommended when the goal is to "enhance" the CLI player. Scripts get
|
||||
access to the entire client API of mpv.
|
||||
|
||||
This is the standard way to create third-party extensions for the player.
|
||||
|
||||
All these access the client API, which is the sum of the various mechanisms
|
||||
provided by the player core, as documented here: `OPTIONS`_,
|
||||
`List of Input Commands`_, `Properties`_, `List of events`_ (also see C API),
|
||||
`Hooks`_.
|
||||
|
||||
TAKING SCREENSHOTS
|
||||
==================
|
||||
|
||||
@@ -811,49 +681,12 @@ listed.
|
||||
to the display as well, e.g.: ``Dropped: 4/34``. This happens only if
|
||||
decoder frame dropping is enabled with the ``--framedrop`` options.
|
||||
(``drop-frame-count`` property.)
|
||||
- Cache state, e.g. ``Cache: 2s/134KB``. Visible if the stream cache is enabled.
|
||||
- Cache state, e.g. ``Cache: 2s+134KB``. Visible if the stream cache is enabled.
|
||||
The first value shows the amount of video buffered in the demuxer in seconds,
|
||||
the second value shows the estimated size of the buffered amount in kilobytes.
|
||||
(``demuxer-cache-duration`` and ``demuxer-cache-state`` properties.)
|
||||
|
||||
|
||||
LOW LATENCY PLAYBACK
|
||||
====================
|
||||
|
||||
mpv is optimized for normal video playback, meaning it actually tries to buffer
|
||||
as much data as it seems to make sense. This will increase latency. Reducing
|
||||
latency is possible only by specifically disabling features which increase
|
||||
latency.
|
||||
|
||||
The builtin ``low-latency`` profile tries to apply some of the options which can
|
||||
reduce latency. You can use ``--profile=low-latency`` to apply all of them. You
|
||||
can list the contents with ``--show-profile=low-latency`` (some of the options
|
||||
are quite obscure, and may change every mpv release).
|
||||
|
||||
Be aware that some of the options can reduce playback quality.
|
||||
|
||||
Most latency is actually caused by inconvenient timing behavior. You can disable
|
||||
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 --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,
|
||||
these can be skipped manually. The experimental ``drop-buffers`` command can
|
||||
be used to discard any buffered data, though it's very disruptive.
|
||||
|
||||
In some cases, manually tuning TCP buffer sizes and such can help to reduce
|
||||
latency.
|
||||
|
||||
Additional options that can be tried:
|
||||
|
||||
- ``--opengl-glfinish=yes``, can reduce buffering in the graphics driver
|
||||
- ``--opengl-swapinterval=0``, same
|
||||
- ``--vo=xv``, same
|
||||
- without audio ``--framedrop=no --speed=1.01`` may help for live sources
|
||||
(results can be mixed)
|
||||
the second value shows the sum of the demuxer forward cache size and the
|
||||
*additional* data buffered in the stream cache in kilobytes.
|
||||
(``demuxer-cache-duration``, ``demuxer-cache-state``, ``cache-used``
|
||||
properties.)
|
||||
|
||||
|
||||
PROTOCOLS
|
||||
@@ -875,10 +708,10 @@ PROTOCOLS
|
||||
|
||||
``ytdl://...``
|
||||
|
||||
By default, the youtube-dl hook script only looks at http(s) URLs. Prefixing
|
||||
an URL with ``ytdl://`` forces it to be always processed by the script. This
|
||||
can also be used to invoke special youtube-dl functionality like playing a
|
||||
video by ID or invoking search.
|
||||
By default, the youtube-dl hook script (enabled by default for mpv CLI)
|
||||
only looks at http URLs. Prefixing an URL with ``ytdl://`` forces it to
|
||||
be always processed by the script. This can also be used to invoke special
|
||||
youtube-dl functionality like playing a video by ID or invoking search.
|
||||
|
||||
Keep in mind that you can't pass youtube-dl command line options by this,
|
||||
and you have to use ``--ytdl-raw-options`` instead.
|
||||
@@ -893,25 +726,40 @@ PROTOCOLS
|
||||
|
||||
``bd://[title][/device]`` ``--bluray-device=PATH``
|
||||
|
||||
Play a Blu-ray disc. Since libbluray 1.0.1, you can read from ISO files
|
||||
by passing them to ``--bluray-device``.
|
||||
Play a Blu-ray disc. Currently, this does not accept ISO files. Instead,
|
||||
you must mount the ISO file as filesystem, and point ``--bluray-device``
|
||||
to the mounted directory directly.
|
||||
|
||||
``title`` can be: ``longest`` or ``first`` (selects the default
|
||||
playlist); ``mpls/<number>`` (selects <number>.mpls playlist);
|
||||
``<number>`` (select playlist with the same index). mpv will list
|
||||
the available playlists on loading.
|
||||
``<number>`` (select playlist with the same index). You can list
|
||||
the available playlists with ``--msg-level=bd=v``.
|
||||
|
||||
``bluray://`` is an alias.
|
||||
|
||||
``dvd://[title][/device]`` ``--dvd-device=PATH``
|
||||
``dvd://[title|[starttitle]-endtitle][/device]`` ``--dvd-device=PATH``
|
||||
|
||||
Play a DVD. DVD menus are not supported. If no title is given, the longest
|
||||
title is auto-selected. Without ``--dvd-device``, it will probably try
|
||||
to open an actual optical drive, if available and implemented for the OS.
|
||||
title is auto-selected.
|
||||
|
||||
``dvdnav://`` is an old alias for ``dvd://`` and does exactly the same
|
||||
thing.
|
||||
|
||||
``dvdread://...:``
|
||||
|
||||
Play a DVD using the old libdvdread code. This is what MPlayer and
|
||||
older mpv versions use for ``dvd://``. Use is discouraged. It's
|
||||
provided only for compatibility and for transition, and to work
|
||||
around outstanding dvdnav bugs (see "DVD library choices" above).
|
||||
|
||||
``tv://[channel][/input_id]`` ``--tv-...``
|
||||
|
||||
Analogue TV via V4L. Also useful for webcams. (Linux only.)
|
||||
|
||||
``pvr://`` ``--pvr-...``
|
||||
|
||||
PVR. (Linux only.)
|
||||
|
||||
``dvb://[cardnumber@]channel`` ``--dvbin-...``
|
||||
|
||||
Digital TV via DVB. (Linux only.)
|
||||
@@ -935,17 +783,9 @@ PROTOCOLS
|
||||
demuxer name, and ``options`` is the (pseudo-)filename passed to the
|
||||
demuxer.
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
::
|
||||
|
||||
mpv av://v4l2:/dev/video0 --profile=low-latency --untimed
|
||||
|
||||
This plays video from the first v4l input with nearly the lowest latency
|
||||
possible. It's a good replacement for the removed ``tv://`` input.
|
||||
Using ``--untimed`` is a hack to output a captured frame immediately,
|
||||
instead of respecting the input framerate. (There may be better ways to
|
||||
handle this in the future.)
|
||||
For example, ``mpv av://lavfi:mandelbrot`` makes use of the libavfilter
|
||||
wrapper included in libavdevice, and will use the ``mandelbrot`` source
|
||||
filter to generate input data.
|
||||
|
||||
``avdevice://`` is an alias.
|
||||
|
||||
@@ -955,25 +795,10 @@ PROTOCOLS
|
||||
``PATH`` itself should start with a third ``/`` to make the path an
|
||||
absolute path.
|
||||
|
||||
``appending://PATH``
|
||||
|
||||
Play a local file, but assume it's being appended to. This is useful for
|
||||
example for files that are currently being downloaded to disk. This will
|
||||
block playback, and stop playback only if no new data was appended after
|
||||
a timeout of about 2 seconds.
|
||||
|
||||
Using this is still a bit of a bad idea, because there is no way to detect
|
||||
if a file is actually being appended, or if it's still written. If you're
|
||||
trying to play the output of some program, consider using a pipe
|
||||
(``something | mpv -``). If it really has to be a file on disk, use tail to
|
||||
make it wait forever, e.g. ``tail -f -c +0 file.mkv | mpv -``.
|
||||
|
||||
``fd://123``
|
||||
|
||||
Read data from the given file descriptor (for example 123). This is similar
|
||||
to piping data to stdin via ``-``, but can use an arbitrary file descriptor.
|
||||
mpv may modify some file descriptor properties when the stream layer "opens"
|
||||
it.
|
||||
|
||||
``fdclose://123``
|
||||
|
||||
@@ -1058,8 +883,6 @@ works like in older mpv releases. The profiles are currently defined as follows:
|
||||
|
||||
.. include:: stats.rst
|
||||
|
||||
.. include:: console.rst
|
||||
|
||||
.. include:: lua.rst
|
||||
|
||||
.. include:: javascript.rst
|
||||
@@ -1097,11 +920,7 @@ behavior of mpv.
|
||||
of ``--v`` options passed to the command line.
|
||||
|
||||
``MPV_LEAK_REPORT``
|
||||
If set to ``1``, enable internal talloc leak reporting. If set to another
|
||||
value, disable leak reporting. If unset, use the default, which normally is
|
||||
``0``. If mpv was built with ``--enable-ta-leak-report``, the default is
|
||||
``1``. If leak reporting was disabled at compile time (``NDEBUG`` in
|
||||
custom ``CFLAGS``), this environment variable is ignored.
|
||||
If set to ``1``, enable internal talloc leak reporting.
|
||||
|
||||
``LADSPA_PATH``
|
||||
Specifies the search path for LADSPA plugins. If it is unset, fully
|
||||
@@ -1253,7 +1072,7 @@ For Windows-specifics, see `FILES ON WINDOWS`_ section.
|
||||
``--write-filename-in-watch-later-config`` option, and the player will
|
||||
add the media filename to the contents of the resume config file.
|
||||
|
||||
``~/.config/mpv/script-opts/osc.conf``
|
||||
``~/.config/mpv/lua-settings/osc.conf``
|
||||
This is loaded by the OSC script. See the `ON SCREEN CONTROLLER`_ docs
|
||||
for details.
|
||||
|
||||
|
||||
+593
-1515
File diff suppressed because it is too large.
Load diff
+4
-92
@@ -128,7 +128,7 @@ Configuration
|
||||
-------------
|
||||
|
||||
The OSC offers limited configuration through a config file
|
||||
``script-opts/osc.conf`` placed in mpv's user dir and through the
|
||||
``lua-settings/osc.conf`` placed in mpv's user dir and through the
|
||||
``--script-opts`` command-line option. Options provided through the command-line
|
||||
will override those from the config file.
|
||||
|
||||
@@ -167,45 +167,9 @@ Configurable Options
|
||||
``seekbarstyle``
|
||||
Default: bar
|
||||
|
||||
Sets the style of the playback position marker and overall shape
|
||||
of the seekbar: ``bar``, ``diamond`` or ``knob``.
|
||||
|
||||
``seekbarhandlesize``
|
||||
Default: 0.6
|
||||
|
||||
Size ratio of the seek handle if ``seekbarstyle`` is set to ``dimaond``
|
||||
or ``knob``. This is relative to the full height of the seekbar.
|
||||
|
||||
``seekbarkeyframes``
|
||||
Default: yes
|
||||
|
||||
Controls the mode used to seek when dragging the seekbar. By default,
|
||||
keyframes are used. If set to false, exact seeking on mouse drags
|
||||
will be used instead. Keyframes are preferred, but exact seeks may be
|
||||
useful in cases where keyframes cannot be found. Note that using exact
|
||||
seeks can potentially make mouse dragging much slower.
|
||||
|
||||
``seekrangestyle``
|
||||
Default: inverted
|
||||
|
||||
Display seekable ranges on the seekbar. ``bar`` shows them on the full
|
||||
height of the bar, ``line`` as a thick line and ``inverted`` as a thin
|
||||
line that is inverted over playback position markers. ``none`` will hide
|
||||
them. Additionally, ``slider`` will show a permanent handle inside the seekbar
|
||||
with cached ranges marked inside. Note that these will look differently
|
||||
based on the seekbarstyle option. Also, ``slider`` does not work with
|
||||
``seekbarstyle`` set to ``bar``.
|
||||
|
||||
``seekrangeseparate``
|
||||
Default: yes
|
||||
|
||||
Controls whether to show line-style seekable ranges on top of the
|
||||
seekbar or separately if ``seekbarstyle`` is set to ``bar``.
|
||||
|
||||
``seekrangealpha``
|
||||
Default: 200
|
||||
|
||||
Alpha of the seekable ranges, 0 (opaque) to 255 (fully transparent).
|
||||
Sets the style of the seekbar, slider (diamond marker), knob (circle
|
||||
marker with guide), or bar (fill).
|
||||
Default pre-0.21.0 was 'slider'.
|
||||
|
||||
``deadzonesize``
|
||||
Default: 0.5
|
||||
@@ -321,58 +285,6 @@ Configurable Options
|
||||
default is conservative to allow wide fonts to be used without overflow.
|
||||
However, with many common fonts a bigger number can be used. YMMV.
|
||||
|
||||
``boxvideo``
|
||||
Default: no
|
||||
|
||||
Whether to overlay the osc over the video (``no``), or to box the video
|
||||
within the areas not covered by the osc (``yes``). If this option is set,
|
||||
the osc may overwrite the ``--video-margin-ratio-*`` options, even if the
|
||||
user has set them. (It will not overwrite them if all of them are set to
|
||||
default values.)
|
||||
|
||||
Currently, this is supported for the ``bottombar`` and ``topbar`` layout
|
||||
only. The other layouts do not change if this option is set. Separately,
|
||||
if window controls are present (see below), they will be affected
|
||||
regardless of which osc layout is in use.
|
||||
|
||||
The border is static and appears even if the OSC is configured to appear
|
||||
only on mouse interaction. If the OSC is invisible, the border is simply
|
||||
filled with the background color (black by default).
|
||||
|
||||
This currently still makes the OSC overlap with subtitles (if the
|
||||
``--sub-use-margins`` option is set to ``yes``, the default). This may be
|
||||
fixed later.
|
||||
|
||||
This does not work correctly with video outputs like ``--vo=xv``, which
|
||||
render OSD into the unscaled video.
|
||||
|
||||
``windowcontrols``
|
||||
Default: auto (Show window controls if there is no window border)
|
||||
|
||||
Whether to show window management controls over the video, and if so,
|
||||
which side of the window to place them. This may be desirable when the
|
||||
window has no decorations, either because they have been explicitly
|
||||
disabled (``border=no``) or because the current platform doesn't support
|
||||
them (eg: gnome-shell with wayland).
|
||||
|
||||
The set of window controls is fixed, offering ``minimize``, ``maximize``,
|
||||
and ``quit``. Not all platforms implement ``minimize`` and ``maximize``,
|
||||
but ``quit`` will always work.
|
||||
|
||||
``windowcontrols_alignment``
|
||||
Default: right
|
||||
|
||||
If window controls are shown, indicates which side should they be aligned
|
||||
to.
|
||||
|
||||
Supports ``left`` and ``right`` which will place the controls on those
|
||||
respective sides.
|
||||
|
||||
``greenandgrumpy``
|
||||
Default: no
|
||||
|
||||
Set to ``yes`` to reduce festivity (i.e. disable santa hat in December.)
|
||||
|
||||
Script Commands
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
+1
-4
@@ -23,7 +23,6 @@ stats:
|
||||
==== ==================
|
||||
1 Show usual stats
|
||||
2 Show frame timings
|
||||
3 Input cache stats
|
||||
==== ==================
|
||||
|
||||
Font
|
||||
@@ -36,7 +35,7 @@ monospaced digits is recommended. By default, the open source font
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
This script can be customized through a config file ``script-opts/stats.conf``
|
||||
This script can be customized through a config file ``lua-settings/stats.conf``
|
||||
placed in mpv's user directory and through the ``--script-opts`` command-line
|
||||
option. The configuration syntax is described in `ON SCREEN CONTROLLER`_.
|
||||
|
||||
@@ -54,8 +53,6 @@ Configurable Options
|
||||
Default: 1
|
||||
``key_page_2``
|
||||
Default: 2
|
||||
``key_page_3``
|
||||
Default: 3
|
||||
|
||||
Key bindings for page switching while stats are displayed.
|
||||
|
||||
|
||||
+98
-303
@@ -1,11 +1,8 @@
|
||||
VIDEO FILTERS
|
||||
=============
|
||||
|
||||
Video filters allow you to modify the video stream and its properties. All of
|
||||
the information described in this section applies to audio filters as well
|
||||
(generally using the prefix ``--af`` instead of ``--vf``).
|
||||
|
||||
The exact syntax is:
|
||||
Video filters allow you to modify the video stream and its properties. The
|
||||
syntax is:
|
||||
|
||||
``--vf=<filter1[=parameter1:parameter2:...],filter2,...>``
|
||||
Setup a chain of video filters. This consists on the filter name, and an
|
||||
@@ -16,7 +13,7 @@ The exact syntax is:
|
||||
name is an arbitrary user-given name, which identifies the filter. This
|
||||
is only needed if you want to toggle the filter at runtime.
|
||||
|
||||
A ``!`` before the filter name means the filter is disabled by default. It
|
||||
A ``!`` before the filter name means the filter is enabled by default. It
|
||||
will be skipped on filter creation. This is also useful for runtime filter
|
||||
toggling.
|
||||
|
||||
@@ -44,18 +41,11 @@ The exact syntax is:
|
||||
the ``lavfi`` filter, which uses a very similar syntax as mpv (MPlayer
|
||||
historically) to specify filters and their parameters.
|
||||
|
||||
Filters can be manipulated at run time. You can use ``@`` labels as described
|
||||
above in combination with the ``vf`` command (see `COMMAND INTERFACE`_) to get
|
||||
more control over this. Initially disabled filters with ``!`` are useful for
|
||||
this as well.
|
||||
|
||||
You can also set defaults for each filter. The defaults are applied before the
|
||||
normal filter parameters. This is deprecated and never worked for the
|
||||
libavfilter bridge.
|
||||
normal filter parameters.
|
||||
|
||||
``--vf-defaults=<filter1[=parameter1:parameter2:...],filter2,...>``
|
||||
Set defaults for each filter. (Deprecated. ``--af-defaults`` is deprecated
|
||||
as well.)
|
||||
Set defaults for each filter.
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -85,35 +75,15 @@ libavfilter bridge.
|
||||
Video filters are managed in lists. There are a few commands to manage the
|
||||
filter list.
|
||||
|
||||
``--vf-append=filter``
|
||||
Appends the filter given as arguments to the filter list.
|
||||
``--vf-add=<filter1[,filter2,...]>``
|
||||
Appends the filters given as arguments to the filter list.
|
||||
|
||||
``--vf-add=filter``
|
||||
Appends the filter given as arguments to the filter list. (Passing multiple
|
||||
filters is currently still possible, but deprecated.)
|
||||
``--vf-pre=<filter1[,filter2,...]>``
|
||||
Prepends the filters given as arguments to the filter list.
|
||||
|
||||
``--vf-pre=filter``
|
||||
Prepends the filters given as arguments to the filter list. (Passing
|
||||
multiple filters is currently still possible, but deprecated.)
|
||||
|
||||
``--vf-remove=filter``
|
||||
Deletes the filter from the list. The filter can be either given the way it
|
||||
was added (filter name and its full argument list), or by label (prefixed
|
||||
with ``@``). Matching of filters works as follows: if either of the compared
|
||||
filters has a label set, only the labels are compared. If none of the
|
||||
filters have a label, the filter name, arguments, and argument order are
|
||||
compared. (Passing multiple filters is currently still possible, but
|
||||
deprecated.)
|
||||
|
||||
``-vf-toggle=filter``
|
||||
Add the given filter to the list if it was not present yet, or remove it
|
||||
from the list if it was present. Matching of filters works as described in
|
||||
``--vf-remove``.
|
||||
|
||||
``--vf-del=filter``
|
||||
Sort of like ``--vf-remove``, but also accepts an index number. Index
|
||||
numbers start at 0, negative numbers address the end of the list (-1 is the
|
||||
last). Deprecated.
|
||||
``--vf-del=<index1[,index2,...]>``
|
||||
Deletes the filters at the given indexes. Index numbers start at 0,
|
||||
negative numbers address the end of the list (-1 is the last).
|
||||
|
||||
``--vf-clr``
|
||||
Completely empties the filter list.
|
||||
@@ -124,61 +94,25 @@ With filters that support it, you can access parameters by their name.
|
||||
Prints the parameter names and parameter value ranges for a particular
|
||||
filter.
|
||||
|
||||
``--vf=<filter=named_parameter1=value1[:named_parameter2=value2:...]>``
|
||||
Sets a named parameter to the given value. Use on and off or yes and no to
|
||||
set flag parameters.
|
||||
|
||||
Available mpv-only filters are:
|
||||
|
||||
``format=fmt=<value>:colormatrix=<value>:...``
|
||||
Applies video parameter overrides, with optional conversion. By default,
|
||||
this overrides the video's parameters without conversion (except for the
|
||||
``fmt`` parameter), but can be made to perform an appropriate conversion
|
||||
with ``convert=yes`` for parameters for which conversion is supported.
|
||||
Restricts the color space for the next filter without doing any conversion.
|
||||
Use together with the scale filter for a real conversion.
|
||||
|
||||
.. note::
|
||||
|
||||
For a list of available formats, see ``format=fmt=help``.
|
||||
|
||||
``<fmt>``
|
||||
Image format name, e.g. rgb15, bgr24, 420p, etc. (default: don't change).
|
||||
|
||||
This filter always performs conversion to the given format.
|
||||
|
||||
.. note::
|
||||
|
||||
For a list of available formats, use ``--vf=format=fmt=help``.
|
||||
|
||||
``<convert=yes|no>``
|
||||
Force conversion of color parameters (default: no).
|
||||
|
||||
If this is disabled (the default), the only conversion that is possibly
|
||||
performed is format conversion if ``<fmt>`` is set. All other parameters
|
||||
(like ``<colormatrix>``) are forced without conversion. This mode is
|
||||
typically useful when files have been incorrectly tagged.
|
||||
|
||||
If this is enabled, libswscale or zimg is used if any of the parameters
|
||||
mismatch. zimg is used of the input/output image formats are supported
|
||||
by mpv's zimg wrapper, and if ``--sws-allow-zimg=yes`` is used. Both
|
||||
libraries may not support all kinds of conversions. This typically
|
||||
results in silent incorrect conversion. zimg has in many cases a better
|
||||
chance of performing the conversion correctly.
|
||||
|
||||
In both cases, the color parameters are set on the output stage of the
|
||||
image format conversion (if ``fmt`` was set). The difference is that
|
||||
with ``convert=no``, the color parameters are not passed on to the
|
||||
converter.
|
||||
|
||||
If input and output video parameters are the same, conversion is always
|
||||
skipped.
|
||||
|
||||
.. admonition:: Examples
|
||||
|
||||
``mpv test.mkv --vf=format:colormatrix=ycgco``
|
||||
Results in incorrect colors (if test.mkv was tagged correctly).
|
||||
|
||||
``mpv test.mkv --vf=format:colormatrix=ycgco:convert=yes --sws-allow-zimg``
|
||||
Results in true conversion to ``ycgco``, assuming the renderer
|
||||
supports it (``--vo=gpu`` normally does). You can add ``--vo=xv``
|
||||
to force a VO which definitely does not support it, which should
|
||||
show incorrect colors as confirmation.
|
||||
|
||||
Using ``--sws-allow-zimg=no`` (or disabling zimg at build time)
|
||||
will use libswscale, which cannot perform this conversion as
|
||||
of this writing.
|
||||
|
||||
Format name, e.g. rgb15, bgr24, 420p, etc. (default: don't change).
|
||||
``<outfmt>``
|
||||
Format name that should be substituted for the output. If they do not
|
||||
have the same bytes per pixel and chroma subsampling, it will fail.
|
||||
``<colormatrix>``
|
||||
Controls the YUV to RGB color space conversion when playing video. There
|
||||
are various standards. Normally, BT.601 should be used for SD video, and
|
||||
@@ -186,7 +120,7 @@ Available mpv-only filters are:
|
||||
results in slightly under or over saturated and shifted colors.
|
||||
|
||||
These options are not always supported. Different video outputs provide
|
||||
varying degrees of support. The ``gpu`` and ``vdpau`` video output
|
||||
varying degrees of support. The ``opengl`` and ``vdpau`` video output
|
||||
drivers usually offer full support. The ``xv`` output can set the color
|
||||
space if the system video driver supports it, but not input and output
|
||||
levels. The ``scale`` video filter can configure color space and input
|
||||
@@ -229,7 +163,7 @@ Available mpv-only filters are:
|
||||
used to override the setting.
|
||||
|
||||
This option only affects video output drivers that perform color
|
||||
management, for example ``gpu`` with the ``target-prim`` or
|
||||
management, for example ``opengl`` with the ``target-prim`` or
|
||||
``icc-profile`` suboptions set.
|
||||
|
||||
If this option is set to ``auto`` (which is the default), the video's
|
||||
@@ -273,10 +207,7 @@ Available mpv-only filters are:
|
||||
:srgb: IEC 61966-2-4 (sRGB)
|
||||
:linear: Linear light
|
||||
:gamma1.8: Pure power curve (gamma 1.8)
|
||||
:gamma2.0: Pure power curve (gamma 2.0)
|
||||
:gamma2.2: Pure power curve (gamma 2.2)
|
||||
:gamma2.4: Pure power curve (gamma 2.4)
|
||||
:gamma2.6: Pure power curve (gamma 2.6)
|
||||
:gamma2.8: Pure power curve (gamma 2.8)
|
||||
:prophoto: ProPhoto RGB (ROMM) curve
|
||||
:pq: ITU-R BT.2100 PQ (Perceptual quantizer) curve
|
||||
@@ -309,13 +240,12 @@ Available mpv-only filters are:
|
||||
:gamma1.2: Scene-referred using a pure power OOTF (gamma=1.2)
|
||||
|
||||
``<stereo-in>``
|
||||
Set the stereo mode the video is assumed to be encoded in. Use
|
||||
``--vf format:stereo-in=help`` to list all available modes. Check with
|
||||
the ``stereo3d`` filter documentation to see what the names mean.
|
||||
Set the stereo mode the video is assumed to be encoded in. Takes the
|
||||
same values as the ``--video-stereo-mode`` option.
|
||||
|
||||
``<stereo-out>``
|
||||
Set the stereo mode the video should be displayed as. Takes the
|
||||
same values as the ``stereo-in`` option.
|
||||
same values as the ``--video-stereo-mode`` option.
|
||||
|
||||
``<rotate>``
|
||||
Set the rotation the video is assumed to be encoded with in degrees.
|
||||
@@ -331,6 +261,17 @@ Available mpv-only filters are:
|
||||
but values such as ``[16:9]`` can be passed too (``[...]`` for quoting
|
||||
to prevent the option parser from interpreting the ``:`` character).
|
||||
|
||||
``<spherical-type>``
|
||||
Type of the spherical projection:
|
||||
|
||||
:auto: As indicated by the file (default)
|
||||
:none: Normal video
|
||||
:equirect: Equirectangular
|
||||
:unknown: Unknown projection
|
||||
|
||||
``<spherical-yaw>``, ``<spherical-pitch>``, ``<spherical-roll>``
|
||||
Reference angle in degree, if spherical video is used.
|
||||
|
||||
``lavfi=graph[:sws-flags[:o=opts]]``
|
||||
Filter video using FFmpeg's libavfilter.
|
||||
|
||||
@@ -410,31 +351,22 @@ Available mpv-only filters are:
|
||||
of that will return errors. As such, you can't use the full power of
|
||||
VapourSynth, but you can use certain filters.
|
||||
|
||||
.. warning::
|
||||
|
||||
Do not use this filter, unless you have expert knowledge in VapourSynth,
|
||||
and know how to fix bugs in the mpv VapourSynth wrapper code.
|
||||
|
||||
If you just want to play video generated by VapourSynth (i.e. using
|
||||
If you just want to play video generated by a VapourSynth (i.e. using
|
||||
a native VapourSynth video source), it's better to use ``vspipe`` and a
|
||||
pipe or FIFO to feed the video to mpv. The same applies if the filter script
|
||||
FIFO to feed the video to mpv. The same applies if the filter script
|
||||
requires random frame access (see ``buffered-frames`` parameter).
|
||||
|
||||
This filter is experimental. If it turns out that it works well and is
|
||||
used, it will be ported to libavfilter. Otherwise, it will be just removed.
|
||||
|
||||
``file``
|
||||
Filename of the script source. Currently, this is always a python
|
||||
script (``.vpy`` in VapourSynth convention).
|
||||
|
||||
The variable ``video_in`` is set to the mpv video source, and it is
|
||||
expected that the script reads video from it. (Otherwise, mpv will
|
||||
decode no video, and the video packet queue will overflow, eventually
|
||||
leading to only audio playing, or worse.)
|
||||
|
||||
The filter graph created by the script is also expected to pass through
|
||||
timestamps using the ``_DurationNum`` and ``_DurationDen`` frame
|
||||
properties.
|
||||
|
||||
See the end of the option list for a full list of script variables
|
||||
defined by mpv.
|
||||
script. The variable ``video_in`` is set to the mpv video source,
|
||||
and it is expected that the script reads video from it. (Otherwise,
|
||||
mpv will decode no video, and the video packet queue will overflow,
|
||||
eventually leading to audio being stopped.) The script is also
|
||||
expected to pass through timestamps using the ``_DurationNum`` and
|
||||
``_DurationDen`` frame properties.
|
||||
|
||||
.. admonition:: Example:
|
||||
|
||||
@@ -452,61 +384,21 @@ Available mpv-only filters are:
|
||||
``buffered-frames``
|
||||
Maximum number of decoded video frames that should be buffered before
|
||||
the filter (default: 4). This specifies the maximum number of frames
|
||||
the script can request in backward direction.
|
||||
|
||||
the script can request in reverse direction.
|
||||
E.g. if ``buffered-frames=5``, and the script just requested frame 15,
|
||||
it can still request frame 10, but frame 9 is not available anymore.
|
||||
If it requests frame 30, mpv will decode 15 more frames, and keep only
|
||||
frames 25-30.
|
||||
|
||||
The only reason why this buffer exists is to serve the random access
|
||||
requests the VapourSynth filter can make.
|
||||
|
||||
The VapourSynth API has a ``getFrameAsync`` function, which takes an
|
||||
absolute frame number. Source filters must respond to all requests. For
|
||||
example, a source filter can request frame 2432, and then frame 3.
|
||||
Source filters typically implement this by pre-indexing the entire
|
||||
file.
|
||||
|
||||
mpv on the other hand is stream oriented, and does not allow filters to
|
||||
seek. (And it would not make sense to allow it, because it would ruin
|
||||
performance.) Filters get frames sequentially in playback direction, and
|
||||
cannot request them out of order.
|
||||
|
||||
To compensate for this mismatch, mpv allows the filter to access frames
|
||||
within a certain window. ``buffered-frames`` controls the size of this
|
||||
window. Most VapourSynth filters happen to work with this, because mpv
|
||||
requests frames sequentially increasing from it, and most filters only
|
||||
require frames "close" to the requested frame.
|
||||
|
||||
If the filter requests a frame that has a higher frame number than the
|
||||
highest buffered frame, new frames will be decoded until the requested
|
||||
frame number is reached. Excessive frames will be flushed out in a FIFO
|
||||
manner (there are only at most ``buffered-frames`` in this buffer).
|
||||
|
||||
If the filter requests a frame that has a lower frame number than the
|
||||
lowest buffered frame, the request cannot be satisfied, and an error
|
||||
is returned to the filter. This kind of error is not supposed to happen
|
||||
in a "proper" VapourSynth environment. What exactly happens depends on
|
||||
the filters involved.
|
||||
|
||||
Increasing this buffer will not improve performance. Rather, it will
|
||||
waste memory, and slow down seeks (when enough frames to fill the buffer
|
||||
need to be decoded at once). It is only needed to prevent the error
|
||||
described in the previous paragraph.
|
||||
|
||||
How many frames a filter requires depends on filter implementation
|
||||
details, and mpv has no way of knowing. A scale filter might need only
|
||||
1 frame, an interpolation filter may require a small number of frames,
|
||||
and the ``Reverse`` filter will require an infinite number of frames.
|
||||
|
||||
If you want reliable operation to the full extend VapourSynth is
|
||||
capable, use ``vspipe``.
|
||||
|
||||
The actual number of buffered frames also depends on the value of the
|
||||
``concurrent-frames`` option. Currently, both option values are
|
||||
multiplied to get the final buffer size.
|
||||
|
||||
(Normally, VapourSynth source filters must provide random access, but
|
||||
mpv was made for playback, and does not provide frame-exact random
|
||||
access. The way this video filter works is a compromise to make simple
|
||||
filters work anyway.)
|
||||
|
||||
``concurrent-frames``
|
||||
Number of frames that should be requested in parallel. The
|
||||
level of concurrency depends on the filter and how quickly mpv can
|
||||
@@ -515,25 +407,15 @@ Available mpv-only filters are:
|
||||
making it higher than the number of cores can actually make it
|
||||
slower.
|
||||
|
||||
Technically, mpv will call the VapourSynth ``getFrameAsync`` function
|
||||
in a loop, until there are ``concurrent-frames`` frames that have not
|
||||
been returned by the filter yet. This also assumes that the rest of the
|
||||
mpv filter chain reads the output of the ``vapoursynth`` filter quickly
|
||||
enough. (For example, if you pause the player, filtering will stop very
|
||||
soon, because the filtered frames are waiting in a queue.)
|
||||
|
||||
Actual concurrency depends on many other factors.
|
||||
|
||||
By default, this uses the special value ``auto``, which sets the option
|
||||
to the number of detected logical CPU cores.
|
||||
|
||||
The following ``.vpy`` script variables are defined by mpv:
|
||||
The following variables are defined by mpv:
|
||||
|
||||
``video_in``
|
||||
The mpv video source as vapoursynth clip. Note that this has an
|
||||
incorrect (very high) length set, which confuses many filters. This is
|
||||
necessary, because the true number of frames is unknown. You can use the
|
||||
``Trim`` filter on the clip to reduce the length.
|
||||
The mpv video source as vapoursynth clip. Note that this has no length
|
||||
set, which confuses many filters. Using ``Trim`` on the clip with a
|
||||
high dummy length can turn it into a finite clip.
|
||||
|
||||
``video_in_dw``, ``video_in_dh``
|
||||
Display size of the video. Can be different from video size if the
|
||||
@@ -543,7 +425,7 @@ Available mpv-only filters are:
|
||||
FPS value as reported by file headers. This value can be wrong or
|
||||
completely broken (e.g. 0 or NaN). Even if the value is correct,
|
||||
if another filter changes the real FPS (by dropping or inserting
|
||||
frames), the value of this variable will not be useful. Note that
|
||||
frames), the value of this variable might not be useful. Note that
|
||||
the ``--fps`` command line option overrides this value.
|
||||
|
||||
Useful for some filters which insist on having a FPS.
|
||||
@@ -551,10 +433,34 @@ Available mpv-only filters are:
|
||||
``display_fps``
|
||||
Refresh rate of the current display. Note that this value can be 0.
|
||||
|
||||
``vapoursynth-lazy``
|
||||
The same as ``vapoursynth``, but doesn't load Python scripts. Instead, a
|
||||
custom backend using Lua and the raw VapourSynth API is used. The syntax
|
||||
is completely different, and absolutely no convenience features are
|
||||
provided. There's no type checking either, and you can trigger crashes.
|
||||
|
||||
.. admonition:: Example:
|
||||
|
||||
::
|
||||
|
||||
video_out = invoke("morpho", "Open", {clip = video_in})
|
||||
|
||||
The special variable ``video_in`` is the mpv video source, while the
|
||||
special variable ``video_out`` is used to read video from. The 1st argument
|
||||
is the plugin (queried with ``getPluginByNs``), the 2nd is the filter name,
|
||||
and the 3rd argument is a table with the arguments. Positional arguments
|
||||
are not supported. The types must match exactly. Since Lua is terrible and
|
||||
can't distinguish integers and floats, integer arguments must be prefixed
|
||||
with ``i_``, in which case the prefix is removed and the argument is cast
|
||||
to an integer. Should the argument's name start with ``i_``, you're out of
|
||||
luck.
|
||||
|
||||
Clips (VSNodeRef) are passed as light userdata, so trying to pass any
|
||||
other userdata type will result in hard crashes.
|
||||
|
||||
``vavpp``
|
||||
VA-API video post processing. Requires the system to support VA-API,
|
||||
i.e. Linux/BSD only. Works with ``--vo=vaapi`` and ``--vo=gpu`` only.
|
||||
Currently deinterlaces. This filter is automatically inserted if
|
||||
VA-AP-API video post processing. Works with ``--vo=vaapi`` and ``--vo=opengl``
|
||||
only. Currently deinterlaces. This filter is automatically inserted if
|
||||
deinterlacing is requested (either using the ``d`` key, by default mapped to
|
||||
the command ``cycle deinterlace``, or the ``--deinterlace`` option).
|
||||
|
||||
@@ -563,22 +469,18 @@ Available mpv-only filters are:
|
||||
|
||||
no
|
||||
Don't perform deinterlacing.
|
||||
auto
|
||||
Select the best quality deinterlacing algorithm (default). This
|
||||
goes by the order of the options as documented, with
|
||||
``motion-compensated`` being considered best quality.
|
||||
first-field
|
||||
Show only first field.
|
||||
bob
|
||||
bob deinterlacing.
|
||||
bob deinterlacing (default).
|
||||
weave, motion-adaptive, motion-compensated
|
||||
Advanced deinterlacing algorithms. Whether these actually work
|
||||
depends on the GPU hardware, the GPU drivers, driver bugs, and
|
||||
mpv bugs.
|
||||
|
||||
``<interlaced-only>``
|
||||
:no: Deinterlace all frames (default).
|
||||
:yes: Only deinterlace frames marked as interlaced.
|
||||
:no: Deinterlace all frames.
|
||||
:yes: Only deinterlace frames marked as interlaced (default).
|
||||
|
||||
``reversal-bug=<yes|no>``
|
||||
:no: Use the API as it was interpreted by older Mesa drivers. While
|
||||
@@ -590,12 +492,12 @@ Available mpv-only filters are:
|
||||
algorithms.
|
||||
|
||||
``vdpaupp``
|
||||
VDPAU video post processing. Works with ``--vo=vdpau`` and ``--vo=gpu``
|
||||
VDPAU video post processing. Works with ``--vo=vdpau`` and ``--vo=opengl``
|
||||
only. This filter is automatically inserted if deinterlacing is requested
|
||||
(either using the ``d`` key, by default mapped to the command
|
||||
``cycle deinterlace``, or the ``--deinterlace`` option). When enabling
|
||||
deinterlacing, it is always preferred over software deinterlacer filters
|
||||
if the ``vdpau`` VO is used, and also if ``gpu`` is used and hardware
|
||||
if the ``vdpau`` VO is used, and also if ``opengl`` is used and hardware
|
||||
decoding was activated at least once (i.e. vdpau was loaded).
|
||||
|
||||
``sharpen=<-1-1>``
|
||||
@@ -633,7 +535,7 @@ Available mpv-only filters are:
|
||||
Try to apply inverse telecine, needs motion adaptive temporal
|
||||
deinterlacing.
|
||||
``interlaced-only=<yes|no>``
|
||||
If ``yes``, only deinterlace frames marked as interlaced (default: no).
|
||||
If ``yes`` (default), only deinterlace frames marked as interlaced.
|
||||
``hqscaling=<0-9>``
|
||||
0
|
||||
Use default VDPAU scaling (default).
|
||||
@@ -647,117 +549,10 @@ Available mpv-only filters are:
|
||||
``deint=<yes|no>``
|
||||
Whether deinterlacing is enabled (default: no).
|
||||
``interlaced-only=<yes|no>``
|
||||
If ``yes``, only deinterlace frames marked as interlaced (default: no).
|
||||
If ``yes`` (default), only deinterlace frames marked as interlaced.
|
||||
``mode=<blend|bob|adaptive|mocomp|ivctc|none>``
|
||||
Tries to select a video processor with the given processing capability.
|
||||
If a video processor supports multiple capabilities, it is not clear
|
||||
which algorithm is actually selected. ``none`` always falls back. On
|
||||
most if not all hardware, this option will probably do nothing, because
|
||||
a video processor usually supports all modes or none.
|
||||
|
||||
``fingerprint=...``
|
||||
Compute video frame fingerprints and provide them as metadata. Actually, it
|
||||
currently barely deserved to be called ``fingerprint``, because it does not
|
||||
compute "proper" fingerprints, only tiny downscaled images (but which can be
|
||||
used to compute image hashes or for similarity matching).
|
||||
|
||||
The main purpose of this filter is to support the ``skip-logo.lua`` script.
|
||||
If this script is dropped, or mpv ever gains a way to load user-defined
|
||||
filters (other than VapourSynth), this filter will be removed. Due to the
|
||||
"special" nature of this filter, it will be removed without warning.
|
||||
|
||||
The intended way to read from the filter is using ``vf-metadata`` (also
|
||||
see ``clear-on-query`` filter parameter). The property will return a list
|
||||
of key/value pairs as follows:
|
||||
|
||||
::
|
||||
|
||||
fp0.pts = 1.2345
|
||||
fp0.hex = 1234abcdef...bcde
|
||||
fp1.pts = 1.4567
|
||||
fp1.hex = abcdef1234...6789
|
||||
...
|
||||
fpN.pts = ...
|
||||
fpN.hex = ...
|
||||
type = gray-hex-16x16
|
||||
|
||||
Each ``fp<N>`` entry is for a frame. The ``pts`` entry specifies the
|
||||
timestamp of the frame (within the filter chain; in simple cases this is
|
||||
the same as the display timestamp). The ``hex`` field is the hex encoded
|
||||
fingerprint, whose size and meaning depend on the ``type`` filter option.
|
||||
The ``type`` field has the same value as the option the filter was created
|
||||
with.
|
||||
|
||||
This returns the frames that were filtered since the last query of the
|
||||
property. If ``clear-on-query=no`` was set, a query doesn't reset the list
|
||||
of frames. In both cases, a maximum of 10 frames is returned. If there are
|
||||
more frames, the oldest frames are discarded. Frames are returned in filter
|
||||
order.
|
||||
|
||||
(This doesn't return a structured list for the per-frame details because the
|
||||
internals of the ``vf-metadata`` mechanism suck. The returned format may
|
||||
change in the future.)
|
||||
|
||||
This filter uses zimg for speed and profit. However, it will fallback to
|
||||
libswscale in a number of situations: lesser pixel formats, unaligned data
|
||||
pointers or strides, or if zimg fails to initialize for unknown reasons. In
|
||||
these cases, the filter will use more CPU. Also, it will output different
|
||||
fingerprints, because libswscale cannot perform the full range expansion we
|
||||
normally request from zimg. As a consequence, the filter may be slower and
|
||||
not work correctly in random situations.
|
||||
|
||||
``type=...``
|
||||
What fingerprint to compute. Available types are:
|
||||
|
||||
:gray-hex-8x8: grayscale, 8 bit, 8x8 size
|
||||
:gray-hex-16x16: grayscale, 8 bit, 16x16 size (default)
|
||||
|
||||
Both types simply remove all colors, downscale the image, concatenate
|
||||
all pixel values to a byte array, and convert the array to a hex string.
|
||||
|
||||
``clear-on-query=yes|no``
|
||||
Clear the list of frame fingerprints if the ``vf-metadata`` property for
|
||||
this filter is queried (default: yes). This requires some care by the
|
||||
user. Some types of accesses might query the filter multiple times,
|
||||
which leads to lost frames.
|
||||
|
||||
``print=yes|no``
|
||||
Print computed fingerprints the the terminal (default: no). This is
|
||||
mostly for testing and such. Scripts should use ``vf-metadata`` to
|
||||
read information from this filter instead.
|
||||
|
||||
``gpu=...``
|
||||
Convert video to RGB using the OpenGL renderer normally used with
|
||||
``--vo=gpu``. This requires that the EGL implementation supports off-screen
|
||||
rendering on the default display. (This is the case with Mesa.)
|
||||
|
||||
Sub-options:
|
||||
|
||||
``w=<pixels>``, ``h=<pixels>``
|
||||
Size of the output in pixels (default: 0). If not positive, this will
|
||||
use the size of the first filtered input frame.
|
||||
|
||||
.. warning::
|
||||
|
||||
This is highly experimental. Performance is bad, and it will not work
|
||||
everywhere in the first place. Some features are not supported.
|
||||
|
||||
.. warning::
|
||||
|
||||
This does not do OSD rendering. If you see OSD, then it has been
|
||||
rendered by the VO backend. (Subtitles are rendered by the ``gpu``
|
||||
filter, if possible.)
|
||||
|
||||
.. warning::
|
||||
|
||||
If you use this with encoding mode, keep in mind that encoding mode will
|
||||
convert the RGB filter's output back to yuv420p in software, using the
|
||||
configured software scaler. Using ``zimg`` might improve this, but in
|
||||
any case it might go against your goals when using this filter.
|
||||
|
||||
.. warning::
|
||||
|
||||
Do not use this with ``--vo=gpu``. It will apply filtering twice, since
|
||||
most ``--vo=gpu`` options are unconditionally applied to the ``gpu``
|
||||
filter. There is no mechanism in mpv to prevent this.
|
||||
|
||||
+37
-113
@@ -71,9 +71,6 @@ Available video output drivers are:
|
||||
Shared memory video output driver without hardware acceleration that works
|
||||
whenever X11 is present.
|
||||
|
||||
Since mpv 0.30.0, you may need to use ``--profile=sw-fast`` to get decent
|
||||
performance.
|
||||
|
||||
.. note:: This is a fallback only, and should not be normally used.
|
||||
|
||||
``vdpau`` (X11 only)
|
||||
@@ -311,7 +308,7 @@ Available video output drivers are:
|
||||
For tuning, refer to your copy of the file ``SDL_hints.h``.
|
||||
|
||||
.. note:: This driver is for compatibility with systems that don't provide
|
||||
proper graphics drivers.
|
||||
proper graphics drivers, or which support GLES only.
|
||||
|
||||
The following global options are supported by this video output:
|
||||
|
||||
@@ -327,7 +324,7 @@ Available video output drivers are:
|
||||
This is low quality, and has issues with OSD.
|
||||
|
||||
.. note:: This driver is for compatibility with crappy systems. You can
|
||||
use vaapi hardware decoding with ``--vo=gpu`` too.
|
||||
use vaapi hardware decoding with ``--vo=opengl`` too.
|
||||
|
||||
The following global options are supported by this video output:
|
||||
|
||||
@@ -386,9 +383,6 @@ Available video output drivers are:
|
||||
Depends on support of true color by modern terminals to display the images
|
||||
at full color range. On Windows it requires an ansi terminal such as mintty.
|
||||
|
||||
Since mpv 0.30.0, you may need to use ``--profile=sw-fast`` to get decent
|
||||
performance.
|
||||
|
||||
``--vo-tct-algo=<algo>``
|
||||
Select how to write the pixels to the terminal.
|
||||
|
||||
@@ -421,8 +415,6 @@ Available video output drivers are:
|
||||
JPEG files, extension .jpeg.
|
||||
png
|
||||
PNG files.
|
||||
webp
|
||||
WebP files.
|
||||
|
||||
``--vo-image-png-compression=<0-9>``
|
||||
PNG compression factor (speed vs. file size tradeoff) (default: 7)
|
||||
@@ -433,30 +425,40 @@ Available video output drivers are:
|
||||
JPEG quality factor (default: 90)
|
||||
``--vo-image-jpeg-optimize=<0-100>``
|
||||
JPEG optimization factor (default: 100)
|
||||
``--vo-image-webp-lossless=<yes|no>``
|
||||
Enable writing lossless WebP files (default: no)
|
||||
``--vo-image-webp-quality=<0-100>``
|
||||
WebP quality (default: 75)
|
||||
``--vo-image-webp-compression=<0-6>``
|
||||
WebP compression factor (default: 4)
|
||||
``--vo-image-outdir=<dirname>``
|
||||
Specify the directory to save the image files to (default: ``./``).
|
||||
|
||||
``libmpv``
|
||||
For use with libmpv direct embedding. As a special case, on OS X it
|
||||
is used like a normal VO within mpv (cocoa-cb). Otherwise useless in any
|
||||
other contexts.
|
||||
(See ``<mpv/render.h>``.)
|
||||
``wayland`` (Wayland only)
|
||||
Wayland shared memory video output as fallback for ``opengl``.
|
||||
|
||||
This also supports many of the options the ``gpu`` VO has, depending on the
|
||||
backend.
|
||||
.. note:: This driver is for compatibility with systems that don't provide
|
||||
working OpenGL drivers.
|
||||
|
||||
The following global options are supported by this video output:
|
||||
|
||||
``--vo-wayland-alpha``
|
||||
Use a buffer format that supports videos and images with alpha
|
||||
information
|
||||
``--vo-wayland-rgb565``
|
||||
Use RGB565 as buffer format. This format is implemented on most
|
||||
platforms, especially on embedded where it is far more efficient then
|
||||
RGB8888.
|
||||
``--vo-wayland-triple-buffering``
|
||||
Use 3 buffers instead of 2. This can lead to more fluid playback, but
|
||||
uses more memory.
|
||||
|
||||
``opengl-cb``
|
||||
For use with libmpv direct OpenGL embedding; useless in any other contexts.
|
||||
(See ``<mpv/opengl_cb.h>``.)
|
||||
|
||||
This also supports many of the options the ``opengl`` VO has.
|
||||
|
||||
``rpi`` (Raspberry Pi)
|
||||
Native video output on the Raspberry Pi using the MMAL API.
|
||||
|
||||
This is deprecated. Use ``--vo=gpu`` instead, which is the default and
|
||||
This is deprecated. Use ``--vo=opengl`` instead, which is the default and
|
||||
provides the same functionality. The ``rpi`` VO will be removed in
|
||||
mpv 0.23.0. Its functionality was folded into --vo=gpu, which now uses
|
||||
mpv 0.23.0. Its functionality was folded into --vo=opengl, which now uses
|
||||
RPI hardware decoding by treating it as a hardware overlay (without applying
|
||||
GL filtering). Also to be changed in 0.23.0: the --fs flag will be reset to
|
||||
"no" by default (like on the other platforms).
|
||||
@@ -486,95 +488,26 @@ Available video output drivers are:
|
||||
Video output driver using Kernel Mode Setting / Direct Rendering Manager.
|
||||
Should be used when one doesn't want to install full-blown graphical
|
||||
environment (e.g. no X). Does not support hardware acceleration (if you
|
||||
need this, check the ``drm`` backend for ``gpu`` VO).
|
||||
|
||||
Since mpv 0.30.0, you may need to use ``--profile=sw-fast`` to get decent
|
||||
performance.
|
||||
need this, check the ``drm`` backend for ``opengl`` VO).
|
||||
|
||||
The following global options are supported by this video output:
|
||||
|
||||
``--drm-connector=[<gpu_number>.]<name>``
|
||||
Select the connector to use (usually this is a monitor.) If ``<name>``
|
||||
is empty or ``auto``, mpv renders the output on the first available
|
||||
connector. Use ``--drm-connector=help`` to get a list of available
|
||||
connector. Use ``--drm-connector=help`` to get list of available
|
||||
connectors. When using multiple graphic cards, use the ``<gpu_number>``
|
||||
argument to disambiguate.
|
||||
(default: empty)
|
||||
|
||||
``--drm-mode=<preferred|highest|N|WxH[@R]>``
|
||||
Mode to use (resolution and frame rate).
|
||||
Possible values:
|
||||
``--drm-mode=<number>``
|
||||
Mode ID to use (resolution, bit depth and frame rate).
|
||||
(default: 0)
|
||||
|
||||
:preferred: Use the preferred mode for the screen on the selected
|
||||
connector. (default)
|
||||
:highest: Use the mode with the highest resolution available on the
|
||||
selected connector.
|
||||
:N: Select mode by index.
|
||||
:WxH[@R]: Specify mode by width, height, and optionally refresh rate.
|
||||
In case several modes match, selects the mode that comes
|
||||
first in the EDID list of modes.
|
||||
|
||||
Use ``--drm-mode=help`` to get a list of available modes for all active
|
||||
connectors.
|
||||
|
||||
``--drm-atomic=<no|auto>``
|
||||
Toggle use of atomic modesetting. Mostly useful for debugging.
|
||||
|
||||
:no: Use legacy modesetting.
|
||||
:auto: Use atomic modesetting, falling back to legacy modesetting if
|
||||
not available. (default)
|
||||
|
||||
Note: Only affects ``gpu-context=drm``. ``vo=drm`` supports legacy
|
||||
modesetting only.
|
||||
|
||||
``--drm-draw-plane=<primary|overlay|N>``
|
||||
Select the DRM plane to which video and OSD is drawn to, under normal
|
||||
circumstances. The plane can be specified as ``primary``, which will
|
||||
pick the first applicable primary plane; ``overlay``, which will pick
|
||||
the first applicable overlay plane; or by index. The index is zero
|
||||
based, and related to the CRTC.
|
||||
(default: primary)
|
||||
|
||||
When using this option with the drmprime-drm hwdec interop, only the OSD
|
||||
is rendered to this plane.
|
||||
|
||||
``--drm-drmprime-video-plane=<primary|overlay|N>``
|
||||
Select the DRM plane to use for video with the drmprime-drm hwdec
|
||||
interop (used by e.g. the rkmpp hwdec on RockChip SoCs, and v4l2 hwdec:s
|
||||
on various other SoC:s). The plane is unused otherwise. This option
|
||||
accepts the same values as ``--drm-draw-plane``. (default: overlay)
|
||||
|
||||
To be able to successfully play 4K video on various SoCs you might need
|
||||
to set ``--drm-draw-plane=overlay --drm-drmprime-video-plane=primary``
|
||||
and setting ``--drm-draw-surface-size=1920x1080``, to render the OSD at a
|
||||
lower resolution (the video when handled by the hwdec will be on the
|
||||
drmprime-video plane and at full 4K resolution)
|
||||
|
||||
``--drm-format=<xrgb8888|xrgb2101010>``
|
||||
Select the DRM format to use (default: xrgb8888). This allows you to
|
||||
choose the bit depth of the DRM mode. xrgb8888 is your usual 24 bit per
|
||||
pixel/8 bits per channel packed RGB format with 8 bits of padding.
|
||||
xrgb2101010 is a packed 30 bits per pixel/10 bits per channel packed RGB
|
||||
format with 2 bits of padding.
|
||||
|
||||
There are cases when xrgb2101010 will work with the ``drm`` VO, but not
|
||||
with the ``drm`` backend for the ``gpu`` VO. This is because with the
|
||||
``gpu`` VO, in addition to requiring support in your DRM driver,
|
||||
requires support for xrgb2101010 in your EGL driver
|
||||
|
||||
``--drm-draw-surface-size=<[WxH]>``
|
||||
Sets the size of the surface used on the draw plane. The surface will
|
||||
then be upscaled to the current screen resolution. This option can be
|
||||
useful when used together with the drmprime-drm hwdec interop at high
|
||||
resolutions, as it allows scaling the draw plane (which in this case
|
||||
only handles the OSD) down to a size the GPU can handle.
|
||||
|
||||
When used without the drmprime-drm hwdec interop this option will just
|
||||
cause the video to get rendered at a different resolution and then
|
||||
scaled to screen size.
|
||||
|
||||
Note: this option is only available with DRM atomic support.
|
||||
(default: display resolution)
|
||||
``--drm-overlay=<number>``
|
||||
Select the DRM overlay index to use.
|
||||
Overlay index is zero based, and related to crtc.
|
||||
(default: 0)
|
||||
|
||||
``mediacodec_embed`` (Android)
|
||||
Renders ``IMGFMT_MEDIACODEC`` frames directly to an ``android.view.Surface``.
|
||||
@@ -585,14 +518,5 @@ Available video output drivers are:
|
||||
many of mpv's features (subtitle rendering, OSD/OSC, video filters, etc)
|
||||
are not available with this driver.
|
||||
|
||||
To use hardware decoding with ``--vo=gpu`` instead, use
|
||||
To use hardware decoding with ``--vo-gpu`` instead, use
|
||||
``--hwdec=mediacodec-copy`` along with ``--gpu-context=android``.
|
||||
|
||||
``wlshm`` (Wayland only)
|
||||
Shared memory video output driver without hardware acceleration that works
|
||||
whenever Wayland is present.
|
||||
|
||||
Since mpv 0.30.0, you may need to use ``--profile=sw-fast`` to get decent
|
||||
performance.
|
||||
|
||||
.. note:: This is a fallback only, and should not be normally used.
|
||||
@@ -144,6 +144,7 @@ Mac OS X
|
||||
* Native OpenGL backend.
|
||||
* Cocoa event loop is independent from MPlayer's event loop, so user
|
||||
actions like accessing menus and live resizing do not block the playback.
|
||||
* Apple Remote support.
|
||||
* Media Keys support.
|
||||
* VDA support using libavcodec hwaccel API instead of FFmpeg's decoder with up
|
||||
to 2-2.5x reduction in CPU usage.
|
||||
@@ -247,7 +248,7 @@ Command Line Switches
|
||||
``-msglevel`` ``--msg-level`` (changed semantics)
|
||||
``-msgmodule`` ``--msg-module``
|
||||
``-name`` ``--x11-name``
|
||||
``-noar`` ``(removed; replaced by MediaPlayer framework)``
|
||||
``-noar`` ``--no-input-appleremote``
|
||||
``-noautosub`` ``--no-sub-auto``
|
||||
``-noconsolecontrols`` ``--no-input-terminal``
|
||||
``-nosound`` ``--no-audio``
|
||||
|
||||
@@ -20,28 +20,24 @@ Release procedure
|
||||
|
||||
While on master:
|
||||
|
||||
- Update the `RELEASE_NOTES` file, replacing the previous release notes.
|
||||
- Update the `RELEASE_NOTES` file.
|
||||
|
||||
- Update the `VERSION` file.
|
||||
|
||||
- Update `DOCS/client-api-changes.rst` and `DOCS/interface-changes.rst`
|
||||
(in particular, update the last version numbers if necessary)
|
||||
|
||||
- Create signed commit with changes.
|
||||
- Commit changes.
|
||||
|
||||
- Create signed tag v0.X.Y.
|
||||
|
||||
- Add -UNKNOWN suffix to version in `VERSION` file.
|
||||
|
||||
- Commit changes, push release branch (`release/0.X`) and tag to GitHub.
|
||||
- Commit changes, push branch and tag to GitHub.
|
||||
|
||||
- Create a new GitHub release using the content of `RELEASE_NOTES` related to
|
||||
the new version.
|
||||
|
||||
If necessary (to e.g. exclude commits already on master), the release can
|
||||
be done on a branch with different commit history. The release branch **must**
|
||||
then be merged to master so `git describe` will pick up the tag.
|
||||
|
||||
Release notes template
|
||||
----------------------
|
||||
|
||||
|
||||
+12
-427
@@ -15,7 +15,7 @@ player/*.c:
|
||||
* parse command line, add files from the command line to playlist
|
||||
(m_config_parse_mp_command_line())
|
||||
* check help options etc. (call handle_help_options()), possibly exit
|
||||
* call mp_play_files() function that works down the playlist:
|
||||
* call play_files() function that works down the playlist:
|
||||
* run idle loop (idle_loop()), until there are files in the
|
||||
playlist or an exit command was given (only if --idle it set)
|
||||
* actually load and play a file in play_current_file():
|
||||
@@ -30,7 +30,7 @@ player/*.c:
|
||||
* determine next entry on the playlist to play
|
||||
* loop, or exit if no next file or quit is requested
|
||||
(see enum stop_play_reason)
|
||||
* call mp_destroy()
|
||||
* call exit_player_with_rc()
|
||||
* run_playloop():
|
||||
* calls fill_audio_out_buffers()
|
||||
This checks whether new audio needs to be decoded, and pushes it
|
||||
@@ -44,9 +44,9 @@ player/*.c:
|
||||
|
||||
Things worth saying about the playback core:
|
||||
- most state is in MPContext (core.h), which is not available to the
|
||||
subsystems (and should not be made available)
|
||||
subsystems
|
||||
- the currently played tracks are in mpctx->current_tracks, and decoder
|
||||
state in track.dec/d_sub
|
||||
state in track.d_video/d_audio/d_sub
|
||||
- the other subsystems rarely call back into the frontend, and the frontend
|
||||
polls them instead (probably a good thing)
|
||||
- one exceptions are wakeup callbacks, which notify a "higher" component
|
||||
@@ -68,7 +68,6 @@ ta.h & ta.c:
|
||||
as in it doesn't require valgrind.) You can enable it by setting the
|
||||
MPV_LEAK_REPORT environment variable to "1":
|
||||
export MPV_LEAK_REPORT=1
|
||||
Or permanently by building with --enable-ta-leak-report.
|
||||
This will list all unfree'd allocations on exit.
|
||||
|
||||
Documentation can be found here:
|
||||
@@ -104,45 +103,13 @@ options/options.h, options/options.c
|
||||
options.c. Most default values for options and MPOpts are in
|
||||
mp_default_opts at the end of options.c.
|
||||
|
||||
MPOpts is unfortunately quite monolithic, but is being incrementally broken
|
||||
up into sub-structs. Many components have their own sub-option structs
|
||||
separate from MPOpts. New options should be bound to the component that uses
|
||||
them. Add a new option table/struct if needed.
|
||||
|
||||
The global MPOpts still contains the sub-structs as fields, which serves to
|
||||
link them to the option parser. For example, an entry like this may be
|
||||
typical:
|
||||
|
||||
OPT_SUBSTRUCT("", demux_opts, demux_conf, 0),
|
||||
|
||||
This directs the option access code to include all options in demux_conf
|
||||
into the global option list, with no prefix (""), and as part of the
|
||||
MPOpts.demux_opts field. The MPOpts.demux_opts field is actually not
|
||||
accessed anywhere, and instead demux.c does this:
|
||||
|
||||
struct m_config_cache *opts_cache =
|
||||
m_config_cache_alloc(demuxer, global, &demux_conf);
|
||||
struct demux_opts *opts = opts_cache->opts;
|
||||
|
||||
... to get a copy of its options.
|
||||
|
||||
See m_config.h (below) how to access options.
|
||||
MPOpts is unfortunately quite monolithic, and virtually accessed by
|
||||
everything.But some components (like video outputs and video filters) have
|
||||
their own sub-option tables separate from MPOpts.
|
||||
|
||||
The actual option parser is spread over m_option.c, m_config.c, and
|
||||
parse_commandline.c, and uses the option table in options.c.
|
||||
|
||||
options/m_config.h & m_config.c:
|
||||
Code for querying and managing options. This (unfortunately) contains both
|
||||
declarations for the "legacy-ish" global m_config struct, and ways to access
|
||||
options in a threads-safe way anywhere, like m_config_cache_alloc().
|
||||
|
||||
m_config_cache_alloc() lets anyone read, observe, and write options in any
|
||||
thread. The only state it needs is struct mpv_global, which is an opaque
|
||||
type that can be passed "down" the component hierarchy. For safety reasons,
|
||||
you should not pass down any pointers to option structs (like MPOpts), but
|
||||
instead pass down mpv_global, and use m_config_cache_alloc() (or similar)
|
||||
to get a synchronized copy of the options.
|
||||
|
||||
input/input.c:
|
||||
This translates keyboard input coming from VOs and other sources (such
|
||||
as remote control devices like Apple IR or client API commands) to the
|
||||
@@ -172,6 +139,9 @@ stream/*:
|
||||
Some stream inputs are just there to invoke special demuxers, like
|
||||
stream_mf.c. (Basically to make the prefix "mf://" do something special.)
|
||||
|
||||
cache.c is a caching wrapper around streams implementations, needed for
|
||||
smooth network playback.
|
||||
|
||||
demux/:
|
||||
Demuxers split data streams into audio/video/sub streams, which in turn
|
||||
are split in packets. Packets (see demux_packet.h) are mostly byte chunks
|
||||
@@ -185,10 +155,6 @@ demux/:
|
||||
There is a stream header for each audio/video/sub stream, and each of them
|
||||
holds codec information about the stream and other information.
|
||||
|
||||
demux.c is a bit big, the main reason being that it contains the demuxer
|
||||
cache, which is implemented as a list of packets. The cache is complex
|
||||
because it support seeking, multiple ranges, prefetching, and so on.
|
||||
|
||||
video/:
|
||||
This contains several things related to audio/video decoding, as well as
|
||||
video filters.
|
||||
@@ -211,13 +177,13 @@ video/out/:
|
||||
Video output. They also create GUI windows and handle user input. In most
|
||||
cases, the windowing code is shared among VOs, like x11_common.c for X11 and
|
||||
w32_common.c for Windows. The VOs stand between frontend and windowing code.
|
||||
vo_gpu can pick a windowing system at runtime, e.g. the same binary can
|
||||
vo_opengl can pick a windowing system at runtime, e.g. the same binary can
|
||||
provide both X11 and Cocoa support on OSX.
|
||||
|
||||
VOs can be reconfigured at runtime. A vo_reconfig() call can change the video
|
||||
resolution and format, without destroying the window.
|
||||
|
||||
vo_gpu should be taken as reference.
|
||||
vo_opengl should be taken as reference.
|
||||
|
||||
audio/:
|
||||
format.h/format.c define the uncompressed audio formats. (As well as some
|
||||
@@ -274,384 +240,3 @@ sub/:
|
||||
etc/:
|
||||
The file input.conf is actually integrated into the mpv binary by the
|
||||
build system. It contains the default keybindings.
|
||||
|
||||
Best practices and Concepts within mpv
|
||||
======================================
|
||||
|
||||
General contribution etc.
|
||||
-------------------------
|
||||
|
||||
See: DOCS/contribute.md
|
||||
|
||||
Error checking
|
||||
--------------
|
||||
|
||||
If an error is relevant, it should be handled. If it's interesting, log the
|
||||
error. However, mpv often keeps errors silent and reports failures somewhat
|
||||
coarsely by propagating them upwards the caller chain. This is OK, as long as
|
||||
the errors are not very interesting, or would require a developer to debug it
|
||||
anyway (in which case using a debugger would be more convenient, and the
|
||||
developer would need to add temporary debug printfs to get extremely detailed
|
||||
information which would not be appropriate during normal operation).
|
||||
|
||||
Basically, keep a balance on error reporting. But always check them, unless you
|
||||
have a good argument not to.
|
||||
|
||||
Memory allocation errors (OOM) are a special class of errors. Normally such
|
||||
allocation failures are not handled "properly". Instead, abort() is called.
|
||||
(New code should use MP_HANDLE_OOM() for this.) This is done out of laziness and
|
||||
for convenience, and due to the fact that MPlayer/mplayer2 never handled it
|
||||
correctly. (MPlayer varied between handling it correctly, trying to do so but
|
||||
failing, and just not caring, while mplayer2 started using abort() for it.)
|
||||
|
||||
This is justifiable in a number of ways. Error handling paths are notoriously
|
||||
untested and buggy, so merely having them won't make your program more reliable.
|
||||
Having these error handling paths also complicates non-error code, due to the
|
||||
need to roll back state at any point after a memory allocation.
|
||||
|
||||
Take any larger body of code, that is supposed to handle OOM, and test whether
|
||||
the error paths actually work, for example by overriding malloc with a version
|
||||
that randomly fails. You will find bugs quickly, and often they will be very
|
||||
annoying to fix (if you can even reproduce them).
|
||||
|
||||
In addition, a clear indication that something went wrong may be missing. On
|
||||
error your program may exhibit "degraded" behavior by design. Consider a video
|
||||
encoder dropping frames somewhere in the middle of a video due to temporary
|
||||
allocation failures, instead of just exiting with an errors. In other cases, it
|
||||
may open conceptual security holes. Failing fast may be better.
|
||||
|
||||
mpv uses GPU APIs, which may be break on allocation errors (because driver
|
||||
authors will have the same issues as described here), or don't even have a real
|
||||
concept for dealing with OOM (OpenGL).
|
||||
|
||||
libmpv is often used by GUIs, which I predict always break if OOM happens.
|
||||
|
||||
Last but not least, OSes like Linux use "overcommit", which basically means that
|
||||
your program may crash any time OOM happens, even if it doesn't use malloc() at
|
||||
all!
|
||||
|
||||
But still, don't just assume malloc() always succeeds. Use MP_HANDLE_OOM(). The
|
||||
ta* APIs do this for you. The reason for this is that dereferencing a NULL
|
||||
pointer can have security relevant consequences if large offsets are involved.
|
||||
Also, a clear error message is better than a random segfault.
|
||||
|
||||
Some big memory allocations are checked anyway. For example, all code must
|
||||
assume that allocating video frames or packets can fail. (The above example
|
||||
of dropping video frames during encoding is entirely possible in mpv.)
|
||||
|
||||
Undefined behavior
|
||||
------------------
|
||||
|
||||
Undefined behavior (UB) is a concept in the C language. C is famous for being a
|
||||
language that makes it almost impossible to write working code, because
|
||||
undefined behavior is so easily triggered, compilers will happily abuse it to
|
||||
generate "faster" code, debugging tools will shout at you, and sometimes it
|
||||
even means your code doesn't work.
|
||||
|
||||
There is a lot of literature on this topic. Read it.
|
||||
|
||||
(In C's defense, UB exists in other languages too, but since they're not used
|
||||
for low level infrastructure, and/or these languages are at times not rigorously
|
||||
defined, simply nobody cares. However, the C standard committee is still guilty
|
||||
for not addressing this. I'll admit that I can't even tell from the standard's
|
||||
gibberish whether some specific behavior is UB or not. It's written like tax
|
||||
law.)
|
||||
|
||||
In mpv, we generally try to avoid undefined behavior. For one, we want portable
|
||||
and reliable operation. But more importantly, we want clean output from
|
||||
debugging tools, in order to find real bugs more quickly and effectively.
|
||||
|
||||
Avoid the "works in practice" argument. Once debugging tools come into play, or
|
||||
simply when "in practice" stops being true, this will all get back to you in a
|
||||
bad way.
|
||||
|
||||
Global state, library safety
|
||||
----------------------------
|
||||
|
||||
Mutable global state is when code uses global variables that are not read-only.
|
||||
This must be avoided in mpv. Always use context structs that the caller of
|
||||
your code needs to allocate, and whose pointers are passed to your functions.
|
||||
|
||||
Library safety means that your code (or library) can be used by a library
|
||||
without causing conflicts with other library users in the same process. To any
|
||||
piece of code, a "safe" library's API can simply be used, without having to
|
||||
worry about other API users that may be around somewhere.
|
||||
|
||||
Libraries are often not library safe, because they they use global mutable state
|
||||
or other "global" resources. Typical examples include use of signals, simple
|
||||
global variables (like hsearch() in libc), or internal caches not protected by
|
||||
locks.
|
||||
|
||||
A surprisingly high number of libraries are not library safe because they need
|
||||
global initialization. Typically they provide an API function, which
|
||||
"initializes" the library, and which must be called before calling any other
|
||||
API functions. Often, you are to provide global configuration parameters, which
|
||||
can change the behavior of the library. If two libraries A and B use library C,
|
||||
but A and B initialize C with different parameters, something "bad" may happen.
|
||||
In addition, these global initialization functions are often not thread-safe. So
|
||||
if A and B try to initialize C at the same time (from different threads and
|
||||
without knowing about each other), it may cause undefined behavior. (libcurl is
|
||||
a good example of both of these issues. FFmpeg and some TLS libraries used to be
|
||||
affected, but improved.)
|
||||
|
||||
This is so bad because library A and B from the previous example most likely
|
||||
have no way to cooperate, because they're from different authors and have no
|
||||
business knowing each others. They'd need a library D, which wraps library C
|
||||
in a safe way. Unfortunately, typically something worse happens: libraries get
|
||||
"infected" by the unsafeness of its sub-libraries, and export a global init API
|
||||
just to initialize the sub-libraries. In the previous example, libraries A and B
|
||||
would export global init APIs just to init library C, even though the rest of
|
||||
A/B are clean and library safe. (Again, libcurl is an example of this, if you
|
||||
subtract other historic anti-features.)
|
||||
|
||||
The main problem with library safety is that its lack propagates to all
|
||||
libraries using the library.
|
||||
|
||||
We require libmpv to be library safe. This is not really possible, because some
|
||||
libraries are not library safe (FFmpeg, Xlib, partially ALSA). However, for
|
||||
ideological reasons, there is no global init API, and best effort is made to try
|
||||
to avoid problems.
|
||||
|
||||
libmpv has some features that are not library safe, but which are disabled by
|
||||
default (such as terminal usage aka stdout, or JSON IPC blocking SIGPIPE for
|
||||
internal convenience).
|
||||
|
||||
A notable, very disgustingly library unsafe behavior of libmpv is calling
|
||||
abort() on some memory allocation failure. See error checking section.
|
||||
|
||||
Logging
|
||||
-------
|
||||
|
||||
All logging and terminal output in mpv goes through the functions and macros
|
||||
provided in common/msg.h. This is in part for library safety, and in part to
|
||||
make sure users can silence all output, or to redirect the output elsewhere,
|
||||
like a log file or the internal console.lua script.
|
||||
|
||||
Locking
|
||||
-------
|
||||
|
||||
See generally available literature. In mpv, we use pthread for this.
|
||||
|
||||
Always keep locking clean. Don't skip locking just because it will work "in
|
||||
practice". (See undefined behavior section.) If your use case is simple, you may
|
||||
use C11 atomics (osdep/atomic.h for partial C99 support), but most likely you
|
||||
will only hurt yourself and others.
|
||||
|
||||
Always make clear which fields in a struct are protected by which lock. If a
|
||||
field is immutable, or simply not thread-safe (e.g. state for a single worker
|
||||
thread), document it as well.
|
||||
|
||||
Internal mpv APIs are assumed to be not thread-safe by default. If they have
|
||||
special guarantees (such as being usable by more than one thread at a time),
|
||||
these should be explicitly documented.
|
||||
|
||||
All internal mpv APIs must be free of global state. Even if a component is not
|
||||
thread-safe, multiple threads can use _different_ instances of it without any
|
||||
locking.
|
||||
|
||||
On a side note, recursive locks may seem convenient at first, but introduce
|
||||
additional problems with condition variables and locking hierarchies. They
|
||||
should be avoided.
|
||||
|
||||
Locking hierarchy
|
||||
-----------------
|
||||
|
||||
A simple way to avoid deadlocks with classic locking is to define a locking
|
||||
hierarchy or lock order. If all threads acquire locks in the same order, no
|
||||
deadlocks will happen.
|
||||
|
||||
For example, a "leaf" lock is a lock that is below all other locks in the
|
||||
hierarchy. You can acquire it any time, as long as you don't acquire other
|
||||
locks while holding it.
|
||||
|
||||
Unfortunately, C has no way to declare or check the lock order, so you should at
|
||||
least document it.
|
||||
|
||||
In addition, try to avoid exposing locks to the outside. Making the declaration
|
||||
of a lock private to a specific .c file (and _not_ exporting accessors or
|
||||
lock/unlock functions that manipulate the lock) is a good idea. Your component's
|
||||
API may acquire internal locks, but should release them when returning. Keeping
|
||||
the entire locking in a single file makes it easy to check it.
|
||||
|
||||
Avoiding callback hell
|
||||
----------------------
|
||||
|
||||
mpv code is separated in components, like the "frontend" (i.e. MPContext mpctx),
|
||||
VOs, AOs, demuxers, and more. The frontend usually calls "down" the usage
|
||||
hierarchy: mpctx almost on top, then things like vo/ao, and utility code on the
|
||||
very bottom.
|
||||
|
||||
"Callback hell" is when when components call both up and down the hierarchy,
|
||||
which for example leads to accidentally recursion, reentrancy problems, or
|
||||
locking nightmares. This is avoided by (mostly) calling only down the hierarchy.
|
||||
Basically the call graph forms a DAG. The other direction is handled by event
|
||||
queues, wakeup callbacks, and similar mechanisms.
|
||||
|
||||
Typically, a component provides an API, and does not know anything about its
|
||||
user. The API user (component higher in the hierarchy) polls the state of the
|
||||
lower component when needed.
|
||||
|
||||
This also enforces some level of modularization, and with some luck the locking
|
||||
hierarchy. (Basically, locks of lower components automatically become leaf
|
||||
locks.) Another positive effect is simpler memory management.
|
||||
|
||||
(Also see e.g.: http://250bpm.com/blog:24)
|
||||
|
||||
Wakeup callbacks
|
||||
----------------
|
||||
|
||||
This is a common concept in mpv. Even the public API uses it. It's used when an
|
||||
API has internal threads (or otherwise triggers asynchronous events), but the
|
||||
component call hierarchy needs to be kept. The wakeup callback is the only
|
||||
exception to the call hierarchy, and always calls up.
|
||||
|
||||
For example, vo spawns a thread that the API user (the mpv frontend) does not
|
||||
need to know about. vo simply provides a single-threaded API (or that looks like
|
||||
one). This API needs a way to notify the API user of new events. But the vo
|
||||
event producer is on the vo thread - it can't simply invoke a callback back into
|
||||
the API user, because then the API user has to deal with locking, despite not
|
||||
using threads. In addition, this will probably cause problems like mentioned in
|
||||
the "callback hell" section, especially lock order issues.
|
||||
|
||||
The solution is the wakeup callback. It merely unblocks the API user from
|
||||
waiting, and the API user then uses the normal vo API to examine whether or
|
||||
which state changed. As a concept, it documents what a wakeup callback is
|
||||
allowed to do and what not, to avoid the aforementioned problems.
|
||||
|
||||
Generally, you are not allowed to call any API from the wakeup callback. You
|
||||
just do whatever is needed to unblock your thread. For example, if it's waiting
|
||||
on a mutex/condition variable, acquire the mutex, set a change flag, signal
|
||||
the condition variable, unlock, return. (This mutex must not be held when
|
||||
calling the API. It must be a leaf lock.)
|
||||
|
||||
Restricting the wakeup callback like this sidesteps any reentrancy issues and
|
||||
other complexities. The API implementation can simply hold internal (and
|
||||
non-recursive) locks while invoking the wakeup callback.
|
||||
|
||||
The API user still needs to deal with locking (probably), but there's only the
|
||||
need to implement a single "receiver", that can handle the entire API of the
|
||||
used component. (Or multiple APIs - MPContext for example has only 1 wakeup
|
||||
callback that handles all AOs, VOs, input, demuxers, and more. It simple re-runs
|
||||
the playloop.)
|
||||
|
||||
You could get something more advanced by turning this into a message queue. The
|
||||
API would append a message to the queue, and the API user can read it. But then
|
||||
you still need a way to "wakeup" the API user (unless you force the API user
|
||||
to block on your API, which will make things inconvenient for the API user). You
|
||||
also need to worry about what happens if the message queue overruns (you either
|
||||
lose messages or have unbounded memory usage). In the mpv public API, the
|
||||
distinction between message queue and wakeup callback is sort of blurry, because
|
||||
it does provide a message queue, but an additional wakeup callback, so API
|
||||
users are not required to call mpv_wait_event() with a high timeout.
|
||||
|
||||
mpv itself prefers using wakeup callbacks over a generic event queue, because
|
||||
most times an event queue is not needed (or complicates things), and it is
|
||||
better to do it manually.
|
||||
|
||||
(You could still abstract the API user side of wakeup callback handling, and
|
||||
avoid reimplementing it all the time. Although mp_dispatch_queue already
|
||||
provides mechanisms for this.)
|
||||
|
||||
Condition variables
|
||||
-------------------
|
||||
|
||||
They're used whenever a thread needs to wait for something, without nonsense
|
||||
like sleep calls or busy waiting. mpv uses the standard pthread API for this.
|
||||
There's a lot of literature on it. Read it.
|
||||
|
||||
For initial understanding, it may be helpful to know that condition variables
|
||||
are not variables that signal a condition. pthread_cond_t does not have any
|
||||
state per-se. Maybe pthread_cond_t would better be named pthread_interrupt_t,
|
||||
because its sole purpose is to interrupt a thread waiting via pthread_cond_wait()
|
||||
(or similar). The "something" in "waiting for something" can be called
|
||||
predicate (to avoid confusing it with "condition"). Consult literature for the
|
||||
proper terms.
|
||||
|
||||
The very short version is...
|
||||
|
||||
Shared declarations:
|
||||
|
||||
pthread_mutex_t lock;
|
||||
pthread_cond_t cond_var;
|
||||
struct something state_var; // protected by lock, changes signaled by cond_var
|
||||
|
||||
Waiter thread:
|
||||
|
||||
pthread_mutex_lock(&lock);
|
||||
|
||||
// Wait for a change in state_var. We want to wait until predicate_fulfilled()
|
||||
// returns true.
|
||||
// Must be a loop for 2 reasons:
|
||||
// 1. cond_var may be associated with other conditions too
|
||||
// 2. pthread_cond_wait() can have sporadic wakeups
|
||||
while (!predicate_fulfilled(&state_var)) {
|
||||
// This unlocks, waits for cond_var to be signaled, and then locks again.
|
||||
// The _whole_ point of cond_var is that unlocking and waiting for the
|
||||
// signal happens atomically.
|
||||
pthread_cond_wait(&cond_var, &lock);
|
||||
}
|
||||
|
||||
// Here you may react to the state change. The state cannot change
|
||||
// asynchronously as long as you still hold the lock (and didn't release
|
||||
// and reacquire it).
|
||||
// ...
|
||||
|
||||
pthread_mutex_unlock(&lock);
|
||||
|
||||
Signaler thread:
|
||||
|
||||
pthread_mutex_lock(&lock);
|
||||
|
||||
// Something changed. Update the shared variable with the new state.
|
||||
update_state(&state_var);
|
||||
|
||||
// Notify that something changed. This will wake up the waiter thread if
|
||||
// it's blocked in pthread_cond_wait(). If not, nothing happens.
|
||||
pthread_cond_broadcast(&cond_var);
|
||||
|
||||
// Fun fact: good implementations wake up the waiter only when the lock is
|
||||
// released, to reduce kernel scheduling overhead.
|
||||
pthread_mutex_unlock(&lock);
|
||||
|
||||
Some basic rules:
|
||||
1. Always access your state under proper locking
|
||||
2. Always check your predicate before every call to pthread_cond_wait()
|
||||
(And don't call pthread_cond_wait() if the predicate is fulfilled.)
|
||||
3. Always call pthread_cond_wait() in a loop
|
||||
(And only if your predicate failed without releasing the lock..)
|
||||
4. Always call pthread_cond_broadcast()/_signal() inside of its associated
|
||||
lock
|
||||
|
||||
mpv sometimes violates rule 3, and leaves "retrying" (i.e. looping) to the
|
||||
caller.
|
||||
|
||||
Common pitfalls:
|
||||
- Thinking that pthread_cond_t is some kind of semaphore, or holds any
|
||||
application state or the user predicate (it _only_ wakes up threads
|
||||
that are at the same time blocking on pthread_cond_wait() and friends,
|
||||
nothing else)
|
||||
- Changing the predicate, but not updating all pthread_cond_broadcast()/
|
||||
_signal() calls correctly
|
||||
- Forgetting that pthread_cond_wait() unlocks the lock (other threads can
|
||||
and must acquire the lock)
|
||||
- Holding multiple nested locks while trying to wait (=> deadlock, violates
|
||||
the lock order anyway)
|
||||
- Waiting for a predicate correctly, but unlocking/relocking before acting
|
||||
on it (unlocking allows arbitrary state changes)
|
||||
- Confusing which lock/condition var. is used to manage a bit of state
|
||||
|
||||
Generally available literature probably has better examples and explanations.
|
||||
|
||||
Using condition variables the proper way is generally preferred over using more
|
||||
messy variants of them. (Just saying because on win32, "SetEvent" exists, and
|
||||
it's inferior to condition variables. Try to avoid the win32 primitives, even if
|
||||
you're dealing with Windows-only code.)
|
||||
|
||||
Threads
|
||||
-------
|
||||
|
||||
Threading should be conservatively used. Normally, mpv code pretends to be
|
||||
single-threaded, and provides thread-unsafe APIs. Threads are used coarsely,
|
||||
and if you can avoid messing with threads, you should. For example, VOs and AOs
|
||||
do not need to deal with threads normally, even though they run on separate
|
||||
threads. The glue code "isolates" them from any threading issues.
|
||||
@@ -1,6 +1,8 @@
|
||||

|
||||

|
||||
|
||||
# mpv
|
||||
## mpv
|
||||
|
||||
--------------
|
||||
|
||||
|
||||
* [External links](#external-links)
|
||||
@@ -9,9 +11,12 @@
|
||||
* [Downloads](#downloads)
|
||||
* [Changelog](#changelog)
|
||||
* [Compilation](#compilation)
|
||||
* [FFmpeg vs. Libav](#ffmpeg-vs-libav)
|
||||
* [FFmpeg ABI compatibility](#ffmpeg-abi-compatibility)
|
||||
* [Release cycle](#release-cycle)
|
||||
* [Bug reports](#bug-reports)
|
||||
* [Contributing](#contributing)
|
||||
* [Relation to MPlayer and mplayer2](#relation-to-mplayer-and-mplayer2)
|
||||
* [License](#license)
|
||||
* [Contact](#contact)
|
||||
|
||||
@@ -20,17 +25,15 @@
|
||||
|
||||
|
||||
* [Wiki](https://github.com/mpv-player/mpv/wiki)
|
||||
* [FAQ][FAQ]
|
||||
* [FAQ](https://github.com/mpv-player/mpv/wiki/FAQ)
|
||||
* [Manual](http://mpv.io/manual/master/)
|
||||
|
||||
|
||||
## Overview
|
||||
|
||||
|
||||
**mpv** is a free (as in freedom) media player for the command line. It supports
|
||||
a wide variety of media file formats, audio and video codecs, and subtitle types.
|
||||
|
||||
There is a [FAQ][FAQ].
|
||||
**mpv** is a media player based on MPlayer and mplayer2. It supports a wide
|
||||
variety of video file formats, audio and video codecs, and subtitle types.
|
||||
|
||||
Releases can be found on the [release list][releases].
|
||||
|
||||
@@ -40,14 +43,11 @@ Releases can be found on the [release list][releases].
|
||||
- A somewhat capable CPU. Hardware decoding might help if the CPU is too slow to
|
||||
decode video in realtime, but must be explicitly enabled with the `--hwdec`
|
||||
option.
|
||||
- A not too crappy GPU. mpv's focus is not on power-efficient playback on
|
||||
embedded or integrated GPUs (for example, hardware decoding is not even
|
||||
enabled by default). Low power GPUs may cause issues like tearing, stutter,
|
||||
etc. The main video output uses shaders for video rendering and scaling,
|
||||
rather than GPU fixed function hardware. On Windows, you might want to make
|
||||
sure the graphics drivers are current. In some cases, ancient fallback video
|
||||
output methods can help (such as `--vo=xv` on Linux), but this use is not
|
||||
recommended or supported.
|
||||
- A not too crappy GPU. mpv is not intended to be used with bad GPUs. There are
|
||||
many caveats with drivers or system compositors causing tearing, stutter,
|
||||
etc. On Windows, you might want to make sure the graphics drivers are
|
||||
current. In some cases, ancient fallback video output methods can help
|
||||
(such as `--vo=xv` on Linux), but this use is not recommended or supported.
|
||||
|
||||
|
||||
## Downloads
|
||||
@@ -116,9 +116,9 @@ Essential dependencies (incomplete list):
|
||||
- Lua (optional, required for the OSC pseudo-GUI and youtube-dl integration)
|
||||
- libjpeg (optional, used for screenshots only)
|
||||
- uchardet (optional, for subtitle charset detection)
|
||||
- nvdec and vaapi libraries for hardware decoding on Linux (optional)
|
||||
- vdpau and vaapi libraries for hardware decoding on Linux (optional)
|
||||
|
||||
Libass dependencies (when building libass):
|
||||
Libass dependencies:
|
||||
|
||||
- gcc or clang, yasm on x86 and x86_64
|
||||
- fribidi, freetype, fontconfig development headers (for libass)
|
||||
@@ -126,17 +126,13 @@ Libass dependencies (when building libass):
|
||||
particularly for correct rendering of non-English text on OSX, and
|
||||
Arabic/Indic scripts on any platform)
|
||||
|
||||
FFmpeg dependencies (when building FFmpeg):
|
||||
FFmpeg dependencies:
|
||||
|
||||
- gcc or clang, yasm on x86 and x86_64
|
||||
- OpenSSL or GnuTLS (have to be explicitly enabled when compiling FFmpeg)
|
||||
- libx264/libmp3lame/libfdk-aac if you want to use encoding (have to be
|
||||
explicitly enabled when compiling FFmpeg)
|
||||
- For native DASH playback, FFmpeg needs to be built with --enable-libxml2
|
||||
(although there are security implications, and DASH support has lots of bugs).
|
||||
- AV1 decoding support requires dav1d.
|
||||
- For good nvidia support on Linux, make sure nv-codec-headers is installed
|
||||
and can be found by configure.
|
||||
- Libav also works, but some features will not work. (See section below.)
|
||||
|
||||
Most of the above libraries are available in suitable versions on normal
|
||||
Linux distributions. For ease of compiling the latest git master of everything,
|
||||
@@ -149,6 +145,28 @@ or cross-compile from Linux with MinGW. See
|
||||
[Windows compilation][windows_compilation].
|
||||
|
||||
|
||||
## FFmpeg vs. Libav
|
||||
|
||||
|
||||
Generally, mpv should work with the latest release as well as the git version
|
||||
of both FFmpeg and Libav. But FFmpeg is preferred, and some mpv features work
|
||||
with FFmpeg only (subtitle formats in particular).
|
||||
|
||||
|
||||
## FFmpeg ABI compatibility
|
||||
|
||||
mpv does not support linking against FFmpeg versions it was not built with, even
|
||||
if the linked version is supposedly ABI-compatible with the version it was
|
||||
compiled against. Expect malfunctions, crashes, and security issues if you
|
||||
do it anyway.
|
||||
|
||||
The reason for not supporting this is because it creates far too much complexity
|
||||
with little to no benefit, coupled with absurd and unusable FFmpeg API
|
||||
artifacts.
|
||||
|
||||
Newer mpv versions will refuse to start if runtime and compile time FFmpeg
|
||||
library versions mismatch.
|
||||
|
||||
## Release cycle
|
||||
|
||||
Every other month, an arbitrary git snapshot is made, and is assigned
|
||||
@@ -185,32 +203,42 @@ You can check [the wiki](https://github.com/mpv-player/mpv/wiki/Stuff-to-do)
|
||||
or the [issue tracker](https://github.com/mpv-player/mpv/issues?q=is%3Aopen+is%3Aissue+label%3A%22feature+request%22)
|
||||
for ideas on what you could contribute with.
|
||||
|
||||
## Relation to MPlayer and mplayer2
|
||||
|
||||
mpv is a fork of MPlayer. Much has changed, and in general, mpv should be
|
||||
considered a completely new program, rather than a MPlayer drop-in replacement.
|
||||
|
||||
For details see [FAQ entry](https://github.com/mpv-player/mpv/wiki/FAQ#How_is_mpv_related_to_MPlayer).
|
||||
|
||||
If you are wondering what's different from mplayer2 and MPlayer, an incomplete
|
||||
and largely unmaintained list of changes is located [here][mplayer-changes].
|
||||
|
||||
## License
|
||||
|
||||
GPLv2 "or later" by default, LGPLv2.1 "or later" with `--enable-lgpl`.
|
||||
See [details.](https://github.com/mpv-player/mpv/blob/master/Copyright)
|
||||
|
||||
## History
|
||||
|
||||
This software is based on the MPlayer project. Before mpv existed as a project,
|
||||
the code base was briefly developed under the mplayer2 project. For details,
|
||||
see the [FAQ][FAQ].
|
||||
|
||||
## Contact
|
||||
|
||||
|
||||
Most activity happens on the IRC channel and the github issue tracker.
|
||||
|
||||
- **GitHub issue tracker**: [issue tracker][issue-tracker] (report bugs here)
|
||||
- **User IRC Channel**: `#mpv` on `irc.freenode.net`
|
||||
- **Developer IRC Channel**: `#mpv-devel` on `irc.freenode.net`
|
||||
- **GitHub issue tracker**: [issue tracker][issue-tracker] (report bugs here)
|
||||
- **User IRC Channel**: `#mpv` on `irc.freenode.net`
|
||||
- **Developer IRC Channel**: `#mpv-devel` on `irc.freenode.net`
|
||||
|
||||
To contact the `mpv` team in private write to `mpv-team@googlegroups.com`. Use
|
||||
only if discretion is required.
|
||||
|
||||
[FAQ]: https://github.com/mpv-player/mpv/wiki/FAQ
|
||||
[releases]: https://github.com/mpv-player/mpv/releases
|
||||
[mpv-build]: https://github.com/mpv-player/mpv-build
|
||||
[homebrew-mpv]: https://github.com/mpv-player/homebrew-mpv
|
||||
[issue-tracker]: https://github.com/mpv-player/mpv/issues
|
||||
[ffmpeg_vs_libav]: https://github.com/mpv-player/mpv/wiki/FFmpeg-versus-Libav
|
||||
[release-policy]: https://github.com/mpv-player/mpv/blob/master/DOCS/release-policy.md
|
||||
[windows_compilation]: https://github.com/mpv-player/mpv/blob/master/DOCS/compile-windows.md
|
||||
[mplayer-changes]: https://github.com/mpv-player/mpv/blob/master/DOCS/mplayer-changes.rst
|
||||
[interface-changes]: https://github.com/mpv-player/mpv/blob/master/DOCS/interface-changes.rst
|
||||
[api-changes]: https://github.com/mpv-player/mpv/blob/master/DOCS/client-api-changes.rst
|
||||
[restore-old-bindings]: https://github.com/mpv-player/mpv/blob/master/etc/restore-old-bindings.conf
|
||||
|
||||
+133
-62
@@ -1,8 +1,36 @@
|
||||
Release 0.31.0
|
||||
Release 0.28.2
|
||||
==============
|
||||
|
||||
This release requires FFmpeg 4.0 or newer.
|
||||
This release contains an additional fix for CVE-2018-6360.
|
||||
|
||||
Fixes and Minor Enhancements
|
||||
----------------------------
|
||||
- ytdl_hook: whitelist subtitle URLs as well (#5456)
|
||||
|
||||
Release 0.28.1
|
||||
==============
|
||||
|
||||
This release fixes CVE-2018-6360.
|
||||
|
||||
Fixes and Minor Enhancements
|
||||
----------------------------
|
||||
- ytdl_hook: whitelist protocols from urls retrieved from youtube-dl (#5456)
|
||||
|
||||
Release 0.28.0
|
||||
==============
|
||||
|
||||
This release needs recent FFmpeg (newer than 3.4) due to major refactoring.
|
||||
Required library versions:
|
||||
|
||||
* libavutil >= 56.6.100
|
||||
* libavcodec >= 58.7.100
|
||||
* libavformat >= 58.0.102
|
||||
* libswscale >= 5.0.101
|
||||
* libavfilter >= 7.0.101
|
||||
* libswresample >= 3.0.100
|
||||
|
||||
The LGPL 2.1+ relicensing process is now mostly complete. The remaining GPL-only
|
||||
code (see Copyright file) can be disabled at build time by using --enable-lgpl.
|
||||
|
||||
Features
|
||||
--------
|
||||
@@ -10,32 +38,33 @@ Features
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- vo_gpu/d3d11: allow configurable swapchain color space for native HDR output
|
||||
- player: add mechanisms for better logging as preperation for console.lua
|
||||
- audio: add ao_audiotrack for Android
|
||||
- input: introduce a pseudo key name that grabs all text input
|
||||
- wayland: add grab zone for resizing window with mouse
|
||||
- osc: implement pseudo client side decorations via OSC
|
||||
- vf_gpu: add video filter using vo_gpu's renderer
|
||||
- added console.lua: interactive REPL for input commands
|
||||
- osc: display Santa hat for idle logo in December
|
||||
- vd_lavc: add option to autoselect known good hwdec implementations
|
||||
- mac: replace old event tap for media key support with MediaPlayer
|
||||
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- input: disable gamepad code by default
|
||||
- vo_gpu: allow vdpau usage under EGL
|
||||
- stream, demux: redo origin policy
|
||||
- Add DRM_PRIME Format Handling and Display for RockChip MPP decoders
|
||||
- csputils: Add support for Display P3 primaries
|
||||
- demux: support multiple seekable cached ranges, display cache ranges on OSC
|
||||
- demux_playlist: support .url files (#5107)
|
||||
- dvb: Add multiple frontends support (up to 8)
|
||||
- dvb: implement parsing of modulation for VDR-style channels config
|
||||
- hwdec: add mediacodec hardware decoder for IMGFMT_MEDIACODEC frames,
|
||||
rename mediacodec to mediacodec-copy
|
||||
- lua: integrate stats.lua script (bound to i/I by default)
|
||||
- vd_lavc: add support for nvdec hwaccel
|
||||
- vo_gpu: add android opengl backend
|
||||
- vo_gpu: initial d3d11 support
|
||||
- vo_gpu: vulkan support
|
||||
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- mac: remove Apple Remote support
|
||||
- client API: deprecate tick event
|
||||
- af: remove deprecated audio filters (channels, equalizer, pan, volume;
|
||||
replacements in lavfi)
|
||||
- vf: remove most GPL video filters (crop, dsize, expand, flip, gradfun, mirror,
|
||||
noformat, pullup, rotate, scale, stereo3d, yadif; replacements in lavfi)
|
||||
- vf_buffer: remove this filter
|
||||
- video: remove automatic stereo3d filter insertion
|
||||
- vo_gpu: remove hwdec_vaglx interop
|
||||
- vo_opengl: refactor into vo_gpu
|
||||
- vo_wayland: remove
|
||||
|
||||
|
||||
Options and Commands
|
||||
@@ -44,64 +73,106 @@ Options and Commands
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- vo_gpu/d3d11: add --d3d11-output-csp for configuring swap chain color space
|
||||
- command: add command-list property
|
||||
- command, input: add input-bindings property
|
||||
- command: add --window-maximized and make --window-minimized settable
|
||||
- command: add property returning hidpi scale
|
||||
- command, lua: add a way to share data between scripts
|
||||
- client API, lua: add new API for setting OSD overlays
|
||||
- demux: add option to create CC tracks eagerly (--sub-create-cc-track)
|
||||
- options: add --start=none to reset previously set start time
|
||||
- options: add --vlang switch
|
||||
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- add a builtin "sw-fast" profile, which restores performance settings
|
||||
for software video conversion. These were switched to higher quality.
|
||||
This affects video outputs like vo_x11 and vo_drm, and screenshots.
|
||||
- options: make --show-profile without parameters list all profiles
|
||||
- wayland: support for --hidpi-window-scale option
|
||||
- stream_lavf: set --network-timeout to 60 seconds by default
|
||||
- x11: scale window-scale by DPI
|
||||
- cache: lower default size to 2*10MB
|
||||
- demux: bump the demuxer cache readahead duration to 10 hours
|
||||
- demux: use seekable cache for network by default, bump prefetch limit
|
||||
- msg: make --msg-level affect --log-file too
|
||||
- player/misc.c: allow both --length and --end to control play endpoint
|
||||
- player: match subtitles with language tags with --sub-auto=exact
|
||||
- rename --opengl-hwdec-interop to --gpu-hwdec-interop (now mostly useless)
|
||||
- vd_lavc: prefer nvdec over vdpau with --hwdec=auto (better codec and surface
|
||||
format support)
|
||||
- vd_lavc: rename --hwdec=rpi to --hwdec=mmal
|
||||
|
||||
|
||||
Deprecated
|
||||
~~~~~~~~~~
|
||||
|
||||
- deprecate --input-file
|
||||
- deprecate --video-sync=display-adrop
|
||||
- deprecate all input section commands
|
||||
- deprecate --display-fps, replaced by --override-display-fps
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- remove deprecated --playlist-pos alias
|
||||
- options: remove --heartbeat-cmd and --heartbeat-interval
|
||||
(incidentally fixes #4888)
|
||||
|
||||
|
||||
Fixes and Minor Enhancements
|
||||
----------------------------
|
||||
|
||||
- vo_drm, vo_x11: allow use of zimg
|
||||
- vo_sdl: Support mouse wheel input, Improve mouse button input
|
||||
- video: fix track switching work for external images
|
||||
- vo_gpu: fix infinite scaler reinit spam
|
||||
- f_output_chain: fix possible crash when changing filters
|
||||
- vo_gpu: opengl: add hack for ancient Mesa/GLX
|
||||
- demux_lavf: export demuxer_id for more formats which have it
|
||||
- lua: make later key bindings always have higher priority
|
||||
- drm_common: fix display FPS estimation for interlaced modes
|
||||
- vo_drm: fix incorrect pixel format possibly being picked for 30-bit
|
||||
- zsh completion: fix pcre usage to really be optional
|
||||
- options: increase consistency between list options and document them
|
||||
- osc: reduce unnecesary CPU usage by using property notifications
|
||||
- TOOLS/autoload.lua: add ogm, ogg and opus extensions
|
||||
- Use /dev/tty instead of stdin for terminal input (#4190)
|
||||
- audio: add audio softvol processing to AO (replaces previously GPL’d code)
|
||||
- audio: fix channel conversion with NA channels (e.g. with ALSA)
|
||||
- audio: fix missing volume update on init and reinit
|
||||
- csputils: Fix DCI P3 primaries white point
|
||||
- demux: don't allow subtitles to mess up buffered time display
|
||||
- demux: fix .cue files with audio files that contain attached pictures
|
||||
- demux: fix accounting for seekable ranges on track switches (fixes missing
|
||||
audio when cycling through audio tracks with e.g. EDL, --merge-files, ordered
|
||||
chapters and youtube-dl pseudo DASH)
|
||||
- demux: fix crash with cue/ordered chapter files (#5027)
|
||||
- demux: speed up cache seeking with a coarse index
|
||||
- demux_lavf: always give libavformat the filename when probing
|
||||
(helps with mp3 files)
|
||||
- demux_mkv: add V_SNOW tag to mkv_video_tags
|
||||
- dvb: Fix long channel switching: next/prev channel
|
||||
- dvb: fixes for ATSC tuning
|
||||
- lavc_conv: clamp timestamps to positive (#5047)
|
||||
- macOS: fix bundle on macOS High Sierra (10.13) (#4926, #4866)
|
||||
- mp_image: always copy color attributes on hw download (#4804)
|
||||
- mp_image: select an explicit fallback for chroma location (#4804)
|
||||
- msg: bump log level of --log-file to -v -v
|
||||
- msg: reinterpret a bunch of message levels
|
||||
- osc: fix rare stack overflow when changing visibility mode
|
||||
- osdep/io: add android-related bullshit to fix files >2 GiB
|
||||
- player/playloop.c: respect playback start time when using --loop-file
|
||||
- player: allow seeking in cached parts of unseekable streams
|
||||
- player: make track language matching case insensitive (#5272)
|
||||
- player: use start timestamp for ab-looping if --ab-loop-a is absent
|
||||
- player: when loading external file, always add all track types (#5132)
|
||||
- restore-old-bindings.conf: add old macOS/Wayland AXIS bindings
|
||||
- screenshot: create directories from template
|
||||
- scripting: report dlerror() output
|
||||
- sd_ass: accept RFC8081 font media types
|
||||
- sd_ass: accept otc as fallback OpenType collection file extension
|
||||
- stream_libarchive: work around various types of locale braindeath
|
||||
(https://git.io/vbiFJ)
|
||||
- subprocess-win: don't change the mouse cursor when creating processes
|
||||
- video: add a hack to avoid missing subtitles with vf_sub (#5194)
|
||||
- video: fix alpha handling (#4983)
|
||||
- video: fix memory leaks (roughly 1 KB per decoded frame) with hwdec copy modes
|
||||
- video: fix rotation and deinterlace auto filters
|
||||
- video: properly pass through ICC data
|
||||
- vo: add support for externally driven renderloop and make wayland use it
|
||||
(partially fixes display-sync under wayland; disables rendering when window
|
||||
is invisible)
|
||||
- vo_gpu: change --tone-mapping-desaturate algorithm
|
||||
- vo_gpu: enable 3DLUTs in dumb mode
|
||||
- vo_gpu: fix gamma scale
|
||||
- vo_gpu: fix mobius tone mapping compatibility to GLSL 120 (#5069)
|
||||
- vo_gpu: fix video sometimes not being rerendered on equalizer change
|
||||
- vo_gpu: kill off FBOTEX_FUZZY (#1814)
|
||||
- vo_gpu: opengl: fix possible screenshot window crash (#4905)
|
||||
- vo_gpu: opengl: use GLX_MESA_swap_control where available
|
||||
- vo_gpu: reduce the --alpha=blend-tiles checkerboard intensity
|
||||
- vo_gpu: win: remove exclusive-fullscreen detection hack
|
||||
- vo_lavc: remove messy delayed subtitle rendering logic (#4689)
|
||||
- wayland_common: implement output tracking, many cleanups and bugfixes
|
||||
- Windows: skip window snapping if Windows handled it
|
||||
- Windows: add more-POSIXy versions of open() and fstat() (#4711)
|
||||
- ytdl_hook: don't prepend ytdl:// to non-youtube links in playlists (#5003)
|
||||
|
||||
|
||||
This listing is not complete. Check DOCS/client-api-changes.rst for a history
|
||||
of changes to the client API, and DOCS/interface-changes.rst for a history
|
||||
of changes to other user-visible interfaces.
|
||||
|
||||
A complete changelog can be seen by running `git log v0.30.0..v0.31.0`
|
||||
A complete changelog can be seen by running `git log v0.27.0..v0.28.0`
|
||||
in the git repository or by visiting either
|
||||
https://github.com/mpv-player/mpv/compare/v0.30.0...v0.31.0 or
|
||||
https://git.srsfckn.biz/mpv/log/?qt=range&q=v0.30.0..v0.31.0
|
||||
https://github.com/mpv-player/mpv/compare/v0.27.0...v0.28.0 or
|
||||
https://git.srsfckn.biz/mpv/log/?qt=range&q=v0.27.0..v0.28.0
|
||||
|
||||
@@ -10,9 +10,11 @@ export PYTHON=/usr/bin/python3
|
||||
"$PYTHON" bootstrap.py
|
||||
"$PYTHON" waf configure \
|
||||
--check-c-compiler=gcc \
|
||||
--enable-spirv-cross \
|
||||
--disable-egl-angle-lib \
|
||||
--enable-crossc \
|
||||
--enable-d3d-hwaccel \
|
||||
--enable-d3d11 \
|
||||
--enable-egl-angle \
|
||||
--enable-jpeg \
|
||||
--enable-lcms2 \
|
||||
--enable-libarchive \
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
#!/usr/bin/bash
|
||||
set -e
|
||||
|
||||
export PYTHON=/usr/bin/python3
|
||||
|
||||
# Write an empty fonts.conf to speed up fc-cache
|
||||
export FONTCONFIG_FILE=/dummy-fonts.conf
|
||||
cat >"$FONTCONFIG_FILE" <<EOF
|
||||
@@ -14,12 +12,12 @@ EOF
|
||||
# Install build dependencies for mpv
|
||||
pacman -S --noconfirm --needed \
|
||||
$MINGW_PACKAGE_PREFIX-toolchain \
|
||||
$MINGW_PACKAGE_PREFIX-angleproject-git \
|
||||
$MINGW_PACKAGE_PREFIX-cmake \
|
||||
$MINGW_PACKAGE_PREFIX-lcms2 \
|
||||
$MINGW_PACKAGE_PREFIX-libarchive \
|
||||
$MINGW_PACKAGE_PREFIX-libass \
|
||||
$MINGW_PACKAGE_PREFIX-libjpeg-turbo \
|
||||
$MINGW_PACKAGE_PREFIX-libplacebo \
|
||||
$MINGW_PACKAGE_PREFIX-lua51 \
|
||||
$MINGW_PACKAGE_PREFIX-ninja \
|
||||
$MINGW_PACKAGE_PREFIX-rubberband \
|
||||
@@ -54,19 +52,21 @@ pacman -Sc --noconfirm
|
||||
# Compile shaderc
|
||||
(
|
||||
git clone --depth=1 https://github.com/google/shaderc && cd shaderc
|
||||
"$PYTHON" utils/git-sync-deps
|
||||
git clone --depth=1 https://github.com/google/glslang.git third_party/glslang
|
||||
git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Tools.git third_party/spirv-tools
|
||||
git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Headers.git third_party/spirv-headers
|
||||
|
||||
mkdir build && cd build
|
||||
cmake -GNinja -DCMAKE_BUILD_TYPE=Release -DSHADERC_SKIP_TESTS=ON \
|
||||
-DCMAKE_INSTALL_PREFIX=$MINGW_PREFIX ..
|
||||
ninja install
|
||||
cp -f libshaderc/libshaderc_shared.dll $MINGW_PREFIX/bin/
|
||||
)
|
||||
|
||||
# Compile SPIRV-Cross
|
||||
# Compile crossc
|
||||
(
|
||||
git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Cross && cd SPIRV-Cross
|
||||
git clone --depth=1 https://github.com/rossy/crossc && cd crossc
|
||||
git submodule update --init
|
||||
|
||||
mkdir build && cd build
|
||||
cmake -GNinja -DSPIRV_CROSS_SHARED=ON -DCMAKE_INSTALL_PREFIX=$MINGW_PREFIX ..
|
||||
ninja install
|
||||
make -j4 install prefix=$MINGW_PREFIX
|
||||
)
|
||||
+24
-60
@@ -20,24 +20,12 @@ def is_user_lib(objfile, libname):
|
||||
not "libc." in libname and \
|
||||
not "libgcc." in libname and \
|
||||
not os.path.basename(libname) == 'Python' and \
|
||||
not os.path.basename(objfile) in libname and \
|
||||
not "libswift" in libname
|
||||
not os.path.basename(objfile) in libname
|
||||
|
||||
def otool(objfile):
|
||||
command = "otool -L '%s' | grep -e '\t' | awk '{ print $1 }'" % objfile
|
||||
output = subprocess.check_output(command, shell = True, universal_newlines=True)
|
||||
return set(filter(partial(is_user_lib, objfile), output.split()))
|
||||
|
||||
def get_rpaths_dev_tools(binary):
|
||||
command = "otool -l '%s' | grep -A2 LC_RPATH | grep path | grep \"Xcode\|CommandLineTools\"" % binary
|
||||
result = subprocess.check_output(command, shell = True, universal_newlines=True)
|
||||
pathRe = re.compile("^\s*path (.*) \(offset \d*\)$")
|
||||
output = []
|
||||
|
||||
for line in result.splitlines():
|
||||
output.append(pathRe.search(line).group(1).strip())
|
||||
|
||||
return output
|
||||
command = "otool -L %s | grep -e '\t' | awk '{ print $1 }'" % objfile
|
||||
output = subprocess.check_output(command, shell = True)
|
||||
return filter(partial(is_user_lib, objfile), output.split())
|
||||
|
||||
def install_name_tool_change(old, new, objfile):
|
||||
subprocess.call(["install_name_tool", "-change", old, new, objfile])
|
||||
@@ -45,15 +33,9 @@ def install_name_tool_change(old, new, objfile):
|
||||
def install_name_tool_id(name, objfile):
|
||||
subprocess.call(["install_name_tool", "-id", name, objfile])
|
||||
|
||||
def install_name_tool_add_rpath(rpath, binary):
|
||||
subprocess.call(["install_name_tool", "-add_rpath", rpath, binary])
|
||||
|
||||
def install_name_tool_delete_rpath(rpath, binary):
|
||||
subprocess.call(["install_name_tool", "-delete_rpath", rpath, binary])
|
||||
|
||||
def libraries(objfile, result = dict()):
|
||||
libs_list = otool(objfile)
|
||||
result[objfile] = libs_list
|
||||
result[objfile] = set(libs_list)
|
||||
|
||||
for lib in libs_list:
|
||||
if lib not in result:
|
||||
@@ -61,19 +43,29 @@ def libraries(objfile, result = dict()):
|
||||
|
||||
return result
|
||||
|
||||
def leafs(libs_dict, processed = []):
|
||||
result = []
|
||||
processed = set(processed)
|
||||
|
||||
for objfile, libs in libs_dict.items():
|
||||
if libs <= processed:
|
||||
result.append(objfile)
|
||||
|
||||
return result
|
||||
|
||||
def lib_path(binary):
|
||||
return os.path.join(os.path.dirname(binary), 'lib')
|
||||
|
||||
def lib_name(lib):
|
||||
return os.path.join("@executable_path", "lib", os.path.basename(lib))
|
||||
|
||||
def process_libraries(libs_dict, binary):
|
||||
libs_set = set(libs_dict)
|
||||
# Remove binary from libs_set to prevent a duplicate of the binary being
|
||||
# added to the libs directory.
|
||||
libs_set.remove(binary)
|
||||
def process_libraries(libs_dict, binary, processed = []):
|
||||
ls = leafs(libs_dict, processed)
|
||||
diff = set(ls) - set(processed)
|
||||
if diff == set([binary]):
|
||||
return
|
||||
|
||||
for src in libs_set:
|
||||
for src in diff:
|
||||
name = lib_name(src)
|
||||
dst = os.path.join(lib_path(binary), os.path.basename(src))
|
||||
|
||||
@@ -84,47 +76,19 @@ def process_libraries(libs_dict, binary):
|
||||
if src in libs_dict[binary]:
|
||||
install_name_tool_change(src, name, binary)
|
||||
|
||||
for p in libs_set:
|
||||
for p in processed:
|
||||
if p in libs_dict[src]:
|
||||
install_name_tool_change(p, lib_name(p), dst)
|
||||
|
||||
def process_swift_libraries(binary):
|
||||
command = ['xcrun', '--find', 'swift-stdlib-tool']
|
||||
swiftStdlibTool = subprocess.check_output(command, universal_newlines=True).strip()
|
||||
# from xcode11 on the dynamic swift libs reside in a separate directory from
|
||||
# the std one, might need versioned paths for future swift versions
|
||||
swiftLibPath = os.path.join(swiftStdlibTool, '../../lib/swift-5.0/macosx')
|
||||
swiftLibPath = os.path.abspath(swiftLibPath)
|
||||
|
||||
command = [swiftStdlibTool, '--copy', '--platform', 'macosx', '--scan-executable', binary, '--destination', lib_path(binary)]
|
||||
|
||||
if os.path.exists(swiftLibPath):
|
||||
command.extend(['--source-libraries', swiftLibPath])
|
||||
|
||||
subprocess.check_output(command, universal_newlines=True)
|
||||
|
||||
print(">> setting additional rpath for swift libraries")
|
||||
install_name_tool_add_rpath("@executable_path/lib", binary)
|
||||
|
||||
def remove_dev_tools_rapths(binary):
|
||||
for path in get_rpaths_dev_tools(binary):
|
||||
install_name_tool_delete_rpath(path, binary)
|
||||
process_libraries(libs_dict, binary, ls)
|
||||
|
||||
def main():
|
||||
binary = os.path.abspath(sys.argv[1])
|
||||
if not os.path.exists(lib_path(binary)):
|
||||
os.makedirs(lib_path(binary))
|
||||
print(">> gathering all linked libraries")
|
||||
libs = libraries(binary)
|
||||
|
||||
print(">> copying and processing all linked libraries")
|
||||
print(libs)
|
||||
process_libraries(libs, binary)
|
||||
|
||||
print(">> removing rpath definitions towards dev tools")
|
||||
remove_dev_tools_rapths(binary)
|
||||
|
||||
print(">> copying and processing swift libraries")
|
||||
process_swift_libraries(binary)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+14
-66
@@ -5,31 +5,12 @@
|
||||
-- the internal playlist. (It stops if the it would add an already existing
|
||||
-- playlist entry at the same position - this makes it "stable".)
|
||||
-- Add at most 5000 * 2 files when starting a file (before + after).
|
||||
|
||||
--[[
|
||||
To configure this script use file autoload.conf in director script-opts (the "script-opts"
|
||||
directory must be in the mpv configuration directory, typically ~/.config/mpv/).
|
||||
|
||||
Example configuration would be:
|
||||
|
||||
disabled=false
|
||||
images=false
|
||||
videos=true
|
||||
audio=true
|
||||
|
||||
--]]
|
||||
|
||||
MAXENTRIES = 5000
|
||||
|
||||
local msg = require 'mp.msg'
|
||||
local options = require 'mp.options'
|
||||
local utils = require 'mp.utils'
|
||||
|
||||
o = {
|
||||
disabled = false,
|
||||
images = true,
|
||||
videos = true,
|
||||
audio = true
|
||||
disabled = false
|
||||
}
|
||||
options.read_options(o)
|
||||
|
||||
@@ -39,29 +20,12 @@ function Set (t)
|
||||
return set
|
||||
end
|
||||
|
||||
function SetUnion (a,b)
|
||||
local res = {}
|
||||
for k in pairs(a) do res[k] = true end
|
||||
for k in pairs(b) do res[k] = true end
|
||||
return res
|
||||
end
|
||||
|
||||
EXTENSIONS_VIDEO = Set {
|
||||
'mkv', 'avi', 'mp4', 'ogv', 'webm', 'rmvb', 'flv', 'wmv', 'mpeg', 'mpg', 'm4v', '3gp'
|
||||
EXTENSIONS = Set {
|
||||
'mkv', 'avi', 'mp4', 'ogv', 'webm', 'rmvb', 'flv', 'wmv', 'mpeg', 'mpg', 'm4v', '3gp',
|
||||
'mp3', 'wav', 'ogm', 'flac', 'm4a', 'wma', 'ogg', 'opus',
|
||||
}
|
||||
|
||||
EXTENSIONS_AUDIO = Set {
|
||||
'mp3', 'wav', 'ogm', 'flac', 'm4a', 'wma', 'ogg', 'opus'
|
||||
}
|
||||
|
||||
EXTENSIONS_IMAGES = Set {
|
||||
'jpg', 'jpeg', 'png', 'tif', 'tiff', 'gif', 'webp', 'svg', 'bmp'
|
||||
}
|
||||
|
||||
EXTENSIONS = Set {}
|
||||
if o.videos then EXTENSIONS = SetUnion(EXTENSIONS, EXTENSIONS_VIDEO) end
|
||||
if o.audio then EXTENSIONS = SetUnion(EXTENSIONS, EXTENSIONS_AUDIO) end
|
||||
if o.images then EXTENSIONS = SetUnion(EXTENSIONS, EXTENSIONS_IMAGES) end
|
||||
mputils = require 'mp.utils'
|
||||
|
||||
function add_files_at(index, files)
|
||||
index = index - 1
|
||||
@@ -120,38 +84,22 @@ function alnumcomp(x, y)
|
||||
return #xt < #yt
|
||||
end
|
||||
|
||||
local autoloaded = nil
|
||||
|
||||
function find_and_add_entries()
|
||||
local path = mp.get_property("path", "")
|
||||
local dir, filename = utils.split_path(path)
|
||||
msg.trace(("dir: %s, filename: %s"):format(dir, filename))
|
||||
if o.disabled then
|
||||
msg.verbose("stopping: autoload disabled")
|
||||
return
|
||||
elseif #dir == 0 then
|
||||
msg.verbose("stopping: not a local path")
|
||||
local dir, filename = mputils.split_path(path)
|
||||
if o.disabled or #dir == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local pl_count = mp.get_property_number("playlist-count", 1)
|
||||
-- check if this is a manually made playlist
|
||||
if (pl_count > 1 and autoloaded == nil) or
|
||||
if (pl_count > 1 and autoload == nil) or
|
||||
(pl_count == 1 and EXTENSIONS[string.lower(get_extension(filename))] == nil) then
|
||||
msg.verbose("stopping: manually made playlist")
|
||||
return
|
||||
else
|
||||
autoloaded = true
|
||||
autoload = true
|
||||
end
|
||||
|
||||
local pl = mp.get_property_native("playlist", {})
|
||||
local pl_current = mp.get_property_number("playlist-pos-1", 1)
|
||||
msg.trace(("playlist-pos-1: %s, playlist: %s"):format(pl_current,
|
||||
utils.to_string(pl)))
|
||||
|
||||
local files = utils.readdir(dir, "files")
|
||||
local files = mputils.readdir(dir, "files")
|
||||
if files == nil then
|
||||
msg.verbose("no other files in directory")
|
||||
return
|
||||
end
|
||||
table.filter(files, function (v, k)
|
||||
@@ -170,6 +118,8 @@ function find_and_add_entries()
|
||||
dir = ""
|
||||
end
|
||||
|
||||
local pl = mp.get_property_native("playlist", {})
|
||||
local pl_current = mp.get_property_number("playlist-pos", 0) + 1
|
||||
-- Find the current pl entry (dir+"/"+filename) in the sorted dir list
|
||||
local current
|
||||
for i = 1, #files do
|
||||
@@ -181,7 +131,6 @@ function find_and_add_entries()
|
||||
if current == nil then
|
||||
return
|
||||
end
|
||||
msg.trace("current file position in files: "..current)
|
||||
|
||||
local append = {[-1] = {}, [1] = {}}
|
||||
for direction = -1, 1, 2 do -- 2 iterations, with direction = -1 and +1
|
||||
@@ -195,7 +144,6 @@ function find_and_add_entries()
|
||||
local filepath = dir .. file
|
||||
if pl_e then
|
||||
-- If there's a playlist entry, and it's the same file, stop.
|
||||
msg.trace(pl_e.filename.." == "..filepath.." ?")
|
||||
if pl_e.filename == filepath then
|
||||
break
|
||||
end
|
||||
@@ -203,11 +151,11 @@ function find_and_add_entries()
|
||||
|
||||
if direction == -1 then
|
||||
if pl_current == 1 then -- never add additional entries in the middle
|
||||
msg.info("Prepending " .. file)
|
||||
mp.msg.info("Prepending " .. file)
|
||||
table.insert(append[-1], 1, filepath)
|
||||
end
|
||||
else
|
||||
msg.info("Adding " .. file)
|
||||
mp.msg.info("Adding " .. file)
|
||||
table.insert(append[1], filepath)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
-- Test script for some command API details.
|
||||
|
||||
local utils = require("mp.utils")
|
||||
|
||||
function join(sep, arr, count)
|
||||
local r = ""
|
||||
if count == nil then
|
||||
count = #arr
|
||||
end
|
||||
for i = 1, count do
|
||||
if i > 1 then
|
||||
r = r .. sep
|
||||
end
|
||||
r = r .. utils.to_string(arr[i])
|
||||
end
|
||||
return r
|
||||
end
|
||||
|
||||
mp.observe_property("vo-configured", "bool", function(_, v)
|
||||
if v ~= true then
|
||||
return
|
||||
end
|
||||
|
||||
print("async expand-text")
|
||||
mp.command_native_async({"expand-text", "hello ${path}!"},
|
||||
function(res, val, err)
|
||||
print("done async expand-text: " .. join(" ", {res, val, err}))
|
||||
end)
|
||||
|
||||
-- make screenshot writing very slow
|
||||
mp.set_property("screenshot-format", "png")
|
||||
mp.set_property("screenshot-png-compression", "9")
|
||||
|
||||
timer = mp.add_periodic_timer(0.1, function() print("I'm alive") end)
|
||||
timer:resume()
|
||||
|
||||
print("Slow screenshot command...")
|
||||
res, err = mp.command_native({"screenshot"})
|
||||
print("done, res: " .. utils.to_string(res))
|
||||
|
||||
print("Slow screenshot async command...")
|
||||
res, err = mp.command_native_async({"screenshot"}, function(res)
|
||||
print("done (async), res: " .. utils.to_string(res))
|
||||
timer:kill()
|
||||
end)
|
||||
print("done (sending), res: " .. utils.to_string(res))
|
||||
|
||||
print("Broken screenshot async command...")
|
||||
mp.command_native_async({"screenshot-to-file", "/nonexistent/bogus.png"},
|
||||
function(res, val, err)
|
||||
print("done err scr.: " .. join(" ", {res, val, err}))
|
||||
end)
|
||||
|
||||
mp.command_native_async({name = "subprocess", args = {"sh", "-c", "echo hi && sleep 10s"}, capture_stdout = true},
|
||||
function(res, val, err)
|
||||
print("done subprocess: " .. join(" ", {res, val, err}))
|
||||
end)
|
||||
|
||||
local x = mp.command_native_async({name = "subprocess", args = {"sleep", "inf"}},
|
||||
function(res, val, err)
|
||||
print("done sleep inf subprocess: " .. join(" ", {res, val, err}))
|
||||
end)
|
||||
mp.add_timeout(15, function()
|
||||
print("aborting sleep inf subprocess after timeout")
|
||||
mp.abort_async_command(x)
|
||||
end)
|
||||
|
||||
-- (assuming this "freezes")
|
||||
local y = mp.command_native_async({name = "sub-add", url = "-"},
|
||||
function(res, val, err)
|
||||
print("done sub-add stdin: " .. join(" ", {res, val, err}))
|
||||
end)
|
||||
mp.add_timeout(20, function()
|
||||
print("aborting sub-add stdin after timeout")
|
||||
mp.abort_async_command(y)
|
||||
end)
|
||||
|
||||
|
||||
|
||||
-- This should get killed on script exit.
|
||||
mp.command_native_async({name = "subprocess", playback_only = false,
|
||||
args = {"sleep", "inf"}}, function()end)
|
||||
|
||||
-- Runs detached; should be killed on player exit (forces timeout)
|
||||
mp.command_native({_flags={"async"}, name = "subprocess",
|
||||
playback_only = false, args = {"sleep", "inf"}})
|
||||
end)
|
||||
|
||||
function freeze_test(playback_only)
|
||||
-- This "freezes" the script, should be killed via timeout.
|
||||
counter = counter and counter + 1 or 0
|
||||
print("freeze! " .. counter)
|
||||
local x = mp.command_native({name = "subprocess",
|
||||
playback_only = playback_only,
|
||||
args = {"sleep", "inf"}})
|
||||
print("done, killed=" .. utils.to_string(x.killed_by_us))
|
||||
end
|
||||
|
||||
mp.register_event("shutdown", function()
|
||||
freeze_test(false)
|
||||
end)
|
||||
|
||||
mp.register_event("idle", function()
|
||||
freeze_test(true)
|
||||
end)
|
||||
@@ -1,37 +0,0 @@
|
||||
-- Test a float property which internally uses NaN.
|
||||
-- Run with --no-config (or just scale-param1 not set).
|
||||
|
||||
local utils = require 'mp.utils'
|
||||
|
||||
prop_name = "scale-param1"
|
||||
|
||||
-- internal NaN, return string "default" instead of NaN
|
||||
v = mp.get_property_native(prop_name, "fail")
|
||||
print("Exp:", "string", "\"default\"")
|
||||
print("Got:", type(v), utils.to_string(v))
|
||||
|
||||
v = mp.get_property(prop_name)
|
||||
print("Exp:", "default")
|
||||
print("Got:", v)
|
||||
|
||||
-- not representable -> return provided fallback value
|
||||
v = mp.get_property_number(prop_name, -100)
|
||||
print("Exp:", -100)
|
||||
print("Got:", v)
|
||||
|
||||
mp.set_property_native(prop_name, 123)
|
||||
v = mp.get_property_number(prop_name, -100)
|
||||
print("Exp:", "number", 123)
|
||||
print("Got:", type(v), utils.to_string(v))
|
||||
|
||||
-- try to set an actual NaN
|
||||
st, msg = mp.set_property_number(prop_name, 0.0/0)
|
||||
print("Exp:", nil, "<message>")
|
||||
print("Got:", st, msg)
|
||||
|
||||
-- set default
|
||||
mp.set_property(prop_name, "default")
|
||||
|
||||
v = mp.get_property(prop_name)
|
||||
print("Exp:", "default")
|
||||
print("Got:", v)
|
||||
@@ -1,266 +0,0 @@
|
||||
--[[
|
||||
|
||||
Automatically skip in files if video frames with pre-supplied fingerprints are
|
||||
detected. This will skip ahead by a pre-configured amount of time if a matching
|
||||
video frame is detected.
|
||||
|
||||
This requires the vf_fingerprint video filter to be compiled in. Read the
|
||||
documentation of this filter for caveats (which will automatically apply to
|
||||
this script as well), such as no support for zero-copy hardware decoding.
|
||||
|
||||
You need to manually gather and provide fingerprints for video frames and add
|
||||
them to a configuration file in script-opts/skip-logo.conf (the "script-opts"
|
||||
directory must be in the mpv configuration directory, typically ~/.config/mpv/).
|
||||
|
||||
Example script-opts/skip-logo.conf:
|
||||
|
||||
|
||||
cases = {
|
||||
{
|
||||
-- Skip ahead 10 seconds if a black frame was detected
|
||||
-- Note: this is dangerous non-sense. It's just for demonstration.
|
||||
name = "black frame", -- print if matched
|
||||
skip = 10, -- number of seconds to skip forward
|
||||
score = 0.3, -- required score
|
||||
fingerprint = "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||
},
|
||||
{
|
||||
-- Skip ahead 20 seconds if a white frame was detected
|
||||
-- Note: this is dangerous non-sense. It's just for demonstration.
|
||||
name = "fun2",
|
||||
skip = 20,
|
||||
fingerprint = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
},
|
||||
}
|
||||
|
||||
This is actually a lua file. Lua was chosen because it seemed less of a pain to
|
||||
parse. Future versions of this script may change the format.
|
||||
|
||||
The fingerprint is a video frame, converted to "gray" (8 bit per pixels), full
|
||||
range, each pixel concatenated into an array, converted to a hex string. You
|
||||
can produce these fingerprints by running this manually:
|
||||
|
||||
mpv --vf=fingerprint:print yourfile.mkv
|
||||
|
||||
This will log the fingerprint of each video frame to the console, along with its
|
||||
timestamp. You find the fingerprint of a unique-enough looking frame, and add
|
||||
it as entry to skip-logo.conf.
|
||||
|
||||
You can provide a score for "fuzziness". If no score is provided, a default
|
||||
value of 0.3 is used. The score is inverse: 0 means exactly the same, while a
|
||||
higher score means a higher difference. Currently, the score is computed as
|
||||
euclidean distance between the video frame and the pre-provided fingerprint,
|
||||
thus the highest score is 16. You probably want a score lower than 1 at least.
|
||||
(This algorithm is very primitive, but also simple and fast to compute.)
|
||||
|
||||
There's always the danger of false positives, which might be quite annoying.
|
||||
It's up to you what you hate more, the logo, or random skips if false positives
|
||||
are detected. Also, it's always active, and might eat too much CPU with files
|
||||
that have a high resolution or framerate. To temporarily disable the script,
|
||||
having a keybind like this in your input.conf will be helpful:
|
||||
|
||||
ctrl+k vf toggle @skip-logo
|
||||
|
||||
This will disable/enable the fingerprint filter, which the script automatically
|
||||
adds at start.
|
||||
|
||||
Another important caveat is that the script currently disables matching during
|
||||
seeking or playback initialization, which means it cannot match the first few
|
||||
frames of a video. This could be fixed, but the author was too lazy to do so.
|
||||
|
||||
--]]
|
||||
|
||||
local utils = require "mp.utils"
|
||||
local msg = require "mp.msg"
|
||||
|
||||
local label = "skip-logo"
|
||||
local meta_property = string.format("vf-metadata/%s", label)
|
||||
|
||||
local config = {}
|
||||
local cases = {}
|
||||
local cur_bmp
|
||||
local seeking = false
|
||||
local playback_start_pts = nil
|
||||
|
||||
-- Convert a hex string to an array. Convert each byte to a [0,1] float by
|
||||
-- interpreting it as normalized uint8_t.
|
||||
-- The data parameter, if not nil, may be used as storage (avoiding garbage).
|
||||
local function hex_to_norm8(hex, data)
|
||||
local size = math.floor(#hex / 2)
|
||||
if #hex ~= size * 2 then
|
||||
return nil
|
||||
end
|
||||
local res
|
||||
if (data ~= nil) and (#data == size) then
|
||||
res = data
|
||||
else
|
||||
res = {}
|
||||
end
|
||||
for i = 1, size do
|
||||
local num = tonumber(hex:sub(i * 2, i * 2 + 1), 16)
|
||||
if num == nil then
|
||||
return nil
|
||||
end
|
||||
res[i] = num / 255.0
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
local function compare_bmp(a, b)
|
||||
if #a ~= #b then
|
||||
return nil -- can't compare
|
||||
end
|
||||
local sum = 0
|
||||
for i = 1, #a do
|
||||
local diff = a[i] - b[i]
|
||||
sum = sum + diff * diff
|
||||
end
|
||||
return math.sqrt(sum)
|
||||
end
|
||||
|
||||
local function load_config()
|
||||
local conf_file = mp.find_config_file("script-opts/skip-logo.conf")
|
||||
local conf_fn
|
||||
local err = nil
|
||||
if conf_file then
|
||||
if setfenv then
|
||||
conf_fn, err = loadfile(conf_file)
|
||||
if conf_fn then
|
||||
setfenv(conf_fn, config)
|
||||
end
|
||||
else
|
||||
msg.warn("Lua 5.2 was not tested, this might go wrong.")
|
||||
conf_fn, err = loadfile(conf_file, "t", config)
|
||||
end
|
||||
else
|
||||
err = "config file not found"
|
||||
end
|
||||
|
||||
if conf_fn and (not err) then
|
||||
local ok, err2 = pcall(conf_fn)
|
||||
err = err2
|
||||
end
|
||||
|
||||
if err then
|
||||
msg.error("Failed to load config file:", err)
|
||||
end
|
||||
|
||||
if config.cases then
|
||||
for n, case in ipairs(config.cases) do
|
||||
local err = nil
|
||||
case.bitmap = hex_to_norm8(case.fingerprint)
|
||||
if case.bitmap == nil then
|
||||
err = "invalid or missing fingerprint field"
|
||||
end
|
||||
if case.score == nil then
|
||||
case.score = 0.3
|
||||
end
|
||||
if type(case.score) ~= "number" then
|
||||
err = "score field is not a number"
|
||||
end
|
||||
if type(case.skip) ~= "number" then
|
||||
err = "skip field is not a number or missing"
|
||||
end
|
||||
if case.name == nil then
|
||||
case.name = ("Entry %d"):format(n)
|
||||
end
|
||||
if err == nil then
|
||||
cases[#cases + 1] = case
|
||||
else
|
||||
msg.error(("Entry %s: %s, ignoring."):format(case.name, err))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
load_config()
|
||||
|
||||
-- Returns true on match and if something was done.
|
||||
local function check_fingerprint(hex, pts)
|
||||
local bmp = hex_to_norm8(hex, cur_bmp)
|
||||
cur_bmp = bmp
|
||||
|
||||
-- If parsing the filter's result failed (well, it shouldn't).
|
||||
assert(bmp ~= nil, "filter returned nonsense")
|
||||
|
||||
for _, case in ipairs(cases) do
|
||||
local score = compare_bmp(case.bitmap, bmp)
|
||||
if (score ~= nil) and (score <= case.score) then
|
||||
msg.warn(("Matching %s: score=%f (required: %f) at %s, skipping %f seconds"):
|
||||
format(case.name, score, case.score, mp.format_time(pts), case.skip))
|
||||
mp.commandv("seek", pts + case.skip, "absolute+exact")
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
local function read_frames()
|
||||
local result = mp.get_property_native(meta_property)
|
||||
if result == nil then
|
||||
return
|
||||
end
|
||||
|
||||
-- Try to get all entries. Out of laziness, assume that there are at most
|
||||
-- 100 entries. (In fact, vf_fingerprint limits it to 10.)
|
||||
for i = 0, 99 do
|
||||
local prefix = string.format("fp%d.", i)
|
||||
local hex = result[prefix .. "hex"]
|
||||
|
||||
local pts = tonumber(result[prefix .. "pts"])
|
||||
if (hex == nil) or (pts == nil) then
|
||||
break
|
||||
end
|
||||
|
||||
local skip = false -- blame Lua for not having "continue" or "goto", not me
|
||||
|
||||
-- If seeking just stopped, there will be frames before the seek target,
|
||||
-- ignore them by checking the timestamps.
|
||||
if playback_start_pts ~= nil then
|
||||
if pts >= playback_start_pts then
|
||||
playback_start_pts = nil -- just for robustness
|
||||
else
|
||||
skip = true
|
||||
end
|
||||
end
|
||||
|
||||
if not skip then
|
||||
if check_fingerprint(hex, pts) then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
mp.observe_property(meta_property, "none", function()
|
||||
-- Ignore frames that are decoded/filtered during seeking.
|
||||
if seeking then
|
||||
return
|
||||
end
|
||||
|
||||
read_frames()
|
||||
end)
|
||||
|
||||
mp.observe_property("seeking", "bool", function(name, val)
|
||||
seeking = val
|
||||
if seeking == false then
|
||||
playback_start_pts = mp.get_property_number("playback-time")
|
||||
read_frames()
|
||||
end
|
||||
end)
|
||||
|
||||
local filters = mp.get_property_native("option-info/vf/choices", {})
|
||||
local found = false
|
||||
for _, f in ipairs(filters) do
|
||||
if f == "fingerprint" then
|
||||
found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if found then
|
||||
mp.command(("no-osd vf add @%s:fingerprint"):format(label, filter))
|
||||
else
|
||||
msg.warn("vf_fingerprint not found")
|
||||
end
|
||||
+1
-3
@@ -213,7 +213,6 @@ elements_matroska = (
|
||||
'Tag*, 7373, sub', (
|
||||
'Targets, 63c0, sub', (
|
||||
'TargetTypeValue, 68ca, uint',
|
||||
'TargetType, 63ca, str',
|
||||
'TargetTrackUID, 63c5, uint',
|
||||
'TargetEditionUID, 63c9, uint',
|
||||
'TargetChapterUID, 63c4, uint',
|
||||
@@ -222,8 +221,7 @@ elements_matroska = (
|
||||
'SimpleTag*, 67c8, sub', (
|
||||
'TagName, 45a3, str',
|
||||
'TagLanguage, 447a, str',
|
||||
'TagString, 4487, str',
|
||||
'TagDefault, 4484, uint',
|
||||
'TagString, 4487, str'
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -22,7 +22,7 @@ OS2Version: 3
|
||||
OS2_WeightWidthSlopeOnly: 0
|
||||
OS2_UseTypoMetrics: 0
|
||||
CreationTime: 1408646554
|
||||
ModificationTime: 1576096543
|
||||
ModificationTime: 1492879730
|
||||
PfmFamily: 81
|
||||
TTFWeight: 400
|
||||
TTFWidth: 5
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
StartChar: uniE112
|
||||
Encoding: 57618 57618 30
|
||||
Width: 768
|
||||
VWidth: 1176
|
||||
Flags: HW
|
||||
LayerCount: 2
|
||||
Fore
|
||||
SplineSet
|
||||
512 40 m 1
|
||||
0 40 l 1
|
||||
0 168 l 1
|
||||
512 168 l 1
|
||||
512 40 l 1
|
||||
EndSplineSet
|
||||
EndChar
|
||||
@@ -1,20 +0,0 @@
|
||||
StartChar: uniE113
|
||||
Encoding: 57619 57619 31
|
||||
Width: 622
|
||||
VWidth: 1178
|
||||
Flags: HW
|
||||
LayerCount: 2
|
||||
Fore
|
||||
SplineSet
|
||||
768 42 m 5
|
||||
0 42 l 5
|
||||
0 746 l 5
|
||||
768 746 l 5
|
||||
768 42 l 5
|
||||
704 106 m 5
|
||||
704 618 l 5
|
||||
64 618 l 5
|
||||
64 106 l 5
|
||||
704 106 l 5
|
||||
EndSplineSet
|
||||
EndChar
|
||||
@@ -1,31 +0,0 @@
|
||||
StartChar: uniE114
|
||||
Encoding: 57620 57620 32
|
||||
Width: 896
|
||||
VWidth: 1178
|
||||
Flags: HW
|
||||
LayerCount: 2
|
||||
Fore
|
||||
SplineSet
|
||||
768 298 m 1
|
||||
576 298 l 1
|
||||
576 42 l 1
|
||||
0 42 l 1
|
||||
0 490 l 1
|
||||
192 490 l 1
|
||||
192 746 l 1
|
||||
768 746 l 1
|
||||
768 298 l 1
|
||||
704 362 m 1
|
||||
704 618 l 1
|
||||
256 618 l 1
|
||||
256 490 l 1
|
||||
576 490 l 1
|
||||
576 362 l 1
|
||||
704 362 l 1
|
||||
512 106 m 1
|
||||
512 362 l 1
|
||||
64 362 l 1
|
||||
64 106 l 1
|
||||
512 106 l 1
|
||||
EndSplineSet
|
||||
EndChar
|
||||
@@ -1,27 +0,0 @@
|
||||
StartChar: uniE115
|
||||
Encoding: 57621 57621 33
|
||||
Width: 844
|
||||
VWidth: 1112
|
||||
Flags: HW
|
||||
LayerCount: 2
|
||||
Fore
|
||||
SplineSet
|
||||
671 40 m 1
|
||||
575 40 l 1
|
||||
335 277 l 1
|
||||
98 40 l 1
|
||||
-1 40 l 1
|
||||
-1 136 l 1
|
||||
236 376 l 1
|
||||
-1 613 l 1
|
||||
-1 712 l 1
|
||||
98 712 l 1
|
||||
335 475 l 1
|
||||
575 712 l 1
|
||||
671 712 l 1
|
||||
671 613 l 1
|
||||
434 376 l 1
|
||||
671 139 l 1
|
||||
671 40 l 1
|
||||
EndSplineSet
|
||||
EndChar
|
||||
+2
-2
@@ -7,7 +7,7 @@ import fileinput
|
||||
from optparse import OptionParser
|
||||
|
||||
def sh(command):
|
||||
return os.popen(command).read().strip()
|
||||
return os.popen(command).read()
|
||||
|
||||
def bundle_path(binary_name):
|
||||
return "%s.app" % binary_name
|
||||
@@ -80,7 +80,7 @@ def main():
|
||||
|
||||
if options.deps:
|
||||
print("> bundling dependencies")
|
||||
print(sh(" ".join(["TOOLS/dylib-unhell.py", target_binary(binary_name)])))
|
||||
sh(" ".join(["TOOLS/dylib-unhell.py", target_binary(binary_name)]))
|
||||
|
||||
print("done.")
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ "x$TARGET" != "xx86_64-w64-mingw32" ]; then
|
||||
if [ "x$LIBAV" != "xffmpeg-git" ] || [ "x$TRAVIS_OS_NAME" != "xlinux" ]; then
|
||||
# trigger build only on one of the matrix nodes
|
||||
exit;
|
||||
fi
|
||||
|
||||
Executable
+283
@@ -0,0 +1,283 @@
|
||||
#!/usr/bin/perl
|
||||
|
||||
# Generate ZSH completion
|
||||
|
||||
#
|
||||
# This file is part of mpv.
|
||||
#
|
||||
# mpv is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2.1 of the License, or (at your option) any later version.
|
||||
#
|
||||
# mpv is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use warnings FATAL => 'uninitialized';
|
||||
|
||||
my $mpv = $ARGV[0] || 'mpv';
|
||||
|
||||
my @opts = parse_main_opts('--list-options', '^ (\-\-[^\s\*]*)\*?\s*(.*)');
|
||||
|
||||
die "Couldn't find any options" unless (@opts);
|
||||
|
||||
my @ao = parse_opts('--ao=help', '^ ([^\s\:]*)\s*: (.*)');
|
||||
my @vo = parse_opts('--vo=help', '^ ([^\s\:]*)\s*: (.*)');
|
||||
|
||||
my @af = parse_opts('--af=help', '^ ([^\s\:]*)\s*: (.*)');
|
||||
my @vf = parse_opts('--vf=help', '^ ([^\s\:]*)\s*: (.*)');
|
||||
|
||||
my @protos = parse_opts('--list-protocols', '^ ([^\s]*)');
|
||||
|
||||
my ($opts_str, $ao_str, $vo_str, $af_str, $vf_str, $protos_str);
|
||||
|
||||
$opts_str .= qq{ '$_' \\\n} foreach (@opts);
|
||||
chomp $opts_str;
|
||||
|
||||
$ao_str .= qq{ '$_' \\\n} foreach (@ao);
|
||||
chomp $ao_str;
|
||||
|
||||
$vo_str .= qq{ '$_' \\\n} foreach (@vo);
|
||||
chomp $vo_str;
|
||||
|
||||
$af_str .= qq{ '$_' \\\n} foreach (@af);
|
||||
chomp $af_str;
|
||||
|
||||
$vf_str .= qq{ '$_' \\\n} foreach (@vf);
|
||||
chomp $vf_str;
|
||||
|
||||
$protos_str = join(' ', @protos);
|
||||
|
||||
my $runtime_completions = <<'EOS';
|
||||
profile|show-profile)
|
||||
local -a profiles
|
||||
local current
|
||||
for current in "${(@f)$($words[1] --profile=help)}"; do
|
||||
current=${current//\*/\\\*}
|
||||
current=${current//\:/\\\:}
|
||||
current=${current//\[/\\\[}
|
||||
current=${current//\]/\\\]}
|
||||
if [[ $current =~ $'\t'([^$'\t']*)$'\t'(.*) ]]; then
|
||||
if [[ -n $match[2] ]]; then
|
||||
current="$match[1][$match[2]]"
|
||||
else
|
||||
current="$match[1]"
|
||||
fi
|
||||
profiles=($profiles $current)
|
||||
fi
|
||||
done
|
||||
if [[ $state == show-profile ]]; then
|
||||
# For --show-profile, only one allowed
|
||||
if (( ${#profiles} > 0 )); then
|
||||
_values 'profile' $profiles && rc=0
|
||||
fi
|
||||
else
|
||||
# For --profile, multiple allowed
|
||||
profiles=($profiles 'help[list profiles]')
|
||||
_values -s , 'profile(s)' $profiles && rc=0
|
||||
fi
|
||||
;;
|
||||
|
||||
audio-device)
|
||||
local -a audio_devices
|
||||
local current
|
||||
for current in "${(@f)$($words[1] --audio-device=help)}"; do
|
||||
current=${current//\*/\\\*}
|
||||
current=${current//\:/\\\:}
|
||||
current=${current//\[/\\\[}
|
||||
current=${current//\]/\\\]}
|
||||
if [[ $current =~ ' '\'([^\']*)\'' \('(.*)'\)' ]]; then
|
||||
audio_devices=($audio_devices "$match[1][$match[2]]")
|
||||
fi
|
||||
done
|
||||
audio_devices=($audio_devices 'help[list audio devices]')
|
||||
_values 'audio device' $audio_devices && rc=0
|
||||
;;
|
||||
EOS
|
||||
chomp $runtime_completions;
|
||||
|
||||
my $tmpl = <<"EOS";
|
||||
#compdef mpv
|
||||
|
||||
# For customization, see:
|
||||
# https://github.com/mpv-player/mpv/wiki/Zsh-completion-customization
|
||||
|
||||
local curcontext="\$curcontext" state state_descr line
|
||||
typeset -A opt_args
|
||||
|
||||
local -a match mbegin mend
|
||||
local MATCH MBEGIN MEND
|
||||
|
||||
# By default, don't complete URLs unless no files match
|
||||
local -a tag_order
|
||||
zstyle -a ":completion:*:*:\$service:*" tag-order tag_order || \
|
||||
zstyle ":completion:*:*:\$service:*" tag-order '!urls'
|
||||
|
||||
local rc=1
|
||||
|
||||
_arguments -C -S \\
|
||||
$opts_str
|
||||
'*:files:->mfiles' && rc=0
|
||||
|
||||
case \$state in
|
||||
ao)
|
||||
_values -s , 'audio outputs' \\
|
||||
$ao_str
|
||||
&& rc=0
|
||||
;;
|
||||
|
||||
vo)
|
||||
_values -s , 'video outputs' \\
|
||||
$vo_str
|
||||
&& rc=0
|
||||
;;
|
||||
|
||||
af)
|
||||
_values -s , 'audio filters' \\
|
||||
$af_str
|
||||
&& rc=0
|
||||
;;
|
||||
|
||||
vf)
|
||||
_values -s , 'video filters' \\
|
||||
$vf_str
|
||||
&& rc=0
|
||||
;;
|
||||
|
||||
$runtime_completions
|
||||
|
||||
files)
|
||||
compset -P '*,'
|
||||
compset -S ',*'
|
||||
_files -r ',/ \\t\\n\\-' && rc=0
|
||||
;;
|
||||
|
||||
mfiles)
|
||||
local expl
|
||||
_tags files urls
|
||||
while _tags; do
|
||||
_requested files expl 'media file' _files && rc=0
|
||||
if _requested urls; then
|
||||
while _next_label urls expl URL; do
|
||||
_urls "\$expl[@]" && rc=0
|
||||
compadd -S '' "\$expl[@]" $protos_str && rc=0
|
||||
done
|
||||
fi
|
||||
(( rc )) || return 0
|
||||
done
|
||||
;;
|
||||
esac
|
||||
|
||||
return rc
|
||||
EOS
|
||||
|
||||
print $tmpl;
|
||||
|
||||
sub parse_main_opts {
|
||||
my ($cmd, $regex) = @_;
|
||||
|
||||
my @list;
|
||||
my @lines = call_mpv($cmd);
|
||||
|
||||
foreach my $line (@lines) {
|
||||
my ($name, $desc) = ($line =~ /^$regex/) or next;
|
||||
|
||||
next if ($desc eq 'removed' || $desc eq 'alias');
|
||||
|
||||
if ($desc =~ /^Flag/) {
|
||||
|
||||
push @list, $name;
|
||||
|
||||
$name =~ /^--(.*)/;
|
||||
if ($1 !~ /^(\{|\}|v|list-options|really-quiet|no-.*)$/) {
|
||||
push @list, "--no-$1";
|
||||
}
|
||||
|
||||
} elsif ($desc =~ /^Print/) {
|
||||
|
||||
push @list, $name;
|
||||
|
||||
} else {
|
||||
|
||||
# Option takes argument
|
||||
|
||||
my $entry = $name;
|
||||
|
||||
$desc =~ s/\:/\\:/g;
|
||||
$entry .= "=-:$desc:";
|
||||
|
||||
if ($desc =~ /^Choices\\: ([^(]*)/) {
|
||||
my $choices = $1;
|
||||
$choices =~ s/ +$//; # strip trailing space
|
||||
$entry .= "($choices)";
|
||||
|
||||
# If "no" is one of the choices, it can also be
|
||||
# negated like a flag (--no-whatever).
|
||||
if ($choices =~ /\bno\b/) {
|
||||
$name =~ s/^--/--no-/;
|
||||
push @list, $name;
|
||||
}
|
||||
} elsif ($line =~ /\[file\]/) {
|
||||
$entry .= '->files';
|
||||
} elsif ($name =~ /^--(ao|vo|af|vf|profile|show-profile|audio-device)$/) {
|
||||
$entry .= "->$1";
|
||||
}
|
||||
push @list, $entry;
|
||||
}
|
||||
}
|
||||
|
||||
# Sort longest first, because zsh won't complete an option listed
|
||||
# after one that's a prefix of it.
|
||||
@list = sort {
|
||||
$a =~ /([^=]*)/; my $ma = $1;
|
||||
$b =~ /([^=]*)/; my $mb = $1;
|
||||
|
||||
length($mb) <=> length($ma)
|
||||
} @list;
|
||||
|
||||
return @list;
|
||||
}
|
||||
|
||||
sub parse_opts {
|
||||
my ($cmd, $regex) = @_;
|
||||
|
||||
my @list;
|
||||
my @lines = call_mpv($cmd);
|
||||
|
||||
foreach my $line (@lines) {
|
||||
if ($line !~ /^$regex/) {
|
||||
next;
|
||||
}
|
||||
|
||||
my $entry = $1;
|
||||
|
||||
if (defined $2) {
|
||||
my $desc = $2;
|
||||
$desc =~ s/\:/\\:/g;
|
||||
$entry .= "[$desc]";
|
||||
}
|
||||
|
||||
push @list, $entry
|
||||
}
|
||||
|
||||
return @list;
|
||||
}
|
||||
|
||||
sub call_mpv {
|
||||
my ($cmd) = @_;
|
||||
my $output = `"$mpv" --no-config $cmd`;
|
||||
if ($? == -1) {
|
||||
die "Could not run mpv: $!";
|
||||
} elsif ((my $exit_code = $? >> 8) != 0) {
|
||||
die "mpv returned $exit_code with output:\n$output";
|
||||
}
|
||||
return split /\n/, $output;
|
||||
}
|
||||
+3
-10
@@ -2,7 +2,6 @@ branches:
|
||||
only:
|
||||
- master
|
||||
- appveyor
|
||||
- ci
|
||||
|
||||
environment:
|
||||
global:
|
||||
@@ -15,18 +14,12 @@ shallow_clone: true
|
||||
test: off
|
||||
|
||||
install:
|
||||
# Support for Ada and Objective-C was removed from MSYS2. GCC won't update if
|
||||
# these packages are installed.
|
||||
- >-
|
||||
C:\msys64\usr\bin\pacman -R --noconfirm --noprogressbar
|
||||
mingw-w64-i686-gcc-ada mingw-w64-i686-gcc-objc mingw-w64-x86_64-gcc-ada
|
||||
mingw-w64-x86_64-gcc-objc
|
||||
# Update core packages
|
||||
- C:\msys64\usr\bin\pacman -Syyuu --noconfirm --noprogressbar --ask=20
|
||||
- C:\msys64\usr\bin\pacman -Syyuu --noconfirm
|
||||
# Update non-core packages
|
||||
- C:\msys64\usr\bin\pacman -Suu --noconfirm --noprogressbar --ask=20
|
||||
- C:\msys64\usr\bin\pacman -Suu --noconfirm
|
||||
# Install required MSYS2 packages
|
||||
- C:\msys64\usr\bin\pacman -S --noconfirm --noprogressbar --needed base-devel perl python pkg-config
|
||||
- C:\msys64\usr\bin\pacman -S --noconfirm --needed base-devel perl python pkg-config
|
||||
# Now MSYS2 is up to date, do the rest of the install from a bash script
|
||||
- C:\msys64\usr\bin\bash -lc "cd && exec \"$APPVEYOR_BUILD_FOLDER\"/TOOLS/appveyor-install.sh"
|
||||
build_script:
|
||||
|
||||
@@ -23,17 +23,15 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "audio/aframe.h"
|
||||
#include "audio/fmt-conversion.h"
|
||||
#include "audio/format.h"
|
||||
#include "common/common.h"
|
||||
#include "common/av_common.h"
|
||||
#include "common/msg.h"
|
||||
#include "options/m_config.h"
|
||||
#include "options/m_option.h"
|
||||
|
||||
#include "f_swresample.h"
|
||||
#include "filter_internal.h"
|
||||
#include "aconverter.h"
|
||||
#include "aframe.h"
|
||||
#include "fmt-conversion.h"
|
||||
#include "format.h"
|
||||
|
||||
#define HAVE_LIBSWRESAMPLE (!HAVE_LIBAV)
|
||||
#define HAVE_LIBAVRESAMPLE HAVE_LIBAV
|
||||
@@ -56,15 +54,18 @@
|
||||
#error "config.h broken or no resampler found"
|
||||
#endif
|
||||
|
||||
struct priv {
|
||||
struct mp_aconverter {
|
||||
struct mp_log *log;
|
||||
struct mpv_global *global;
|
||||
double playback_speed;
|
||||
bool is_resampling;
|
||||
bool passthrough_mode;
|
||||
struct AVAudioResampleContext *avrctx;
|
||||
struct mp_aframe *avrctx_fmt; // output format of avrctx
|
||||
struct mp_aframe *pool_fmt; // format used to allocate frames for avrctx output
|
||||
struct mp_aframe *pre_out_fmt; // format before final conversion
|
||||
struct AVAudioResampleContext *avrctx_out; // for output channel reordering
|
||||
struct mp_resample_opts *opts; // opts requested by the user
|
||||
const struct mp_resample_opts *opts; // opts requested by the user
|
||||
// At least libswresample keeps a pointer around for this:
|
||||
int reorder_in[MP_NUM_CHANNELS];
|
||||
int reorder_out[MP_NUM_CHANNELS];
|
||||
@@ -79,56 +80,35 @@ struct priv {
|
||||
int out_format;
|
||||
struct mp_chmap out_channels;
|
||||
|
||||
double current_pts;
|
||||
struct mp_aframe *input;
|
||||
|
||||
double cmd_speed;
|
||||
double speed;
|
||||
|
||||
struct mp_swresample public;
|
||||
};
|
||||
|
||||
#define OPT_BASE_STRUCT struct mp_resample_opts
|
||||
const struct m_sub_options resample_conf = {
|
||||
.opts = (const m_option_t[]) {
|
||||
OPT_INTRANGE("audio-resample-filter-size", filter_size, 0, 0, 32),
|
||||
OPT_INTRANGE("audio-resample-phase-shift", phase_shift, 0, 0, 30),
|
||||
OPT_FLAG("audio-resample-linear", linear, 0),
|
||||
OPT_DOUBLE("audio-resample-cutoff", cutoff, M_OPT_RANGE,
|
||||
.min = 0, .max = 1),
|
||||
OPT_FLAG("audio-normalize-downmix", normalize, 0),
|
||||
OPT_DOUBLE("audio-resample-max-output-size", max_output_frame_size, 0),
|
||||
OPT_KEYVALUELIST("audio-swresample-o", avopts, 0),
|
||||
{0}
|
||||
},
|
||||
.size = sizeof(struct mp_resample_opts),
|
||||
.defaults = &(const struct mp_resample_opts)MP_RESAMPLE_OPTS_DEF,
|
||||
.change_flags = UPDATE_AUDIO,
|
||||
struct mp_aframe *input; // queued input frame
|
||||
bool input_eof; // queued input EOF
|
||||
struct mp_aframe *output; // queued output frame
|
||||
bool output_eof; // queued output EOF
|
||||
};
|
||||
|
||||
#if HAVE_LIBAVRESAMPLE
|
||||
static double get_delay(struct priv *p)
|
||||
static double get_delay(struct mp_aconverter *p)
|
||||
{
|
||||
return avresample_get_delay(p->avrctx) / (double)p->in_rate +
|
||||
avresample_available(p->avrctx) / (double)p->out_rate;
|
||||
}
|
||||
static int get_out_samples(struct priv *p, int in_samples)
|
||||
static int get_out_samples(struct mp_aconverter *p, int in_samples)
|
||||
{
|
||||
return avresample_get_out_samples(p->avrctx, in_samples);
|
||||
}
|
||||
#else
|
||||
static double get_delay(struct priv *p)
|
||||
static double get_delay(struct mp_aconverter *p)
|
||||
{
|
||||
int64_t base = p->in_rate * (int64_t)p->out_rate;
|
||||
return swr_get_delay(p->avrctx, base) / (double)base;
|
||||
}
|
||||
static int get_out_samples(struct priv *p, int in_samples)
|
||||
static int get_out_samples(struct mp_aconverter *p, int in_samples)
|
||||
{
|
||||
return swr_get_out_samples(p->avrctx, in_samples);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void close_lavrr(struct priv *p)
|
||||
static void close_lavrr(struct mp_aconverter *p)
|
||||
{
|
||||
if (p->avrctx)
|
||||
avresample_close(p->avrctx);
|
||||
@@ -159,7 +139,7 @@ static struct mp_chmap fudge_pairs[][2] = {
|
||||
// strictly speaking incompatible channel pairs. For example, 7.1 should be
|
||||
// changed to 7.1(wide) without dropping the SL/SR channels. (We still leave
|
||||
// it to libswresample to create the remix matrix.)
|
||||
static uint64_t fudge_layout_conversion(struct priv *p,
|
||||
static uint64_t fudge_layout_conversion(struct mp_aconverter *p,
|
||||
uint64_t in, uint64_t out)
|
||||
{
|
||||
for (int n = 0; n < MP_ARRAY_SIZE(fudge_pairs); n++) {
|
||||
@@ -194,17 +174,19 @@ static void transpose_order(int *map, int num)
|
||||
memcpy(map, nmap, sizeof(nmap));
|
||||
}
|
||||
|
||||
static bool configure_lavrr(struct priv *p, bool verbose)
|
||||
static bool configure_lavrr(struct mp_aconverter *p, bool verbose)
|
||||
{
|
||||
close_lavrr(p);
|
||||
|
||||
p->in_rate = rate_from_speed(p->in_rate_user, p->speed);
|
||||
p->in_rate = rate_from_speed(p->in_rate_user, p->playback_speed);
|
||||
|
||||
MP_VERBOSE(p, "%dHz %s %s -> %dHz %s %s\n",
|
||||
p->in_rate, mp_chmap_to_str(&p->in_channels),
|
||||
af_fmt_to_str(p->in_format),
|
||||
p->out_rate, mp_chmap_to_str(&p->out_channels),
|
||||
af_fmt_to_str(p->out_format));
|
||||
p->passthrough_mode = p->opts->allow_passthrough &&
|
||||
p->in_rate == p->out_rate &&
|
||||
p->in_format == p->out_format &&
|
||||
mp_chmap_equals(&p->in_channels, &p->out_channels);
|
||||
|
||||
if (p->passthrough_mode)
|
||||
return true;
|
||||
|
||||
p->avrctx = avresample_alloc_context();
|
||||
p->avrctx_out = avresample_alloc_context();
|
||||
@@ -218,11 +200,7 @@ static bool configure_lavrr(struct priv *p, bool verbose)
|
||||
if (in_samplefmt == AV_SAMPLE_FMT_NONE ||
|
||||
out_samplefmt == AV_SAMPLE_FMT_NONE ||
|
||||
out_samplefmtp == AV_SAMPLE_FMT_NONE)
|
||||
{
|
||||
MP_ERR(p, "unsupported conversion: %s -> %s\n",
|
||||
af_fmt_to_str(p->in_format), af_fmt_to_str(p->out_format));
|
||||
goto error;
|
||||
}
|
||||
|
||||
av_opt_set_int(p->avrctx, "filter_size", p->opts->filter_size, 0);
|
||||
av_opt_set_int(p->avrctx, "phase_shift", p->opts->phase_shift, 0);
|
||||
@@ -233,7 +211,12 @@ static bool configure_lavrr(struct priv *p, bool verbose)
|
||||
cutoff = MPMAX(1.0 - 6.5 / (p->opts->filter_size + 8), 0.80);
|
||||
av_opt_set_double(p->avrctx, "cutoff", cutoff, 0);
|
||||
|
||||
int global_normalize;
|
||||
mp_read_option_raw(p->global, "audio-normalize-downmix", &m_option_type_flag,
|
||||
&global_normalize);
|
||||
int normalize = p->opts->normalize;
|
||||
if (normalize < 0)
|
||||
normalize = global_normalize;
|
||||
#if HAVE_LIBSWRESAMPLE
|
||||
av_opt_set_double(p->avrctx, "rematrix_maxval", normalize ? 1 : 1000, 0);
|
||||
#else
|
||||
@@ -349,18 +332,33 @@ static bool configure_lavrr(struct priv *p, bool verbose)
|
||||
|
||||
error:
|
||||
close_lavrr(p);
|
||||
mp_filter_internal_mark_failed(p->public.f);
|
||||
MP_FATAL(p, "libswresample failed to initialize.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
static void reset(struct mp_filter *f)
|
||||
bool mp_aconverter_reconfig(struct mp_aconverter *p,
|
||||
int in_rate, int in_format, struct mp_chmap in_channels,
|
||||
int out_rate, int out_format, struct mp_chmap out_channels)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
close_lavrr(p);
|
||||
|
||||
p->current_pts = MP_NOPTS_VALUE;
|
||||
TA_FREEP(&p->input);
|
||||
TA_FREEP(&p->output);
|
||||
p->input_eof = p->output_eof = false;
|
||||
|
||||
p->playback_speed = 1.0;
|
||||
|
||||
p->in_rate_user = in_rate;
|
||||
p->in_format = in_format;
|
||||
p->in_channels = in_channels;
|
||||
p->out_rate = out_rate;
|
||||
p->out_format = out_format;
|
||||
p->out_channels = out_channels;
|
||||
|
||||
return configure_lavrr(p, true);
|
||||
}
|
||||
|
||||
void mp_aconverter_flush(struct mp_aconverter *p)
|
||||
{
|
||||
if (!p->avrctx)
|
||||
return;
|
||||
#if HAVE_LIBSWRESAMPLE
|
||||
@@ -372,6 +370,11 @@ static void reset(struct mp_filter *f)
|
||||
#endif
|
||||
}
|
||||
|
||||
void mp_aconverter_set_speed(struct mp_aconverter *p, double speed)
|
||||
{
|
||||
p->playback_speed = speed;
|
||||
}
|
||||
|
||||
static void extra_output_conversion(struct mp_aframe *mpa)
|
||||
{
|
||||
int format = af_fmt_from_planar(mp_aframe_get_format(mpa));
|
||||
@@ -399,7 +402,7 @@ static bool reorder_planes(struct mp_aframe *mpa, int *reorder,
|
||||
if (!mp_aframe_set_chmap(mpa, newmap))
|
||||
return false;
|
||||
|
||||
int num_planes = mp_aframe_get_planes(mpa);
|
||||
int num_planes = newmap->num;
|
||||
uint8_t **planes = mp_aframe_get_data_rw(mpa);
|
||||
uint8_t *old_planes[MP_NUM_CHANNELS];
|
||||
assert(num_planes <= MP_NUM_CHANNELS);
|
||||
@@ -429,8 +432,7 @@ static bool reorder_planes(struct mp_aframe *mpa, int *reorder,
|
||||
}
|
||||
|
||||
static int resample_frame(struct AVAudioResampleContext *r,
|
||||
struct mp_aframe *out, struct mp_aframe *in,
|
||||
int consume_in)
|
||||
struct mp_aframe *out, struct mp_aframe *in)
|
||||
{
|
||||
// Be aware that the channel layout and count can be different for in and
|
||||
// out frames. In some situations the caller will fix up the frames before
|
||||
@@ -443,26 +445,17 @@ static int resample_frame(struct AVAudioResampleContext *r,
|
||||
av_o ? av_o->nb_samples : 0,
|
||||
av_i ? av_i->extended_data : NULL,
|
||||
av_i ? av_i->linesize[0] : 0,
|
||||
av_i ? MPMIN(av_i->nb_samples, consume_in) : 0);
|
||||
av_i ? av_i->nb_samples : 0);
|
||||
}
|
||||
|
||||
static struct mp_frame filter_resample_output(struct priv *p,
|
||||
struct mp_aframe *in)
|
||||
static void filter_resample(struct mp_aconverter *p, struct mp_aframe *in)
|
||||
{
|
||||
struct mp_aframe *out = NULL;
|
||||
|
||||
if (!p->avrctx)
|
||||
goto error;
|
||||
|
||||
// Limit the filtered data size for better latency when changing speed.
|
||||
// Avoid buffering data within the resampler => restrict input size.
|
||||
// p->in_rate already includes the speed factor.
|
||||
double s = p->opts->max_output_frame_size / 1000 * p->in_rate;
|
||||
int max_in = lrint(MPCLAMP(s, 128, INT_MAX));
|
||||
int consume_in = in ? mp_aframe_get_size(in) : 0;
|
||||
consume_in = MPMIN(consume_in, max_in);
|
||||
|
||||
int samples = get_out_samples(p, consume_in);
|
||||
int samples = get_out_samples(p, in ? mp_aframe_get_size(in) : 0);
|
||||
out = mp_aframe_create();
|
||||
mp_aframe_config_copy(out, p->pool_fmt);
|
||||
if (mp_aframe_pool_allocate(p->out_pool, out, samples) < 0)
|
||||
@@ -470,7 +463,7 @@ static struct mp_frame filter_resample_output(struct priv *p,
|
||||
|
||||
int out_samples = 0;
|
||||
if (samples) {
|
||||
out_samples = resample_frame(p->avrctx, out, in, consume_in);
|
||||
out_samples = resample_frame(p->avrctx, out, in);
|
||||
if (out_samples < 0 || out_samples > samples)
|
||||
goto error;
|
||||
mp_aframe_set_size(out, out_samples);
|
||||
@@ -491,7 +484,7 @@ static struct mp_frame filter_resample_output(struct priv *p,
|
||||
}
|
||||
int got = 0;
|
||||
if (out_samples)
|
||||
got = resample_frame(p->avrctx_out, new, out, out_samples);
|
||||
got = resample_frame(p->avrctx_out, new, out);
|
||||
talloc_free(out);
|
||||
out = new;
|
||||
if (got != out_samples)
|
||||
@@ -500,140 +493,43 @@ static struct mp_frame filter_resample_output(struct priv *p,
|
||||
|
||||
extra_output_conversion(out);
|
||||
|
||||
if (in) {
|
||||
if (in)
|
||||
mp_aframe_copy_attributes(out, in);
|
||||
p->current_pts = mp_aframe_end_pts(in);
|
||||
mp_aframe_skip_samples(in, consume_in);
|
||||
}
|
||||
|
||||
if (out_samples) {
|
||||
if (p->current_pts != MP_NOPTS_VALUE) {
|
||||
double delay = get_delay(p) * mp_aframe_get_speed(out) +
|
||||
mp_aframe_duration(out) +
|
||||
(p->input ? mp_aframe_duration(p->input) : 0);
|
||||
mp_aframe_set_pts(out, p->current_pts - delay);
|
||||
mp_aframe_mul_speed(out, p->speed);
|
||||
}
|
||||
p->output = out;
|
||||
} else {
|
||||
TA_FREEP(&out);
|
||||
talloc_free(out);
|
||||
}
|
||||
p->output_eof = !in; // we've read everything
|
||||
|
||||
return out ? MAKE_FRAME(MP_FRAME_AUDIO, out) : MP_NO_FRAME;
|
||||
return;
|
||||
error:
|
||||
talloc_free(out);
|
||||
MP_ERR(p, "Error on resampling.\n");
|
||||
mp_filter_internal_mark_failed(p->public.f);
|
||||
return MP_NO_FRAME;
|
||||
}
|
||||
|
||||
static void process(struct mp_filter *f)
|
||||
static void filter(struct mp_aconverter *p)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
|
||||
if (!mp_pin_in_needs_data(f->ppins[1]))
|
||||
if (p->output || p->output_eof || !(p->input || p->input_eof))
|
||||
return;
|
||||
|
||||
p->speed = p->cmd_speed * p->public.speed;
|
||||
int new_rate = rate_from_speed(p->in_rate_user, p->playback_speed);
|
||||
|
||||
struct mp_aframe *input = NULL;
|
||||
if (!p->input) {
|
||||
struct mp_frame frame = mp_pin_out_read(f->ppins[0]);
|
||||
if (p->passthrough_mode && new_rate != p->in_rate)
|
||||
configure_lavrr(p, false);
|
||||
|
||||
if (frame.type == MP_FRAME_AUDIO) {
|
||||
input = frame.data;
|
||||
} else if (!frame.type) {
|
||||
return; // no new data
|
||||
} else if (frame.type != MP_FRAME_EOF) {
|
||||
MP_ERR(p, "Unsupported frame type.\n");
|
||||
mp_frame_unref(&frame);
|
||||
mp_filter_internal_mark_failed(f);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!input && !p->avrctx) {
|
||||
// Obviously no draining needed.
|
||||
mp_pin_in_write(f->ppins[1], MP_EOF_FRAME);
|
||||
return;
|
||||
}
|
||||
if (p->passthrough_mode) {
|
||||
p->output = p->input;
|
||||
p->input = NULL;
|
||||
p->output_eof = p->input_eof;
|
||||
p->input_eof = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (input) {
|
||||
assert(!p->input);
|
||||
|
||||
struct mp_swresample *s = &p->public;
|
||||
|
||||
int in_rate = mp_aframe_get_rate(input);
|
||||
int in_format = mp_aframe_get_format(input);
|
||||
struct mp_chmap in_channels = {0};
|
||||
mp_aframe_get_chmap(input, &in_channels);
|
||||
|
||||
if (!in_rate || !in_format || !in_channels.num) {
|
||||
MP_ERR(p, "Frame with invalid format unsupported\n");
|
||||
talloc_free(input);
|
||||
mp_filter_internal_mark_failed(f);
|
||||
return;
|
||||
}
|
||||
|
||||
int out_rate = s->out_rate ? s->out_rate : in_rate;
|
||||
int out_format = s->out_format ? s->out_format : in_format;
|
||||
struct mp_chmap out_channels =
|
||||
s->out_channels.num ? s->out_channels : in_channels;
|
||||
|
||||
if (p->in_rate_user != in_rate ||
|
||||
p->in_format != in_format ||
|
||||
!mp_chmap_equals(&p->in_channels, &in_channels) ||
|
||||
p->out_rate != out_rate ||
|
||||
p->out_format != out_format ||
|
||||
!mp_chmap_equals(&p->out_channels, &out_channels) ||
|
||||
!p->avrctx)
|
||||
{
|
||||
if (p->avrctx) {
|
||||
// drain remaining audio
|
||||
struct mp_frame out = filter_resample_output(p, NULL);
|
||||
if (out.type) {
|
||||
mp_pin_in_write(f->ppins[1], out);
|
||||
// continue filtering next time.
|
||||
mp_pin_out_unread(f->ppins[0],
|
||||
MAKE_FRAME(MP_FRAME_AUDIO, input));
|
||||
input = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
MP_VERBOSE(p, "format change, reinitializing resampler\n");
|
||||
|
||||
p->in_rate_user = in_rate;
|
||||
p->in_format = in_format;
|
||||
p->in_channels = in_channels;
|
||||
p->out_rate = out_rate;
|
||||
p->out_format = out_format;
|
||||
p->out_channels = out_channels;
|
||||
|
||||
if (!configure_lavrr(p, true)) {
|
||||
talloc_free(input);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!input) {
|
||||
// continue filtering next time
|
||||
mp_filter_internal_mark_progress(f);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
p->input = input;
|
||||
}
|
||||
|
||||
int new_rate = rate_from_speed(p->in_rate_user, p->speed);
|
||||
bool exact_rate = new_rate == p->in_rate;
|
||||
bool use_comp = fabs(new_rate / (double)p->in_rate - 1) <= 0.01;
|
||||
// If we've never used compensation, avoid setting it - even if it's in
|
||||
// theory a NOP, libswresample will enable resampling. _If_ we're
|
||||
// resampling, we might have to disable previously enabled compensation.
|
||||
if (exact_rate && !p->is_resampling)
|
||||
use_comp = false;
|
||||
if (p->avrctx && use_comp) {
|
||||
AVRational r =
|
||||
av_d2q(p->speed * p->in_rate_user / p->in_rate, INT_MAX / 2);
|
||||
if (p->avrctx && !(!p->is_resampling && new_rate == p->in_rate)) {
|
||||
AVRational r = av_d2q(p->playback_speed * p->in_rate_user / p->in_rate,
|
||||
INT_MAX / 2);
|
||||
// Essentially, swr/avresample_set_compensation() does 2 things:
|
||||
// - adjust output sample rate by sample_delta/compensation_distance
|
||||
// - reset the adjustment after compensation_distance output samples
|
||||
@@ -642,105 +538,104 @@ static void process(struct mp_filter *f)
|
||||
// feeding it, until the next filter() call.
|
||||
int mult = INT_MAX / 2 / MPMAX(MPMAX(abs(r.num), abs(r.den)), 1);
|
||||
r = (AVRational){ r.num * mult, r.den * mult };
|
||||
if (r.den == r.num)
|
||||
r = (AVRational){0}; // fully disable
|
||||
if (avresample_set_compensation(p->avrctx, r.den - r.num, r.den) >= 0) {
|
||||
exact_rate = true;
|
||||
p->is_resampling = true; // libswresample can auto-enable it
|
||||
new_rate = p->in_rate;
|
||||
p->is_resampling = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!exact_rate) {
|
||||
bool need_reinit = fabs(new_rate / (double)p->in_rate - 1) > 0.01;
|
||||
if (need_reinit && new_rate != p->in_rate) {
|
||||
// Before reconfiguring, drain the audio that is still buffered
|
||||
// in the resampler.
|
||||
struct mp_frame out = filter_resample_output(p, NULL);
|
||||
bool need_drain = !!out.type;
|
||||
if (need_drain)
|
||||
mp_pin_in_write(f->ppins[1], out);
|
||||
filter_resample(p, NULL);
|
||||
// Reinitialize resampler.
|
||||
configure_lavrr(p, false);
|
||||
// If we've written output, we must continue filtering next time.
|
||||
if (need_drain)
|
||||
return;
|
||||
p->output_eof = false;
|
||||
if (p->output)
|
||||
return; // need to read output before continuing filtering
|
||||
}
|
||||
|
||||
struct mp_frame out = filter_resample_output(p, p->input);
|
||||
filter_resample(p, p->input);
|
||||
TA_FREEP(&p->input);
|
||||
p->input_eof = false;
|
||||
}
|
||||
|
||||
if (out.type) {
|
||||
mp_pin_in_write(f->ppins[1], out);
|
||||
if (!p->input)
|
||||
mp_pin_out_repeat_eof(f->ppins[0]);
|
||||
} else if (p->input) {
|
||||
mp_filter_internal_mark_progress(f); // try to consume more input
|
||||
} else {
|
||||
mp_pin_in_write(f->ppins[1], MP_EOF_FRAME);
|
||||
// Queue input. If true, ownership of in passes to mp_aconverted and the input
|
||||
// was accepted. Otherwise, return false and reject in.
|
||||
// in==NULL means trigger EOF.
|
||||
bool mp_aconverter_write_input(struct mp_aconverter *p, struct mp_aframe *in)
|
||||
{
|
||||
if (p->input || p->input_eof)
|
||||
return false;
|
||||
|
||||
p->input = in;
|
||||
p->input_eof = !in;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Return output frame, or NULL if nothing available.
|
||||
// *eof is set to true if NULL is returned, and it was due to EOF.
|
||||
struct mp_aframe *mp_aconverter_read_output(struct mp_aconverter *p, bool *eof)
|
||||
{
|
||||
*eof = false;
|
||||
|
||||
filter(p);
|
||||
|
||||
if (p->output) {
|
||||
struct mp_aframe *out = p->output;
|
||||
p->output = NULL;
|
||||
return out;
|
||||
}
|
||||
|
||||
if (p->input && !mp_aframe_get_size(p->input))
|
||||
TA_FREEP(&p->input);
|
||||
*eof = p->output_eof;
|
||||
p->output_eof = false;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
double mp_swresample_get_delay(struct mp_swresample *s)
|
||||
double mp_aconverter_get_latency(struct mp_aconverter *p)
|
||||
{
|
||||
struct priv *p = s->f->priv;
|
||||
double delay = get_delay(p);
|
||||
|
||||
return get_delay(p);
|
||||
if (p->input)
|
||||
delay += mp_aframe_duration(p->input);
|
||||
|
||||
// In theory this is influenced by playback speed, but other parts of the
|
||||
// player get it wrong anyway.
|
||||
if (p->output)
|
||||
delay += mp_aframe_duration(p->output);
|
||||
|
||||
return delay;
|
||||
}
|
||||
|
||||
static bool command(struct mp_filter *f, struct mp_filter_command *cmd)
|
||||
static void destroy_aconverter(void *ptr)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
|
||||
if (cmd->type == MP_FILTER_COMMAND_SET_SPEED_RESAMPLE) {
|
||||
p->cmd_speed = cmd->speed;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void destroy(struct mp_filter *f)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
struct mp_aconverter *p = ptr;
|
||||
|
||||
close_lavrr(p);
|
||||
TA_FREEP(&p->input);
|
||||
|
||||
talloc_free(p->input);
|
||||
talloc_free(p->output);
|
||||
}
|
||||
|
||||
static const struct mp_filter_info swresample_filter = {
|
||||
.name = "swresample",
|
||||
.priv_size = sizeof(struct priv),
|
||||
.process = process,
|
||||
.command = command,
|
||||
.reset = reset,
|
||||
.destroy = destroy,
|
||||
};
|
||||
|
||||
struct mp_swresample *mp_swresample_create(struct mp_filter *parent,
|
||||
struct mp_resample_opts *opts)
|
||||
// If opts is not NULL, the pointer must be valid for the lifetime of the
|
||||
// mp_aconverter.
|
||||
struct mp_aconverter *mp_aconverter_create(struct mpv_global *global,
|
||||
struct mp_log *log,
|
||||
const struct mp_resample_opts *opts)
|
||||
{
|
||||
struct mp_filter *f = mp_filter_create(parent, &swresample_filter);
|
||||
if (!f)
|
||||
return NULL;
|
||||
struct mp_aconverter *p = talloc_zero(NULL, struct mp_aconverter);
|
||||
p->log = log;
|
||||
p->global = global;
|
||||
|
||||
mp_filter_add_pin(f, MP_PIN_IN, "in");
|
||||
mp_filter_add_pin(f, MP_PIN_OUT, "out");
|
||||
static const struct mp_resample_opts defs = MP_RESAMPLE_OPTS_DEF;
|
||||
|
||||
struct priv *p = f->priv;
|
||||
p->public.f = f;
|
||||
p->public.speed = 1.0;
|
||||
p->cmd_speed = 1.0;
|
||||
p->log = f->log;
|
||||
|
||||
if (opts) {
|
||||
p->opts = talloc_dup(p, opts);
|
||||
p->opts->avopts = mp_dup_str_array(p, p->opts->avopts);
|
||||
} else {
|
||||
p->opts = mp_get_config_group(p, f->global, &resample_conf);
|
||||
}
|
||||
p->opts = opts ? opts : &defs;
|
||||
|
||||
p->reorder_buffer = mp_aframe_pool_create(p);
|
||||
p->out_pool = mp_aframe_pool_create(p);
|
||||
|
||||
return &p->public;
|
||||
talloc_set_destructor(p, destroy_aconverter);
|
||||
|
||||
return p;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "chmap.h"
|
||||
|
||||
struct mp_aconverter;
|
||||
struct mp_aframe;
|
||||
struct mpv_global;
|
||||
struct mp_log;
|
||||
|
||||
struct mp_resample_opts {
|
||||
int filter_size;
|
||||
int phase_shift;
|
||||
int linear;
|
||||
double cutoff;
|
||||
int normalize;
|
||||
int allow_passthrough;
|
||||
char **avopts;
|
||||
};
|
||||
|
||||
#define MP_RESAMPLE_OPTS_DEF { \
|
||||
.filter_size = 16, \
|
||||
.cutoff = 0.0, \
|
||||
.phase_shift = 10, \
|
||||
.normalize = -1, \
|
||||
}
|
||||
|
||||
struct mp_aconverter *mp_aconverter_create(struct mpv_global *global,
|
||||
struct mp_log *log,
|
||||
const struct mp_resample_opts *opts);
|
||||
bool mp_aconverter_reconfig(struct mp_aconverter *p,
|
||||
int in_rate, int in_format, struct mp_chmap in_channels,
|
||||
int out_rate, int out_format, struct mp_chmap out_channels);
|
||||
void mp_aconverter_flush(struct mp_aconverter *p);
|
||||
void mp_aconverter_set_speed(struct mp_aconverter *p, double speed);
|
||||
bool mp_aconverter_write_input(struct mp_aconverter *p, struct mp_aframe *in);
|
||||
struct mp_aframe *mp_aconverter_read_output(struct mp_aconverter *p, bool *eof);
|
||||
double mp_aconverter_get_latency(struct mp_aconverter *p);
|
||||
+12
-187
@@ -32,11 +32,6 @@ struct mp_aframe {
|
||||
// We support spdif formats, which are allocated as AV_SAMPLE_FMT_S16.
|
||||
int format;
|
||||
double pts;
|
||||
double speed;
|
||||
};
|
||||
|
||||
struct avframe_opaque {
|
||||
double speed;
|
||||
};
|
||||
|
||||
static void free_frame(void *ptr)
|
||||
@@ -48,11 +43,11 @@ static void free_frame(void *ptr)
|
||||
struct mp_aframe *mp_aframe_create(void)
|
||||
{
|
||||
struct mp_aframe *frame = talloc_zero(NULL, struct mp_aframe);
|
||||
frame->pts = MP_NOPTS_VALUE;
|
||||
frame->av_frame = av_frame_alloc();
|
||||
if (!frame->av_frame)
|
||||
abort();
|
||||
talloc_set_destructor(frame, free_frame);
|
||||
mp_aframe_reset(frame);
|
||||
return frame;
|
||||
}
|
||||
|
||||
@@ -66,7 +61,6 @@ struct mp_aframe *mp_aframe_new_ref(struct mp_aframe *frame)
|
||||
dst->chmap = frame->chmap;
|
||||
dst->format = frame->format;
|
||||
dst->pts = frame->pts;
|
||||
dst->speed = frame->speed;
|
||||
|
||||
if (mp_aframe_is_allocated(frame)) {
|
||||
if (av_frame_ref(dst->av_frame, frame->av_frame) < 0)
|
||||
@@ -86,7 +80,6 @@ void mp_aframe_reset(struct mp_aframe *frame)
|
||||
frame->chmap.num = 0;
|
||||
frame->format = 0;
|
||||
frame->pts = MP_NOPTS_VALUE;
|
||||
frame->speed = 1.0;
|
||||
}
|
||||
|
||||
// Remove all actual audio data and leave only the metadata.
|
||||
@@ -127,11 +120,6 @@ struct mp_aframe *mp_aframe_from_avframe(struct AVFrame *av_frame)
|
||||
mp_chmap_from_channels(&frame->chmap, av_frame->channels);
|
||||
#endif
|
||||
|
||||
if (av_frame->opaque_ref) {
|
||||
struct avframe_opaque *op = (void *)av_frame->opaque_ref->data;
|
||||
frame->speed = op->speed;
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
@@ -149,16 +137,6 @@ struct AVFrame *mp_aframe_to_avframe(struct mp_aframe *frame)
|
||||
if (!mp_chmap_is_lavc(&frame->chmap))
|
||||
return NULL;
|
||||
|
||||
if (!frame->av_frame->opaque_ref && frame->speed != 1.0) {
|
||||
frame->av_frame->opaque_ref =
|
||||
av_buffer_alloc(sizeof(struct avframe_opaque));
|
||||
if (!frame->av_frame->opaque_ref)
|
||||
return NULL;
|
||||
|
||||
struct avframe_opaque *op = (void *)frame->av_frame->opaque_ref->data;
|
||||
op->speed = frame->speed;
|
||||
}
|
||||
|
||||
return av_frame_clone(frame->av_frame);
|
||||
}
|
||||
|
||||
@@ -205,7 +183,6 @@ void mp_aframe_config_copy(struct mp_aframe *dst, struct mp_aframe *src)
|
||||
void mp_aframe_copy_attributes(struct mp_aframe *dst, struct mp_aframe *src)
|
||||
{
|
||||
dst->pts = src->pts;
|
||||
dst->speed = src->speed;
|
||||
|
||||
int rate = dst->av_frame->sample_rate;
|
||||
|
||||
@@ -339,37 +316,6 @@ void mp_aframe_set_pts(struct mp_aframe *frame, double pts)
|
||||
frame->pts = pts;
|
||||
}
|
||||
|
||||
// Set a speed factor. This is multiplied with the sample rate to get the
|
||||
// "effective" samplerate (mp_aframe_get_effective_rate()), which will be used
|
||||
// to do PTS calculations. If speed!=1.0, the PTS values always refer to the
|
||||
// original PTS (before changing speed), and if you want reasonably continuous
|
||||
// PTS between frames, you need to use the effective samplerate.
|
||||
void mp_aframe_set_speed(struct mp_aframe *frame, double factor)
|
||||
{
|
||||
frame->speed = factor;
|
||||
}
|
||||
|
||||
// Adjust current speed factor.
|
||||
void mp_aframe_mul_speed(struct mp_aframe *frame, double factor)
|
||||
{
|
||||
frame->speed *= factor;
|
||||
}
|
||||
|
||||
double mp_aframe_get_speed(struct mp_aframe *frame)
|
||||
{
|
||||
return frame->speed;
|
||||
}
|
||||
|
||||
// Matters for speed changed frames (such as a frame which has been resampled
|
||||
// to play at a different speed).
|
||||
// Return the sample rate at which the frame would have to be played to result
|
||||
// in the same duration as the original frame before the speed change.
|
||||
// This is used for A/V sync.
|
||||
double mp_aframe_get_effective_rate(struct mp_aframe *frame)
|
||||
{
|
||||
return mp_aframe_get_rate(frame) / frame->speed;
|
||||
}
|
||||
|
||||
// Return number of data pointers.
|
||||
int mp_aframe_get_planes(struct mp_aframe *frame)
|
||||
{
|
||||
@@ -393,69 +339,48 @@ int mp_aframe_get_total_plane_samples(struct mp_aframe *frame)
|
||||
? 1 : mp_aframe_get_channels(frame));
|
||||
}
|
||||
|
||||
char *mp_aframe_format_str_buf(char *buf, size_t buf_size, struct mp_aframe *fmt)
|
||||
{
|
||||
char ch[128];
|
||||
mp_chmap_to_str_buf(ch, sizeof(ch), &fmt->chmap);
|
||||
char *hr_ch = mp_chmap_to_str_hr(&fmt->chmap);
|
||||
if (strcmp(hr_ch, ch) != 0)
|
||||
mp_snprintf_cat(ch, sizeof(ch), " (%s)", hr_ch);
|
||||
snprintf(buf, buf_size, "%dHz %s %dch %s", fmt->av_frame->sample_rate,
|
||||
ch, fmt->chmap.num, af_fmt_to_str(fmt->format));
|
||||
return buf;
|
||||
}
|
||||
|
||||
// Set data to the audio after the given number of samples (i.e. slice it).
|
||||
void mp_aframe_skip_samples(struct mp_aframe *f, int samples)
|
||||
{
|
||||
assert(samples >= 0 && samples <= mp_aframe_get_size(f));
|
||||
|
||||
if (av_frame_make_writable(f->av_frame) < 0)
|
||||
return; // go complain to ffmpeg
|
||||
|
||||
int num_planes = mp_aframe_get_planes(f);
|
||||
size_t sstride = mp_aframe_get_sstride(f);
|
||||
for (int n = 0; n < num_planes; n++) {
|
||||
memmove(f->av_frame->extended_data[n],
|
||||
f->av_frame->extended_data[n] + samples * sstride,
|
||||
(f->av_frame->nb_samples - samples) * sstride);
|
||||
}
|
||||
for (int n = 0; n < num_planes; n++)
|
||||
f->av_frame->extended_data[n] += samples * sstride;
|
||||
|
||||
f->av_frame->nb_samples -= samples;
|
||||
|
||||
if (f->pts != MP_NOPTS_VALUE)
|
||||
f->pts += samples / mp_aframe_get_effective_rate(f);
|
||||
f->pts += samples / (double)mp_aframe_get_rate(f);
|
||||
}
|
||||
|
||||
// Return the timestamp of the sample just after the end of this frame.
|
||||
double mp_aframe_end_pts(struct mp_aframe *f)
|
||||
{
|
||||
double rate = mp_aframe_get_effective_rate(f);
|
||||
if (f->pts == MP_NOPTS_VALUE || rate <= 0)
|
||||
int rate = mp_aframe_get_rate(f);
|
||||
if (f->pts == MP_NOPTS_VALUE || rate < 1)
|
||||
return MP_NOPTS_VALUE;
|
||||
return f->pts + f->av_frame->nb_samples / rate;
|
||||
return f->pts + f->av_frame->nb_samples / (double)rate;
|
||||
}
|
||||
|
||||
// Return the duration in seconds of the frame (0 if invalid).
|
||||
double mp_aframe_duration(struct mp_aframe *f)
|
||||
{
|
||||
double rate = mp_aframe_get_effective_rate(f);
|
||||
if (rate <= 0)
|
||||
int rate = mp_aframe_get_rate(f);
|
||||
if (rate < 1)
|
||||
return 0;
|
||||
return f->av_frame->nb_samples / rate;
|
||||
return f->av_frame->nb_samples / (double)rate;
|
||||
}
|
||||
|
||||
// Clip the given frame to the given timestamp range. Adjusts the frame size
|
||||
// and timestamp.
|
||||
// Refuses to change spdif frames.
|
||||
void mp_aframe_clip_timestamps(struct mp_aframe *f, double start, double end)
|
||||
{
|
||||
double f_end = mp_aframe_end_pts(f);
|
||||
double rate = mp_aframe_get_effective_rate(f);
|
||||
int rate = mp_aframe_get_rate(f);
|
||||
if (f_end == MP_NOPTS_VALUE)
|
||||
return;
|
||||
if (af_fmt_is_spdif(mp_aframe_get_format(f)))
|
||||
return;
|
||||
if (end != MP_NOPTS_VALUE) {
|
||||
if (f_end >= end) {
|
||||
if (f->pts >= end) {
|
||||
@@ -480,102 +405,6 @@ void mp_aframe_clip_timestamps(struct mp_aframe *f, double start, double end)
|
||||
}
|
||||
}
|
||||
|
||||
bool mp_aframe_copy_samples(struct mp_aframe *dst, int dst_offset,
|
||||
struct mp_aframe *src, int src_offset,
|
||||
int samples)
|
||||
{
|
||||
if (!mp_aframe_config_equals(dst, src))
|
||||
return false;
|
||||
|
||||
if (mp_aframe_get_size(dst) < dst_offset + samples ||
|
||||
mp_aframe_get_size(src) < src_offset + samples)
|
||||
return false;
|
||||
|
||||
uint8_t **s = mp_aframe_get_data_ro(src);
|
||||
uint8_t **d = mp_aframe_get_data_rw(dst);
|
||||
if (!s || !d)
|
||||
return false;
|
||||
|
||||
int planes = mp_aframe_get_planes(dst);
|
||||
size_t sstride = mp_aframe_get_sstride(dst);
|
||||
|
||||
for (int n = 0; n < planes; n++) {
|
||||
memcpy(d[n] + dst_offset * sstride, s[n] + src_offset * sstride,
|
||||
samples * sstride);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mp_aframe_set_silence(struct mp_aframe *f, int offset, int samples)
|
||||
{
|
||||
if (mp_aframe_get_size(f) < offset + samples)
|
||||
return false;
|
||||
|
||||
int format = mp_aframe_get_format(f);
|
||||
uint8_t **d = mp_aframe_get_data_rw(f);
|
||||
if (!d)
|
||||
return false;
|
||||
|
||||
int planes = mp_aframe_get_planes(f);
|
||||
size_t sstride = mp_aframe_get_sstride(f);
|
||||
|
||||
for (int n = 0; n < planes; n++)
|
||||
af_fill_silence(d[n] + offset * sstride, samples * sstride, format);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mp_aframe_reverse(struct mp_aframe *f)
|
||||
{
|
||||
int format = mp_aframe_get_format(f);
|
||||
size_t bps = af_fmt_to_bytes(format);
|
||||
if (!af_fmt_is_pcm(format) || bps > 16)
|
||||
return false;
|
||||
|
||||
uint8_t **d = mp_aframe_get_data_rw(f);
|
||||
if (!d)
|
||||
return false;
|
||||
|
||||
int planes = mp_aframe_get_planes(f);
|
||||
int samples = mp_aframe_get_size(f);
|
||||
int channels = mp_aframe_get_channels(f);
|
||||
size_t sstride = mp_aframe_get_sstride(f);
|
||||
|
||||
int plane_samples = channels;
|
||||
if (af_fmt_is_planar(format))
|
||||
plane_samples = 1;
|
||||
|
||||
for (int p = 0; p < planes; p++) {
|
||||
for (int n = 0; n < samples / 2; n++) {
|
||||
int s1_offset = n * sstride;
|
||||
int s2_offset = (samples - 1 - n) * sstride;
|
||||
for (int c = 0; c < plane_samples; c++) {
|
||||
// Nobody said it'd be fast.
|
||||
char tmp[16];
|
||||
uint8_t *s1 = d[p] + s1_offset + c * bps;
|
||||
uint8_t *s2 = d[p] + s2_offset + c * bps;
|
||||
memcpy(tmp, s2, bps);
|
||||
memcpy(s2, s1, bps);
|
||||
memcpy(s1, tmp, bps);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int mp_aframe_approx_byte_size(struct mp_aframe *frame)
|
||||
{
|
||||
// God damn, AVFrame is too fucking annoying. Just go with the size that
|
||||
// allocating a new frame would use.
|
||||
int planes = mp_aframe_get_planes(frame);
|
||||
size_t sstride = mp_aframe_get_sstride(frame);
|
||||
int samples = frame->av_frame->nb_samples;
|
||||
int plane_size = MP_ALIGN_UP(sstride * MPMAX(samples, 1), 32);
|
||||
return plane_size * planes + sizeof(*frame);
|
||||
}
|
||||
|
||||
struct mp_aframe_pool {
|
||||
AVBufferPool *avpool;
|
||||
int element_size;
|
||||
@@ -598,11 +427,7 @@ int mp_aframe_pool_allocate(struct mp_aframe_pool *pool, struct mp_aframe *frame
|
||||
{
|
||||
int planes = mp_aframe_get_planes(frame);
|
||||
size_t sstride = mp_aframe_get_sstride(frame);
|
||||
// FFmpeg hardcodes similar hidden possibly-requirements in a number of
|
||||
// places: av_frame_get_buffer(), libavcodec's get_buffer(), mem.c,
|
||||
// probably more.
|
||||
int align_samples = MP_ALIGN_UP(MPMAX(samples, 1), 32);
|
||||
int plane_size = MP_ALIGN_UP(sstride * align_samples, 64);
|
||||
int plane_size = MP_ALIGN_UP(sstride * MPMAX(samples, 1), 32);
|
||||
int size = plane_size * planes;
|
||||
|
||||
if (size <= 0 || mp_aframe_is_allocated(frame))
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
struct mp_aframe;
|
||||
struct AVFrame;
|
||||
@@ -36,36 +34,21 @@ int mp_aframe_get_channels(struct mp_aframe *frame);
|
||||
int mp_aframe_get_rate(struct mp_aframe *frame);
|
||||
int mp_aframe_get_size(struct mp_aframe *frame);
|
||||
double mp_aframe_get_pts(struct mp_aframe *frame);
|
||||
double mp_aframe_get_speed(struct mp_aframe *frame);
|
||||
double mp_aframe_get_effective_rate(struct mp_aframe *frame);
|
||||
|
||||
bool mp_aframe_set_format(struct mp_aframe *frame, int format);
|
||||
bool mp_aframe_set_chmap(struct mp_aframe *frame, struct mp_chmap *in);
|
||||
bool mp_aframe_set_rate(struct mp_aframe *frame, int rate);
|
||||
bool mp_aframe_set_size(struct mp_aframe *frame, int samples);
|
||||
void mp_aframe_set_pts(struct mp_aframe *frame, double pts);
|
||||
void mp_aframe_set_speed(struct mp_aframe *frame, double factor);
|
||||
void mp_aframe_mul_speed(struct mp_aframe *frame, double factor);
|
||||
|
||||
int mp_aframe_get_planes(struct mp_aframe *frame);
|
||||
int mp_aframe_get_total_plane_samples(struct mp_aframe *frame);
|
||||
size_t mp_aframe_get_sstride(struct mp_aframe *frame);
|
||||
|
||||
bool mp_aframe_reverse(struct mp_aframe *frame);
|
||||
|
||||
int mp_aframe_approx_byte_size(struct mp_aframe *frame);
|
||||
|
||||
char *mp_aframe_format_str_buf(char *buf, size_t buf_size, struct mp_aframe *fmt);
|
||||
#define mp_aframe_format_str(fmt) mp_aframe_format_str_buf((char[32]){0}, 32, (fmt))
|
||||
|
||||
void mp_aframe_skip_samples(struct mp_aframe *f, int samples);
|
||||
double mp_aframe_end_pts(struct mp_aframe *f);
|
||||
double mp_aframe_duration(struct mp_aframe *f);
|
||||
void mp_aframe_clip_timestamps(struct mp_aframe *f, double start, double end);
|
||||
bool mp_aframe_copy_samples(struct mp_aframe *dst, int dst_offset,
|
||||
struct mp_aframe *src, int src_offset,
|
||||
int samples);
|
||||
bool mp_aframe_set_silence(struct mp_aframe *f, int offset, int samples);
|
||||
|
||||
struct mp_aframe_pool;
|
||||
struct mp_aframe_pool *mp_aframe_pool_create(void *ta_parent);
|
||||
|
||||
+625
@@ -0,0 +1,625 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <libavutil/buffer.h>
|
||||
#include <libavutil/frame.h>
|
||||
#include <libavutil/mem.h>
|
||||
#include <libavutil/version.h>
|
||||
|
||||
#include "mpv_talloc.h"
|
||||
#include "common/common.h"
|
||||
#include "fmt-conversion.h"
|
||||
#include "audio.h"
|
||||
#include "aframe.h"
|
||||
|
||||
static void update_redundant_info(struct mp_audio *mpa)
|
||||
{
|
||||
assert(mp_chmap_is_empty(&mpa->channels) ||
|
||||
mp_chmap_is_valid(&mpa->channels));
|
||||
mpa->nch = mpa->channels.num;
|
||||
mpa->bps = af_fmt_to_bytes(mpa->format);
|
||||
if (af_fmt_is_planar(mpa->format)) {
|
||||
mpa->spf = 1;
|
||||
mpa->num_planes = mpa->nch;
|
||||
mpa->sstride = mpa->bps;
|
||||
} else {
|
||||
mpa->spf = mpa->nch;
|
||||
mpa->num_planes = 1;
|
||||
mpa->sstride = mpa->bps * mpa->nch;
|
||||
}
|
||||
}
|
||||
|
||||
void mp_audio_set_format(struct mp_audio *mpa, int format)
|
||||
{
|
||||
mpa->format = format;
|
||||
update_redundant_info(mpa);
|
||||
}
|
||||
|
||||
void mp_audio_set_num_channels(struct mp_audio *mpa, int num_channels)
|
||||
{
|
||||
mp_chmap_from_channels(&mpa->channels, num_channels);
|
||||
update_redundant_info(mpa);
|
||||
}
|
||||
|
||||
void mp_audio_set_channels(struct mp_audio *mpa, const struct mp_chmap *chmap)
|
||||
{
|
||||
mpa->channels = *chmap;
|
||||
update_redundant_info(mpa);
|
||||
}
|
||||
|
||||
void mp_audio_copy_config(struct mp_audio *dst, const struct mp_audio *src)
|
||||
{
|
||||
dst->format = src->format;
|
||||
dst->channels = src->channels;
|
||||
dst->rate = src->rate;
|
||||
update_redundant_info(dst);
|
||||
}
|
||||
|
||||
bool mp_audio_config_equals(const struct mp_audio *a, const struct mp_audio *b)
|
||||
{
|
||||
return a->format == b->format && a->rate == b->rate &&
|
||||
mp_chmap_equals(&a->channels, &b->channels);
|
||||
}
|
||||
|
||||
bool mp_audio_config_valid(const struct mp_audio *mpa)
|
||||
{
|
||||
return mp_chmap_is_valid(&mpa->channels) && af_fmt_is_valid(mpa->format)
|
||||
&& mpa->rate >= 1 && mpa->rate < 10000000;
|
||||
}
|
||||
|
||||
char *mp_audio_config_to_str_buf(char *buf, size_t buf_sz, struct mp_audio *mpa)
|
||||
{
|
||||
char ch[128];
|
||||
mp_chmap_to_str_buf(ch, sizeof(ch), &mpa->channels);
|
||||
char *hr_ch = mp_chmap_to_str_hr(&mpa->channels);
|
||||
if (strcmp(hr_ch, ch) != 0)
|
||||
mp_snprintf_cat(ch, sizeof(ch), " (%s)", hr_ch);
|
||||
snprintf(buf, buf_sz, "%dHz %s %dch %s", mpa->rate,
|
||||
ch, mpa->channels.num, af_fmt_to_str(mpa->format));
|
||||
return buf;
|
||||
}
|
||||
|
||||
void mp_audio_force_interleaved_format(struct mp_audio *mpa)
|
||||
{
|
||||
if (af_fmt_is_planar(mpa->format))
|
||||
mp_audio_set_format(mpa, af_fmt_from_planar(mpa->format));
|
||||
}
|
||||
|
||||
// Return used size of a plane. (The size is the same for all planes.)
|
||||
int mp_audio_psize(struct mp_audio *mpa)
|
||||
{
|
||||
return mpa->samples * mpa->sstride;
|
||||
}
|
||||
|
||||
void mp_audio_set_null_data(struct mp_audio *mpa)
|
||||
{
|
||||
for (int n = 0; n < MP_NUM_CHANNELS; n++) {
|
||||
mpa->planes[n] = NULL;
|
||||
mpa->allocated[n] = NULL;
|
||||
}
|
||||
mpa->samples = 0;
|
||||
}
|
||||
|
||||
static int get_plane_size(const struct mp_audio *mpa, int samples)
|
||||
{
|
||||
if (samples < 0 || !mp_audio_config_valid(mpa))
|
||||
return -1;
|
||||
if (samples >= INT_MAX / mpa->sstride)
|
||||
return -1;
|
||||
return MPMAX(samples * mpa->sstride, 1);
|
||||
}
|
||||
|
||||
static void mp_audio_destructor(void *ptr)
|
||||
{
|
||||
struct mp_audio *mpa = ptr;
|
||||
for (int n = 0; n < MP_NUM_CHANNELS; n++)
|
||||
av_buffer_unref(&mpa->allocated[n]);
|
||||
}
|
||||
|
||||
/* Reallocate the data stored in mpa->planes[n] so that enough samples are
|
||||
* available on every plane. The previous data is kept (for the smallest
|
||||
* common number of samples before/after resize).
|
||||
*
|
||||
* This also makes sure the resulting buffer is writable (even in the case
|
||||
* the buffer has the correct size).
|
||||
*
|
||||
* mpa->samples is not set or used.
|
||||
*
|
||||
* This function is flexible enough to handle format and channel layout
|
||||
* changes. In these cases, all planes are reallocated as needed. Unused
|
||||
* planes are freed.
|
||||
*
|
||||
* mp_audio_realloc(mpa, 0) will still yield non-NULL for mpa->data[n].
|
||||
*
|
||||
* Allocated data is implicitly freed on talloc_free(mpa).
|
||||
*/
|
||||
void mp_audio_realloc(struct mp_audio *mpa, int samples)
|
||||
{
|
||||
int size = get_plane_size(mpa, samples);
|
||||
if (size < 0)
|
||||
abort(); // oom or invalid parameters
|
||||
if (!mp_audio_is_writeable(mpa)) {
|
||||
for (int n = 0; n < MP_NUM_CHANNELS; n++) {
|
||||
av_buffer_unref(&mpa->allocated[n]);
|
||||
mpa->planes[n] = NULL;
|
||||
}
|
||||
}
|
||||
for (int n = 0; n < mpa->num_planes; n++) {
|
||||
if (!mpa->allocated[n] || size != mpa->allocated[n]->size) {
|
||||
if (av_buffer_realloc(&mpa->allocated[n], size) < 0)
|
||||
abort(); // OOM
|
||||
}
|
||||
mpa->planes[n] = mpa->allocated[n]->data;
|
||||
}
|
||||
for (int n = mpa->num_planes; n < MP_NUM_CHANNELS; n++) {
|
||||
av_buffer_unref(&mpa->allocated[n]);
|
||||
mpa->planes[n] = NULL;
|
||||
}
|
||||
talloc_set_destructor(mpa, mp_audio_destructor);
|
||||
}
|
||||
|
||||
// Like mp_audio_realloc(), but only reallocate if the audio grows in size.
|
||||
// If the buffer is reallocated, also preallocate.
|
||||
void mp_audio_realloc_min(struct mp_audio *mpa, int samples)
|
||||
{
|
||||
if (samples > mp_audio_get_allocated_size(mpa) || !mp_audio_is_writeable(mpa)) {
|
||||
size_t alloc = ta_calc_prealloc_elems(samples);
|
||||
if (alloc > INT_MAX)
|
||||
abort(); // oom
|
||||
mp_audio_realloc(mpa, alloc);
|
||||
}
|
||||
}
|
||||
|
||||
/* Get the size allocated for the data, in number of samples. If the allocated
|
||||
* size isn't on sample boundaries (e.g. after format changes), the returned
|
||||
* sample number is a rounded down value.
|
||||
*
|
||||
* Note that this only works in situations where mp_audio_realloc() also works!
|
||||
*/
|
||||
int mp_audio_get_allocated_size(struct mp_audio *mpa)
|
||||
{
|
||||
int size = 0;
|
||||
for (int n = 0; n < mpa->num_planes; n++) {
|
||||
for (int i = 0; i < MP_NUM_CHANNELS && mpa->allocated[i]; i++) {
|
||||
uint8_t *start = mpa->allocated[i]->data;
|
||||
uint8_t *end = start + mpa->allocated[i]->size;
|
||||
uint8_t *plane = mpa->planes[n];
|
||||
if (plane >= start && plane < end) {
|
||||
int s = MPMIN((end - plane) / mpa->sstride, INT_MAX);
|
||||
size = n == 0 ? s : MPMIN(size, s);
|
||||
goto next;
|
||||
}
|
||||
}
|
||||
return 0; // plane is not covered by any buffer
|
||||
next: ;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
// Clear the samples [start, start + length) with silence.
|
||||
void mp_audio_fill_silence(struct mp_audio *mpa, int start, int length)
|
||||
{
|
||||
assert(start >= 0 && length >= 0 && start + length <= mpa->samples);
|
||||
int offset = start * mpa->sstride;
|
||||
int size = length * mpa->sstride;
|
||||
for (int n = 0; n < mpa->num_planes; n++) {
|
||||
if (n > 0 && mpa->planes[n] == mpa->planes[0])
|
||||
continue; // silly optimization for special cases
|
||||
af_fill_silence((char *)mpa->planes[n] + offset, size, mpa->format);
|
||||
}
|
||||
}
|
||||
|
||||
// All integer parameters are in samples.
|
||||
// dst and src can overlap.
|
||||
void mp_audio_copy(struct mp_audio *dst, int dst_offset,
|
||||
struct mp_audio *src, int src_offset, int length)
|
||||
{
|
||||
assert(mp_audio_config_equals(dst, src));
|
||||
assert(length >= 0);
|
||||
assert(dst_offset >= 0 && dst_offset + length <= dst->samples);
|
||||
assert(src_offset >= 0 && src_offset + length <= src->samples);
|
||||
|
||||
for (int n = 0; n < dst->num_planes; n++) {
|
||||
memmove((char *)dst->planes[n] + dst_offset * dst->sstride,
|
||||
(char *)src->planes[n] + src_offset * src->sstride,
|
||||
length * dst->sstride);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy fields that describe characteristics of the audio frame, but which are
|
||||
// not part of the core format (format/channels/rate), and not part of the
|
||||
// data (samples).
|
||||
void mp_audio_copy_attributes(struct mp_audio *dst, struct mp_audio *src)
|
||||
{
|
||||
// nothing yet
|
||||
}
|
||||
|
||||
// Set data to the audio after the given number of samples (i.e. slice it).
|
||||
void mp_audio_skip_samples(struct mp_audio *data, int samples)
|
||||
{
|
||||
assert(samples >= 0 && samples <= data->samples);
|
||||
|
||||
for (int n = 0; n < data->num_planes; n++)
|
||||
data->planes[n] = (uint8_t *)data->planes[n] + samples * data->sstride;
|
||||
|
||||
data->samples -= samples;
|
||||
|
||||
if (data->pts != MP_NOPTS_VALUE)
|
||||
data->pts += samples / (double)data->rate;
|
||||
}
|
||||
|
||||
// Return the timestamp of the sample just after the end of this frame.
|
||||
double mp_audio_end_pts(struct mp_audio *f)
|
||||
{
|
||||
if (f->pts == MP_NOPTS_VALUE || f->rate < 1)
|
||||
return MP_NOPTS_VALUE;
|
||||
return f->pts + f->samples / (double)f->rate;
|
||||
}
|
||||
|
||||
// Clip the given frame to the given timestamp range. Adjusts the frame size
|
||||
// and timestamp.
|
||||
void mp_audio_clip_timestamps(struct mp_audio *f, double start, double end)
|
||||
{
|
||||
double f_end = mp_audio_end_pts(f);
|
||||
if (f_end == MP_NOPTS_VALUE)
|
||||
return;
|
||||
if (end != MP_NOPTS_VALUE) {
|
||||
if (f_end >= end) {
|
||||
if (f->pts >= end) {
|
||||
f->samples = 0;
|
||||
} else {
|
||||
int new = (end - f->pts) * f->rate;
|
||||
f->samples = MPCLAMP(new, 0, f->samples);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (start != MP_NOPTS_VALUE) {
|
||||
if (f->pts < start) {
|
||||
if (f_end <= start) {
|
||||
f->samples = 0;
|
||||
f->pts = f_end;
|
||||
} else {
|
||||
int skip = (start - f->pts) * f->rate;
|
||||
skip = MPCLAMP(skip, 0, f->samples);
|
||||
mp_audio_skip_samples(f, skip);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Return false if the frame data is shared, true otherwise.
|
||||
// Will return true for non-refcounted frames.
|
||||
bool mp_audio_is_writeable(struct mp_audio *data)
|
||||
{
|
||||
bool ok = true;
|
||||
for (int n = 0; n < MP_NUM_CHANNELS; n++) {
|
||||
if (data->allocated[n])
|
||||
ok &= av_buffer_is_writable(data->allocated[n]);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
static void mp_audio_steal_data(struct mp_audio *dst, struct mp_audio *src)
|
||||
{
|
||||
talloc_set_destructor(dst, mp_audio_destructor);
|
||||
mp_audio_destructor(dst);
|
||||
*dst = *src;
|
||||
talloc_set_destructor(src, NULL);
|
||||
talloc_free(src);
|
||||
}
|
||||
|
||||
// Make sure the frame owns the audio data, and if not, copy the data.
|
||||
// Return negative value on failure (which means it can't be made writeable).
|
||||
// Non-refcounted frames are always considered writeable.
|
||||
int mp_audio_make_writeable(struct mp_audio *data)
|
||||
{
|
||||
if (!mp_audio_is_writeable(data)) {
|
||||
struct mp_audio *new = talloc(NULL, struct mp_audio);
|
||||
*new = *data;
|
||||
mp_audio_set_null_data(new); // use format only
|
||||
mp_audio_realloc(new, data->samples);
|
||||
new->samples = data->samples;
|
||||
mp_audio_copy(new, 0, data, 0, data->samples);
|
||||
mp_audio_steal_data(data, new);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct mp_audio *mp_audio_from_avframe(struct AVFrame *avframe)
|
||||
{
|
||||
AVFrame *tmp = NULL;
|
||||
struct mp_audio *new = talloc_zero(NULL, struct mp_audio);
|
||||
talloc_set_destructor(new, mp_audio_destructor);
|
||||
|
||||
mp_audio_set_format(new, af_from_avformat(avframe->format));
|
||||
|
||||
struct mp_chmap lavc_chmap;
|
||||
mp_chmap_from_lavc(&lavc_chmap, avframe->channel_layout);
|
||||
|
||||
#if LIBAVUTIL_VERSION_MICRO >= 100
|
||||
// FFmpeg being stupid POS again
|
||||
if (lavc_chmap.num != avframe->channels)
|
||||
mp_chmap_from_channels(&lavc_chmap, avframe->channels);
|
||||
#endif
|
||||
|
||||
new->rate = avframe->sample_rate;
|
||||
|
||||
mp_audio_set_channels(new, &lavc_chmap);
|
||||
|
||||
// Force refcounted frame.
|
||||
if (!avframe->buf[0]) {
|
||||
tmp = av_frame_alloc();
|
||||
if (!tmp)
|
||||
goto fail;
|
||||
if (av_frame_ref(tmp, avframe) < 0)
|
||||
goto fail;
|
||||
avframe = tmp;
|
||||
}
|
||||
|
||||
// If we can't handle the format (e.g. too many channels), bail out.
|
||||
if (!mp_audio_config_valid(new))
|
||||
goto fail;
|
||||
|
||||
for (int n = 0; n < AV_NUM_DATA_POINTERS + avframe->nb_extended_buf; n++) {
|
||||
AVBufferRef *buf = n < AV_NUM_DATA_POINTERS ? avframe->buf[n]
|
||||
: avframe->extended_buf[n - AV_NUM_DATA_POINTERS];
|
||||
if (!buf)
|
||||
break;
|
||||
if (n >= MP_NUM_CHANNELS)
|
||||
goto fail;
|
||||
new->allocated[n] = av_buffer_ref(buf);
|
||||
if (!new->allocated[n])
|
||||
goto fail;
|
||||
}
|
||||
|
||||
for (int n = 0; n < new->num_planes; n++)
|
||||
new->planes[n] = avframe->extended_data[n];
|
||||
new->samples = avframe->nb_samples;
|
||||
|
||||
return new;
|
||||
|
||||
fail:
|
||||
talloc_free(new);
|
||||
av_frame_free(&tmp);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct mp_audio *mp_audio_from_aframe(struct mp_aframe *aframe)
|
||||
{
|
||||
if (!aframe)
|
||||
return NULL;
|
||||
|
||||
struct AVFrame *av = mp_aframe_get_raw_avframe(aframe);
|
||||
struct mp_audio *res = mp_audio_from_avframe(av);
|
||||
if (!res)
|
||||
return NULL;
|
||||
struct mp_chmap chmap = {0};
|
||||
mp_aframe_get_chmap(aframe, &chmap);
|
||||
mp_audio_set_channels(res, &chmap);
|
||||
mp_audio_set_format(res, mp_aframe_get_format(aframe));
|
||||
res->pts = mp_aframe_get_pts(aframe);
|
||||
return res;
|
||||
}
|
||||
|
||||
void mp_audio_config_from_aframe(struct mp_audio *dst, struct mp_aframe *src)
|
||||
{
|
||||
*dst = (struct mp_audio){0};
|
||||
struct mp_chmap chmap = {0};
|
||||
mp_aframe_get_chmap(src, &chmap);
|
||||
mp_audio_set_channels(dst, &chmap);
|
||||
mp_audio_set_format(dst, mp_aframe_get_format(src));
|
||||
dst->rate = mp_aframe_get_rate(src);
|
||||
}
|
||||
|
||||
struct mp_aframe *mp_audio_to_aframe(struct mp_audio *mpa)
|
||||
{
|
||||
if (!mpa)
|
||||
return NULL;
|
||||
|
||||
struct mp_aframe *aframe = mp_aframe_create();
|
||||
struct AVFrame *av = mp_aframe_get_raw_avframe(aframe);
|
||||
mp_aframe_set_format(aframe, mpa->format);
|
||||
mp_aframe_set_chmap(aframe, &mpa->channels);
|
||||
mp_aframe_set_rate(aframe, mpa->rate);
|
||||
|
||||
// bullshit it into ffmpeg-compatible parameters
|
||||
struct mp_audio mpb = *mpa;
|
||||
struct mp_chmap chmap;
|
||||
mp_chmap_set_unknown(&chmap, mpb.channels.num);
|
||||
mp_audio_set_channels(&mpb, &chmap);
|
||||
if (af_fmt_is_spdif(mpb.format))
|
||||
mp_audio_set_format(&mpb, AF_FORMAT_S16);
|
||||
|
||||
// put the reference into av, which magically puts it into aframe
|
||||
// aframe keeps its parameters, so the bullshit doesn't matter
|
||||
if (mp_audio_to_avframe(&mpb, av) < 0) {
|
||||
talloc_free(aframe);
|
||||
return NULL;
|
||||
}
|
||||
return aframe;
|
||||
}
|
||||
|
||||
int mp_audio_to_avframe(struct mp_audio *frame, struct AVFrame *avframe)
|
||||
{
|
||||
av_frame_unref(avframe);
|
||||
|
||||
avframe->nb_samples = frame->samples;
|
||||
avframe->format = af_to_avformat(frame->format);
|
||||
if (avframe->format == AV_SAMPLE_FMT_NONE)
|
||||
goto fail;
|
||||
|
||||
avframe->channel_layout = mp_chmap_to_lavc(&frame->channels);
|
||||
if (!avframe->channel_layout)
|
||||
goto fail;
|
||||
#if LIBAVUTIL_VERSION_MICRO >= 100
|
||||
// FFmpeg being a stupid POS again
|
||||
avframe->channels = frame->channels.num;
|
||||
#endif
|
||||
avframe->sample_rate = frame->rate;
|
||||
|
||||
if (frame->num_planes > AV_NUM_DATA_POINTERS) {
|
||||
avframe->extended_data =
|
||||
av_mallocz_array(frame->num_planes, sizeof(avframe->extended_data[0]));
|
||||
int extbufs = frame->num_planes - AV_NUM_DATA_POINTERS;
|
||||
avframe->extended_buf =
|
||||
av_mallocz_array(extbufs, sizeof(avframe->extended_buf[0]));
|
||||
if (!avframe->extended_data || !avframe->extended_buf)
|
||||
goto fail;
|
||||
avframe->nb_extended_buf = extbufs;
|
||||
}
|
||||
|
||||
for (int p = 0; p < frame->num_planes; p++)
|
||||
avframe->extended_data[p] = frame->planes[p];
|
||||
avframe->linesize[0] = frame->samples * frame->sstride;
|
||||
|
||||
for (int p = 0; p < AV_NUM_DATA_POINTERS; p++)
|
||||
avframe->data[p] = avframe->extended_data[p];
|
||||
|
||||
for (int p = 0; p < frame->num_planes; p++) {
|
||||
if (!frame->allocated[p])
|
||||
break;
|
||||
AVBufferRef *nref = av_buffer_ref(frame->allocated[p]);
|
||||
if (!nref)
|
||||
goto fail;
|
||||
if (p < AV_NUM_DATA_POINTERS) {
|
||||
avframe->buf[p] = nref;
|
||||
} else {
|
||||
avframe->extended_buf[p - AV_NUM_DATA_POINTERS] = nref;
|
||||
}
|
||||
}
|
||||
|
||||
// Force refcounted frame.
|
||||
if (!avframe->buf[0]) {
|
||||
AVFrame *tmp = av_frame_alloc();
|
||||
if (!tmp)
|
||||
goto fail;
|
||||
if (av_frame_ref(tmp, avframe) < 0)
|
||||
goto fail;
|
||||
av_frame_free(&avframe);
|
||||
avframe = tmp;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
av_frame_unref(avframe);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Returns NULL on failure. The input is always unreffed.
|
||||
struct AVFrame *mp_audio_to_avframe_and_unref(struct mp_audio *frame)
|
||||
{
|
||||
struct AVFrame *avframe = av_frame_alloc();
|
||||
if (!avframe)
|
||||
goto fail;
|
||||
|
||||
if (mp_audio_to_avframe(frame, avframe) < 0)
|
||||
goto fail;
|
||||
|
||||
talloc_free(frame);
|
||||
return avframe;
|
||||
|
||||
fail:
|
||||
av_frame_free(&avframe);
|
||||
talloc_free(frame);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct mp_audio_pool {
|
||||
AVBufferPool *avpool;
|
||||
int element_size;
|
||||
};
|
||||
|
||||
struct mp_audio_pool *mp_audio_pool_create(void *ta_parent)
|
||||
{
|
||||
return talloc_zero(ta_parent, struct mp_audio_pool);
|
||||
}
|
||||
|
||||
static void mp_audio_pool_destructor(void *p)
|
||||
{
|
||||
struct mp_audio_pool *pool = p;
|
||||
av_buffer_pool_uninit(&pool->avpool);
|
||||
}
|
||||
|
||||
// Allocate data using the given format and number of samples.
|
||||
// Returns NULL on error.
|
||||
struct mp_audio *mp_audio_pool_get(struct mp_audio_pool *pool,
|
||||
const struct mp_audio *fmt, int samples)
|
||||
{
|
||||
int size = get_plane_size(fmt, samples);
|
||||
if (size < 0)
|
||||
return NULL;
|
||||
if (!pool->avpool || size > pool->element_size) {
|
||||
size_t alloc = ta_calc_prealloc_elems(size);
|
||||
if (alloc >= INT_MAX)
|
||||
return NULL;
|
||||
av_buffer_pool_uninit(&pool->avpool);
|
||||
pool->element_size = alloc;
|
||||
pool->avpool = av_buffer_pool_init(pool->element_size, NULL);
|
||||
if (!pool->avpool)
|
||||
return NULL;
|
||||
talloc_set_destructor(pool, mp_audio_pool_destructor);
|
||||
}
|
||||
struct mp_audio *new = talloc_ptrtype(NULL, new);
|
||||
talloc_set_destructor(new, mp_audio_destructor);
|
||||
*new = *fmt;
|
||||
mp_audio_set_null_data(new);
|
||||
new->samples = samples;
|
||||
for (int n = 0; n < new->num_planes; n++) {
|
||||
new->allocated[n] = av_buffer_pool_get(pool->avpool);
|
||||
if (!new->allocated[n]) {
|
||||
talloc_free(new);
|
||||
return NULL;
|
||||
}
|
||||
new->planes[n] = new->allocated[n]->data;
|
||||
}
|
||||
return new;
|
||||
}
|
||||
|
||||
// Return a copy of the given frame.
|
||||
// Returns NULL on error.
|
||||
struct mp_audio *mp_audio_pool_new_copy(struct mp_audio_pool *pool,
|
||||
struct mp_audio *frame)
|
||||
{
|
||||
struct mp_audio *new = mp_audio_pool_get(pool, frame, frame->samples);
|
||||
if (new) {
|
||||
mp_audio_copy(new, 0, frame, 0, new->samples);
|
||||
mp_audio_copy_attributes(new, frame);
|
||||
}
|
||||
return new;
|
||||
}
|
||||
|
||||
// Exactly like mp_audio_make_writeable(), but get the data from the pool.
|
||||
int mp_audio_pool_make_writeable(struct mp_audio_pool *pool,
|
||||
struct mp_audio *data)
|
||||
{
|
||||
if (mp_audio_is_writeable(data))
|
||||
return 0;
|
||||
struct mp_audio *new = mp_audio_pool_get(pool, data, data->samples);
|
||||
if (!new)
|
||||
return -1;
|
||||
mp_audio_copy(new, 0, data, 0, data->samples);
|
||||
mp_audio_copy_attributes(new, data);
|
||||
mp_audio_steal_data(data, new);
|
||||
return 0;
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MP_AUDIO_H
|
||||
#define MP_AUDIO_H
|
||||
|
||||
#include "format.h"
|
||||
#include "chmap.h"
|
||||
|
||||
// Audio data chunk
|
||||
struct mp_audio {
|
||||
int samples; // number of samples in data (per channel)
|
||||
void *planes[MP_NUM_CHANNELS]; // data buffer (one per plane)
|
||||
int rate; // sample rate
|
||||
struct mp_chmap channels; // channel layout, use mp_audio_set_*() to set
|
||||
int format; // format (AF_FORMAT_...), use mp_audio_set_format() to set
|
||||
// Redundant fields, for convenience
|
||||
int sstride; // distance between 2 samples in bytes on a plane
|
||||
// interleaved: bps * nch
|
||||
// planar: bps
|
||||
int nch; // number of channels (redundant with chmap)
|
||||
int spf; // sub-samples per sample on each plane
|
||||
int num_planes; // number of planes
|
||||
int bps; // size of sub-samples (af_fmt_to_bytes(format))
|
||||
|
||||
double pts; // currently invalid within the filter chain
|
||||
|
||||
// --- private
|
||||
// These do not necessarily map directly to planes[]. They can have
|
||||
// different order or count. There shouldn't be more buffers than planes.
|
||||
// If allocated[n] is NULL, allocated[n+1] must also be NULL.
|
||||
struct AVBufferRef *allocated[MP_NUM_CHANNELS];
|
||||
};
|
||||
|
||||
void mp_audio_set_format(struct mp_audio *mpa, int format);
|
||||
void mp_audio_set_num_channels(struct mp_audio *mpa, int num_channels);
|
||||
void mp_audio_set_channels(struct mp_audio *mpa, const struct mp_chmap *chmap);
|
||||
void mp_audio_copy_config(struct mp_audio *dst, const struct mp_audio *src);
|
||||
bool mp_audio_config_equals(const struct mp_audio *a, const struct mp_audio *b);
|
||||
bool mp_audio_config_valid(const struct mp_audio *mpa);
|
||||
|
||||
char *mp_audio_config_to_str_buf(char *buf, size_t buf_sz, struct mp_audio *mpa);
|
||||
#define mp_audio_config_to_str(m) mp_audio_config_to_str_buf((char[64]){0}, 64, (m))
|
||||
|
||||
void mp_audio_force_interleaved_format(struct mp_audio *mpa);
|
||||
|
||||
int mp_audio_psize(struct mp_audio *mpa);
|
||||
|
||||
void mp_audio_set_null_data(struct mp_audio *mpa);
|
||||
|
||||
void mp_audio_realloc(struct mp_audio *mpa, int samples);
|
||||
void mp_audio_realloc_min(struct mp_audio *mpa, int samples);
|
||||
int mp_audio_get_allocated_size(struct mp_audio *mpa);
|
||||
|
||||
void mp_audio_fill_silence(struct mp_audio *mpa, int start, int length);
|
||||
|
||||
void mp_audio_copy(struct mp_audio *dst, int dst_offset,
|
||||
struct mp_audio *src, int src_offset, int length);
|
||||
void mp_audio_copy_attributes(struct mp_audio *dst, struct mp_audio *src);
|
||||
void mp_audio_skip_samples(struct mp_audio *data, int samples);
|
||||
void mp_audio_clip_timestamps(struct mp_audio *f, double start, double end);
|
||||
double mp_audio_end_pts(struct mp_audio *data);
|
||||
|
||||
bool mp_audio_is_writeable(struct mp_audio *data);
|
||||
int mp_audio_make_writeable(struct mp_audio *data);
|
||||
|
||||
struct AVFrame;
|
||||
struct mp_audio *mp_audio_from_avframe(struct AVFrame *avframe);
|
||||
struct AVFrame *mp_audio_to_avframe_and_unref(struct mp_audio *frame);
|
||||
int mp_audio_to_avframe(struct mp_audio *frame, struct AVFrame *avframe);
|
||||
|
||||
struct mp_aframe;
|
||||
struct mp_audio *mp_audio_from_aframe(struct mp_aframe *aframe);
|
||||
void mp_audio_config_from_aframe(struct mp_audio *dst, struct mp_aframe *src);
|
||||
struct mp_aframe *mp_audio_to_aframe(struct mp_audio *mpa);
|
||||
|
||||
struct mp_audio_pool;
|
||||
struct mp_audio_pool *mp_audio_pool_create(void *ta_parent);
|
||||
struct mp_audio *mp_audio_pool_get(struct mp_audio_pool *pool,
|
||||
const struct mp_audio *fmt, int samples);
|
||||
struct mp_audio *mp_audio_pool_new_copy(struct mp_audio_pool *pool,
|
||||
struct mp_audio *frame);
|
||||
int mp_audio_pool_make_writeable(struct mp_audio_pool *pool,
|
||||
struct mp_audio *frame);
|
||||
|
||||
#include "filter/af.h"
|
||||
|
||||
#endif
|
||||
@@ -87,9 +87,6 @@ static void copy_planes(struct mp_audio_buffer *ab,
|
||||
uint8_t **dst, int dst_offset,
|
||||
uint8_t **src, int src_offset, int length)
|
||||
{
|
||||
if (!length)
|
||||
return;
|
||||
|
||||
for (int n = 0; n < ab->num_planes; n++) {
|
||||
memmove((char *)dst[n] + dst_offset * ab->sstride,
|
||||
(char *)src[n] + src_offset * ab->sstride,
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MPLAYER_AD_H
|
||||
#define MPLAYER_AD_H
|
||||
|
||||
#include "common/codecs.h"
|
||||
#include "demux/stheader.h"
|
||||
#include "demux/demux.h"
|
||||
|
||||
#include "audio/format.h"
|
||||
#include "audio/aframe.h"
|
||||
#include "dec_audio.h"
|
||||
|
||||
struct mp_decoder_list;
|
||||
|
||||
/* interface of audio decoder drivers */
|
||||
struct ad_functions {
|
||||
const char *name;
|
||||
void (*add_decoders)(struct mp_decoder_list *list);
|
||||
int (*init)(struct dec_audio *da, const char *decoder);
|
||||
void (*uninit)(struct dec_audio *da);
|
||||
int (*control)(struct dec_audio *da, int cmd, void *arg);
|
||||
// Return whether or not the packet has been consumed.
|
||||
bool (*send_packet)(struct dec_audio *da, struct demux_packet *pkt);
|
||||
// Return whether decoding is still going on (false if EOF was reached).
|
||||
// Never returns false & *out set, but can return true with !*out.
|
||||
bool (*receive_frame)(struct dec_audio *da, struct mp_aframe **out);
|
||||
};
|
||||
|
||||
enum ad_ctrl {
|
||||
ADCTRL_RESET = 1, // flush and reset state, e.g. after seeking
|
||||
};
|
||||
|
||||
#endif /* MPLAYER_AD_H */
|
||||
+62
-94
@@ -27,32 +27,28 @@
|
||||
#include <libavutil/intreadwrite.h>
|
||||
|
||||
#include "mpv_talloc.h"
|
||||
#include "audio/aframe.h"
|
||||
#include "audio/fmt-conversion.h"
|
||||
|
||||
#include "config.h"
|
||||
#include "common/av_common.h"
|
||||
#include "common/codecs.h"
|
||||
#include "common/global.h"
|
||||
#include "common/msg.h"
|
||||
#include "demux/packet.h"
|
||||
#include "demux/stheader.h"
|
||||
#include "filters/f_decoder_wrapper.h"
|
||||
#include "filters/filter_internal.h"
|
||||
#include "options/m_config.h"
|
||||
#include "options/options.h"
|
||||
|
||||
#include "ad.h"
|
||||
#include "audio/fmt-conversion.h"
|
||||
|
||||
struct priv {
|
||||
AVCodecContext *avctx;
|
||||
AVFrame *avframe;
|
||||
struct mp_chmap force_channel_map;
|
||||
bool force_channel_map;
|
||||
uint32_t skip_samples, trim_samples;
|
||||
bool preroll_done;
|
||||
double next_pts;
|
||||
AVRational codec_timebase;
|
||||
struct lavc_state state;
|
||||
|
||||
struct mp_decoder public;
|
||||
};
|
||||
|
||||
static void uninit(struct dec_audio *da);
|
||||
|
||||
#define OPT_BASE_STRUCT struct ad_lavc_params
|
||||
struct ad_lavc_params {
|
||||
float ac3drc;
|
||||
@@ -77,25 +73,26 @@ const struct m_sub_options ad_lavc_conf = {
|
||||
},
|
||||
};
|
||||
|
||||
static bool init(struct mp_filter *da, struct mp_codec_params *codec,
|
||||
const char *decoder)
|
||||
static int init(struct dec_audio *da, const char *decoder)
|
||||
{
|
||||
struct priv *ctx = da->priv;
|
||||
struct MPOpts *mpopts = mp_get_config_group(ctx, da->global, &mp_opt_root);
|
||||
struct ad_lavc_params *opts =
|
||||
mp_get_config_group(ctx, da->global, &ad_lavc_conf);
|
||||
struct MPOpts *mpopts = da->opts;
|
||||
struct ad_lavc_params *opts = mpopts->ad_lavc_params;
|
||||
AVCodecContext *lavc_context;
|
||||
AVCodec *lavc_codec;
|
||||
struct mp_codec_params *c = da->codec;
|
||||
|
||||
ctx->codec_timebase = mp_get_codec_timebase(codec);
|
||||
struct priv *ctx = talloc_zero(NULL, struct priv);
|
||||
da->priv = ctx;
|
||||
|
||||
if (codec->force_channels)
|
||||
ctx->force_channel_map = codec->channels;
|
||||
ctx->codec_timebase = mp_get_codec_timebase(da->codec);
|
||||
|
||||
ctx->force_channel_map = c->force_channels;
|
||||
|
||||
lavc_codec = avcodec_find_decoder_by_name(decoder);
|
||||
if (!lavc_codec) {
|
||||
MP_ERR(da, "Cannot find codec '%s' in libavcodec...\n", decoder);
|
||||
return false;
|
||||
uninit(da);
|
||||
return 0;
|
||||
}
|
||||
|
||||
lavc_context = avcodec_alloc_context3(lavc_codec);
|
||||
@@ -124,9 +121,10 @@ static bool init(struct mp_filter *da, struct mp_codec_params *codec,
|
||||
|
||||
mp_set_avopts(da->log, lavc_context, opts->avopts);
|
||||
|
||||
if (mp_set_avctx_codec_headers(lavc_context, codec) < 0) {
|
||||
if (mp_set_avctx_codec_headers(lavc_context, c) < 0) {
|
||||
MP_ERR(da, "Could not set decoder parameters.\n");
|
||||
return false;
|
||||
uninit(da);
|
||||
return 0;
|
||||
}
|
||||
|
||||
mp_set_avcodec_threads(da->log, lavc_context, opts->threads);
|
||||
@@ -134,35 +132,41 @@ static bool init(struct mp_filter *da, struct mp_codec_params *codec,
|
||||
/* open it */
|
||||
if (avcodec_open2(lavc_context, lavc_codec, NULL) < 0) {
|
||||
MP_ERR(da, "Could not open codec.\n");
|
||||
return false;
|
||||
uninit(da);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ctx->next_pts = MP_NOPTS_VALUE;
|
||||
|
||||
return true;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void destroy(struct mp_filter *da)
|
||||
static void uninit(struct dec_audio *da)
|
||||
{
|
||||
struct priv *ctx = da->priv;
|
||||
if (!ctx)
|
||||
return;
|
||||
|
||||
avcodec_free_context(&ctx->avctx);
|
||||
av_frame_free(&ctx->avframe);
|
||||
}
|
||||
|
||||
static void reset(struct mp_filter *da)
|
||||
static int control(struct dec_audio *da, int cmd, void *arg)
|
||||
{
|
||||
struct priv *ctx = da->priv;
|
||||
|
||||
avcodec_flush_buffers(ctx->avctx);
|
||||
ctx->skip_samples = 0;
|
||||
ctx->trim_samples = 0;
|
||||
ctx->preroll_done = false;
|
||||
ctx->next_pts = MP_NOPTS_VALUE;
|
||||
ctx->state = (struct lavc_state){0};
|
||||
switch (cmd) {
|
||||
case ADCTRL_RESET:
|
||||
avcodec_flush_buffers(ctx->avctx);
|
||||
ctx->skip_samples = 0;
|
||||
ctx->trim_samples = 0;
|
||||
ctx->preroll_done = false;
|
||||
ctx->next_pts = MP_NOPTS_VALUE;
|
||||
return CONTROL_TRUE;
|
||||
}
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
|
||||
static int send_packet(struct mp_filter *da, struct demux_packet *mpkt)
|
||||
static bool send_packet(struct dec_audio *da, struct demux_packet *mpkt)
|
||||
{
|
||||
struct priv *priv = da->priv;
|
||||
AVCodecContext *avctx = priv->avctx;
|
||||
@@ -177,12 +181,16 @@ static int send_packet(struct mp_filter *da, struct demux_packet *mpkt)
|
||||
mp_set_av_packet(&pkt, mpkt, &priv->codec_timebase);
|
||||
|
||||
int ret = avcodec_send_packet(avctx, mpkt ? &pkt : NULL);
|
||||
|
||||
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
|
||||
return false;
|
||||
|
||||
if (ret < 0)
|
||||
MP_ERR(da, "Error decoding audio.\n");
|
||||
return ret;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int receive_frame(struct mp_filter *da, struct mp_frame *out)
|
||||
static bool receive_frame(struct dec_audio *da, struct mp_aframe **out)
|
||||
{
|
||||
struct priv *priv = da->priv;
|
||||
AVCodecContext *avctx = priv->avctx;
|
||||
@@ -192,9 +200,8 @@ static int receive_frame(struct mp_filter *da, struct mp_frame *out)
|
||||
if (ret == AVERROR_EOF) {
|
||||
// If flushing was initialized earlier and has ended now, make it start
|
||||
// over in case we get new packets at some point in the future.
|
||||
// (Dont' reset the filter itself, we want to keep other state.)
|
||||
avcodec_flush_buffers(priv->avctx);
|
||||
return ret;
|
||||
control(da, ADCTRL_RESET, NULL);
|
||||
return false;
|
||||
} else if (ret < 0 && ret != AVERROR(EAGAIN)) {
|
||||
MP_ERR(da, "Error decoding audio.\n");
|
||||
}
|
||||
@@ -205,18 +212,16 @@ static int receive_frame(struct mp_filter *da, struct mp_frame *out)
|
||||
#endif
|
||||
|
||||
if (!priv->avframe->buf[0])
|
||||
return ret;
|
||||
return true;
|
||||
|
||||
double out_pts = mp_pts_from_av(priv->avframe->pts, &priv->codec_timebase);
|
||||
|
||||
struct mp_aframe *mpframe = mp_aframe_from_avframe(priv->avframe);
|
||||
if (!mpframe) {
|
||||
MP_ERR(da, "Converting libavcodec frame to mpv frame failed.\n");
|
||||
return ret;
|
||||
}
|
||||
if (!mpframe)
|
||||
return true;
|
||||
|
||||
if (priv->force_channel_map.num)
|
||||
mp_aframe_set_chmap(mpframe, &priv->force_channel_map);
|
||||
if (priv->force_channel_map)
|
||||
mp_aframe_set_chmap(mpframe, &da->codec->channels);
|
||||
|
||||
if (out_pts == MP_NOPTS_VALUE)
|
||||
out_pts = priv->next_pts;
|
||||
@@ -252,53 +257,11 @@ static int receive_frame(struct mp_filter *da, struct mp_frame *out)
|
||||
priv->trim_samples -= trim;
|
||||
}
|
||||
|
||||
if (mp_aframe_get_size(mpframe) > 0) {
|
||||
*out = MAKE_FRAME(MP_FRAME_AUDIO, mpframe);
|
||||
} else {
|
||||
talloc_free(mpframe);
|
||||
}
|
||||
*out = mpframe;
|
||||
|
||||
av_frame_unref(priv->avframe);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void process(struct mp_filter *ad)
|
||||
{
|
||||
struct priv *priv = ad->priv;
|
||||
|
||||
lavc_process(ad, &priv->state, send_packet, receive_frame);
|
||||
}
|
||||
|
||||
static const struct mp_filter_info ad_lavc_filter = {
|
||||
.name = "ad_lavc",
|
||||
.priv_size = sizeof(struct priv),
|
||||
.process = process,
|
||||
.reset = reset,
|
||||
.destroy = destroy,
|
||||
};
|
||||
|
||||
static struct mp_decoder *create(struct mp_filter *parent,
|
||||
struct mp_codec_params *codec,
|
||||
const char *decoder)
|
||||
{
|
||||
struct mp_filter *da = mp_filter_create(parent, &ad_lavc_filter);
|
||||
if (!da)
|
||||
return NULL;
|
||||
|
||||
mp_filter_add_pin(da, MP_PIN_IN, "in");
|
||||
mp_filter_add_pin(da, MP_PIN_OUT, "out");
|
||||
|
||||
da->log = mp_log_new(da, parent->log, NULL);
|
||||
|
||||
struct priv *priv = da->priv;
|
||||
priv->public.f = da;
|
||||
|
||||
if (!init(da, codec, decoder)) {
|
||||
talloc_free(da);
|
||||
return NULL;
|
||||
}
|
||||
return &priv->public;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void add_decoders(struct mp_decoder_list *list)
|
||||
@@ -306,7 +269,12 @@ static void add_decoders(struct mp_decoder_list *list)
|
||||
mp_add_lavc_decoders(list, AVMEDIA_TYPE_AUDIO);
|
||||
}
|
||||
|
||||
const struct mp_decoder_fns ad_lavc = {
|
||||
.create = create,
|
||||
const struct ad_functions ad_lavc = {
|
||||
.name = "lavc",
|
||||
.add_decoders = add_decoders,
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.control = control,
|
||||
.send_packet = send_packet,
|
||||
.receive_frame = receive_frame,
|
||||
};
|
||||
+79
-96
@@ -24,16 +24,11 @@
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavutil/opt.h>
|
||||
|
||||
#include "audio/aframe.h"
|
||||
#include "audio/format.h"
|
||||
#include "common/av_common.h"
|
||||
#include "common/codecs.h"
|
||||
#include "config.h"
|
||||
#include "common/msg.h"
|
||||
#include "demux/packet.h"
|
||||
#include "demux/stheader.h"
|
||||
#include "filters/f_decoder_wrapper.h"
|
||||
#include "filters/filter_internal.h"
|
||||
#include "common/av_common.h"
|
||||
#include "options/options.h"
|
||||
#include "ad.h"
|
||||
|
||||
#define OUTBUF_SIZE 65536
|
||||
|
||||
@@ -48,8 +43,8 @@ struct spdifContext {
|
||||
struct mp_aframe *fmt;
|
||||
int sstride;
|
||||
struct mp_aframe_pool *pool;
|
||||
|
||||
struct mp_decoder public;
|
||||
bool got_eof;
|
||||
struct demux_packet *queued_packet;
|
||||
};
|
||||
|
||||
static int write_packet(void *p, uint8_t *buf, int buf_size)
|
||||
@@ -67,8 +62,7 @@ static int write_packet(void *p, uint8_t *buf, int buf_size)
|
||||
return buf_size;
|
||||
}
|
||||
|
||||
// (called on both filter destruction _and_ if lavf fails to init)
|
||||
static void destroy(struct mp_filter *da)
|
||||
static void uninit(struct dec_audio *da)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
AVFormatContext *lavf_ctx = spdif_ctx->lavf_ctx;
|
||||
@@ -80,11 +74,27 @@ static void destroy(struct mp_filter *da)
|
||||
av_freep(&lavf_ctx->pb->buffer);
|
||||
av_freep(&lavf_ctx->pb);
|
||||
avformat_free_context(lavf_ctx);
|
||||
talloc_free(spdif_ctx->queued_packet);
|
||||
spdif_ctx->lavf_ctx = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void determine_codec_params(struct mp_filter *da, AVPacket *pkt,
|
||||
static int init(struct dec_audio *da, const char *decoder)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = talloc_zero(NULL, struct spdifContext);
|
||||
da->priv = spdif_ctx;
|
||||
spdif_ctx->log = da->log;
|
||||
spdif_ctx->use_dts_hd = da->opts->dtshd;
|
||||
spdif_ctx->pool = mp_aframe_pool_create(spdif_ctx);
|
||||
|
||||
if (strcmp(decoder, "spdif_dts_hd") == 0)
|
||||
spdif_ctx->use_dts_hd = true;
|
||||
|
||||
spdif_ctx->codec_id = mp_codec_to_av_codec_id(da->codec->codec);
|
||||
return spdif_ctx->codec_id != AV_CODEC_ID_NONE;
|
||||
}
|
||||
|
||||
static void determine_codec_params(struct dec_audio *da, AVPacket *pkt,
|
||||
int *out_profile, int *out_rate)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
@@ -147,7 +157,7 @@ done:
|
||||
MP_WARN(da, "Failed to parse codec profile.\n");
|
||||
}
|
||||
|
||||
static int init_filter(struct mp_filter *da, AVPacket *pkt)
|
||||
static int init_filter(struct dec_audio *da, AVPacket *pkt)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
|
||||
@@ -208,21 +218,13 @@ static int init_filter(struct mp_filter *da, AVPacket *pkt)
|
||||
break;
|
||||
case AV_CODEC_ID_DTS: {
|
||||
bool is_hd = profile == FF_PROFILE_DTS_HD_HRA ||
|
||||
profile == FF_PROFILE_DTS_HD_MA ||
|
||||
profile == FF_PROFILE_DTS_HD_MA ||
|
||||
profile == FF_PROFILE_UNKNOWN;
|
||||
|
||||
// Apparently, DTS-HD over SPDIF is specified to be 7.1 (8 channels)
|
||||
// for DTS-HD MA, and stereo (2 channels) for DTS-HD HRA. The bit
|
||||
// streaming rate as well as the signaled channel count are defined
|
||||
// based on this value.
|
||||
int dts_hd_spdif_channel_count = profile == FF_PROFILE_DTS_HD_HRA ?
|
||||
2 : 8;
|
||||
if (spdif_ctx->use_dts_hd && is_hd) {
|
||||
av_dict_set_int(&format_opts, "dtshd_rate",
|
||||
dts_hd_spdif_channel_count * 96000, 0);
|
||||
av_dict_set(&format_opts, "dtshd_rate", "768000", 0); // 4*192000
|
||||
sample_format = AF_FORMAT_S_DTSHD;
|
||||
samplerate = 192000;
|
||||
num_channels = dts_hd_spdif_channel_count;
|
||||
num_channels = 2*4;
|
||||
} else {
|
||||
sample_format = AF_FORMAT_S_DTS;
|
||||
samplerate = 48000;
|
||||
@@ -269,36 +271,39 @@ static int init_filter(struct mp_filter *da, AVPacket *pkt)
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
destroy(da);
|
||||
mp_filter_internal_mark_failed(da);
|
||||
uninit(da);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void process(struct mp_filter *da)
|
||||
|
||||
static bool send_packet(struct dec_audio *da, struct demux_packet *mpkt)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
|
||||
if (!mp_pin_can_transfer_data(da->ppins[1], da->ppins[0]))
|
||||
return;
|
||||
if (spdif_ctx->queued_packet || spdif_ctx->got_eof)
|
||||
return false;
|
||||
|
||||
struct mp_frame inframe = mp_pin_out_read(da->ppins[0]);
|
||||
if (inframe.type == MP_FRAME_EOF) {
|
||||
mp_pin_in_write(da->ppins[1], inframe);
|
||||
return;
|
||||
} else if (inframe.type != MP_FRAME_PACKET) {
|
||||
if (inframe.type) {
|
||||
MP_ERR(da, "unknown frame type\n");
|
||||
mp_filter_internal_mark_failed(da);
|
||||
}
|
||||
return;
|
||||
spdif_ctx->queued_packet = mpkt ? demux_copy_packet(mpkt) : NULL;
|
||||
spdif_ctx->got_eof = !mpkt;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool receive_frame(struct dec_audio *da, struct mp_aframe **out)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
|
||||
if (spdif_ctx->got_eof) {
|
||||
spdif_ctx->got_eof = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
struct demux_packet *mpkt = inframe.data;
|
||||
struct mp_aframe *out = NULL;
|
||||
double pts = mpkt->pts;
|
||||
if (!spdif_ctx->queued_packet)
|
||||
return true;
|
||||
|
||||
double pts = spdif_ctx->queued_packet->pts;
|
||||
|
||||
AVPacket pkt;
|
||||
mp_set_av_packet(&pkt, mpkt, NULL);
|
||||
mp_set_av_packet(&pkt, spdif_ctx->queued_packet, NULL);
|
||||
pkt.pts = pkt.dts = 0;
|
||||
if (!spdif_ctx->lavf_ctx) {
|
||||
if (init_filter(da, &pkt) < 0)
|
||||
@@ -312,29 +317,39 @@ static void process(struct mp_filter *da)
|
||||
goto done;
|
||||
}
|
||||
|
||||
out = mp_aframe_new_ref(spdif_ctx->fmt);
|
||||
*out = mp_aframe_new_ref(spdif_ctx->fmt);
|
||||
int samples = spdif_ctx->out_buffer_len / spdif_ctx->sstride;
|
||||
if (mp_aframe_pool_allocate(spdif_ctx->pool, out, samples) < 0) {
|
||||
TA_FREEP(&out);
|
||||
if (mp_aframe_pool_allocate(spdif_ctx->pool, *out, samples) < 0) {
|
||||
TA_FREEP(out);
|
||||
goto done;
|
||||
}
|
||||
|
||||
uint8_t **data = mp_aframe_get_data_rw(out);
|
||||
uint8_t **data = mp_aframe_get_data_rw(*out);
|
||||
if (!data) {
|
||||
TA_FREEP(&out);
|
||||
TA_FREEP(out);
|
||||
goto done;
|
||||
}
|
||||
|
||||
memcpy(data[0], spdif_ctx->out_buffer, spdif_ctx->out_buffer_len);
|
||||
mp_aframe_set_pts(out, pts);
|
||||
mp_aframe_set_pts(*out, pts);
|
||||
|
||||
done:
|
||||
talloc_free(mpkt);
|
||||
if (out) {
|
||||
mp_pin_in_write(da->ppins[1], MAKE_FRAME(MP_FRAME_AUDIO, out));
|
||||
} else {
|
||||
mp_filter_internal_mark_failed(da);
|
||||
talloc_free(spdif_ctx->queued_packet);
|
||||
spdif_ctx->queued_packet = NULL;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int control(struct dec_audio *da, int cmd, void *arg)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
switch (cmd) {
|
||||
case ADCTRL_RESET:
|
||||
talloc_free(spdif_ctx->queued_packet);
|
||||
spdif_ctx->queued_packet = NULL;
|
||||
spdif_ctx->got_eof = false;
|
||||
return CONTROL_TRUE;
|
||||
}
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
|
||||
static const int codecs[] = {
|
||||
@@ -386,49 +401,17 @@ struct mp_decoder_list *select_spdif_codec(const char *codec, const char *pref)
|
||||
const char *suffix_name = dts_hd_allowed ? "dts_hd" : codec;
|
||||
char name[80];
|
||||
snprintf(name, sizeof(name), "spdif_%s", suffix_name);
|
||||
mp_add_decoder(list, codec, name,
|
||||
mp_add_decoder(list, "spdif", codec, name,
|
||||
"libavformat/spdifenc audio pass-through decoder");
|
||||
return list;
|
||||
}
|
||||
|
||||
static const struct mp_filter_info ad_spdif_filter = {
|
||||
.name = "ad_spdif",
|
||||
.priv_size = sizeof(struct spdifContext),
|
||||
.process = process,
|
||||
.destroy = destroy,
|
||||
};
|
||||
|
||||
static struct mp_decoder *create(struct mp_filter *parent,
|
||||
struct mp_codec_params *codec,
|
||||
const char *decoder)
|
||||
{
|
||||
struct mp_filter *da = mp_filter_create(parent, &ad_spdif_filter);
|
||||
if (!da)
|
||||
return NULL;
|
||||
|
||||
mp_filter_add_pin(da, MP_PIN_IN, "in");
|
||||
mp_filter_add_pin(da, MP_PIN_OUT, "out");
|
||||
|
||||
da->log = mp_log_new(da, parent->log, NULL);
|
||||
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
spdif_ctx->log = da->log;
|
||||
spdif_ctx->pool = mp_aframe_pool_create(spdif_ctx);
|
||||
spdif_ctx->public.f = da;
|
||||
|
||||
if (strcmp(decoder, "spdif_dts_hd") == 0)
|
||||
spdif_ctx->use_dts_hd = true;
|
||||
|
||||
spdif_ctx->codec_id = mp_codec_to_av_codec_id(codec->codec);
|
||||
|
||||
|
||||
if (spdif_ctx->codec_id == AV_CODEC_ID_NONE) {
|
||||
talloc_free(da);
|
||||
return NULL;
|
||||
}
|
||||
return &spdif_ctx->public;
|
||||
}
|
||||
|
||||
const struct mp_decoder_fns ad_spdif = {
|
||||
.create = create,
|
||||
const struct ad_functions ad_spdif = {
|
||||
.name = "spdif",
|
||||
.add_decoders = NULL,
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.control = control,
|
||||
.send_packet = send_packet,
|
||||
.receive_frame = receive_frame,
|
||||
};
|
||||
@@ -0,0 +1,290 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <math.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <libavutil/mem.h>
|
||||
|
||||
#include "demux/codec_tags.h"
|
||||
|
||||
#include "common/codecs.h"
|
||||
#include "common/msg.h"
|
||||
#include "common/recorder.h"
|
||||
#include "misc/bstr.h"
|
||||
#include "options/options.h"
|
||||
|
||||
#include "stream/stream.h"
|
||||
#include "demux/demux.h"
|
||||
|
||||
#include "demux/stheader.h"
|
||||
|
||||
#include "dec_audio.h"
|
||||
#include "ad.h"
|
||||
#include "audio/format.h"
|
||||
|
||||
extern const struct ad_functions ad_lavc;
|
||||
|
||||
// Not a real codec - specially treated.
|
||||
extern const struct ad_functions ad_spdif;
|
||||
|
||||
static const struct ad_functions * const ad_drivers[] = {
|
||||
&ad_lavc,
|
||||
NULL
|
||||
};
|
||||
|
||||
static void uninit_decoder(struct dec_audio *d_audio)
|
||||
{
|
||||
audio_reset_decoding(d_audio);
|
||||
if (d_audio->ad_driver) {
|
||||
MP_VERBOSE(d_audio, "Uninit audio decoder.\n");
|
||||
d_audio->ad_driver->uninit(d_audio);
|
||||
}
|
||||
d_audio->ad_driver = NULL;
|
||||
talloc_free(d_audio->priv);
|
||||
d_audio->priv = NULL;
|
||||
}
|
||||
|
||||
static int init_audio_codec(struct dec_audio *d_audio, const char *decoder)
|
||||
{
|
||||
if (!d_audio->ad_driver->init(d_audio, decoder)) {
|
||||
MP_VERBOSE(d_audio, "Audio decoder init failed.\n");
|
||||
d_audio->ad_driver = NULL;
|
||||
uninit_decoder(d_audio);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct mp_decoder_list *audio_decoder_list(void)
|
||||
{
|
||||
struct mp_decoder_list *list = talloc_zero(NULL, struct mp_decoder_list);
|
||||
for (int i = 0; ad_drivers[i] != NULL; i++)
|
||||
ad_drivers[i]->add_decoders(list);
|
||||
return list;
|
||||
}
|
||||
|
||||
static struct mp_decoder_list *audio_select_decoders(struct dec_audio *d_audio)
|
||||
{
|
||||
struct MPOpts *opts = d_audio->opts;
|
||||
const char *codec = d_audio->codec->codec;
|
||||
|
||||
struct mp_decoder_list *list = audio_decoder_list();
|
||||
struct mp_decoder_list *new =
|
||||
mp_select_decoders(d_audio->log, list, codec, opts->audio_decoders);
|
||||
if (d_audio->try_spdif && codec) {
|
||||
struct mp_decoder_list *spdif =
|
||||
select_spdif_codec(codec, opts->audio_spdif);
|
||||
mp_append_decoders(spdif, new);
|
||||
talloc_free(new);
|
||||
new = spdif;
|
||||
}
|
||||
talloc_free(list);
|
||||
return new;
|
||||
}
|
||||
|
||||
static const struct ad_functions *find_driver(const char *name)
|
||||
{
|
||||
for (int i = 0; ad_drivers[i] != NULL; i++) {
|
||||
if (strcmp(ad_drivers[i]->name, name) == 0)
|
||||
return ad_drivers[i];
|
||||
}
|
||||
if (strcmp(name, "spdif") == 0)
|
||||
return &ad_spdif;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int audio_init_best_codec(struct dec_audio *d_audio)
|
||||
{
|
||||
uninit_decoder(d_audio);
|
||||
assert(!d_audio->ad_driver);
|
||||
|
||||
struct mp_decoder_entry *decoder = NULL;
|
||||
struct mp_decoder_list *list = audio_select_decoders(d_audio);
|
||||
|
||||
mp_print_decoders(d_audio->log, MSGL_V, "Codec list:", list);
|
||||
|
||||
for (int n = 0; n < list->num_entries; n++) {
|
||||
struct mp_decoder_entry *sel = &list->entries[n];
|
||||
const struct ad_functions *driver = find_driver(sel->family);
|
||||
if (!driver)
|
||||
continue;
|
||||
MP_VERBOSE(d_audio, "Opening audio decoder %s\n", sel->decoder);
|
||||
d_audio->ad_driver = driver;
|
||||
if (init_audio_codec(d_audio, sel->decoder)) {
|
||||
decoder = sel;
|
||||
break;
|
||||
}
|
||||
MP_WARN(d_audio, "Audio decoder init failed for %s\n", sel->decoder);
|
||||
}
|
||||
|
||||
if (d_audio->ad_driver) {
|
||||
d_audio->decoder_desc =
|
||||
talloc_asprintf(d_audio, "%s (%s)", decoder->decoder, decoder->desc);
|
||||
MP_VERBOSE(d_audio, "Selected audio codec: %s\n", d_audio->decoder_desc);
|
||||
} else {
|
||||
MP_ERR(d_audio, "Failed to initialize an audio decoder for codec '%s'.\n",
|
||||
d_audio->codec->codec);
|
||||
}
|
||||
|
||||
talloc_free(list);
|
||||
return !!d_audio->ad_driver;
|
||||
}
|
||||
|
||||
void audio_uninit(struct dec_audio *d_audio)
|
||||
{
|
||||
if (!d_audio)
|
||||
return;
|
||||
uninit_decoder(d_audio);
|
||||
talloc_free(d_audio);
|
||||
}
|
||||
|
||||
void audio_reset_decoding(struct dec_audio *d_audio)
|
||||
{
|
||||
if (d_audio->ad_driver)
|
||||
d_audio->ad_driver->control(d_audio, ADCTRL_RESET, NULL);
|
||||
d_audio->pts = MP_NOPTS_VALUE;
|
||||
talloc_free(d_audio->current_frame);
|
||||
d_audio->current_frame = NULL;
|
||||
talloc_free(d_audio->packet);
|
||||
d_audio->packet = NULL;
|
||||
talloc_free(d_audio->new_segment);
|
||||
d_audio->new_segment = NULL;
|
||||
d_audio->start = d_audio->end = MP_NOPTS_VALUE;
|
||||
}
|
||||
|
||||
static void fix_audio_pts(struct dec_audio *da)
|
||||
{
|
||||
if (!da->current_frame)
|
||||
return;
|
||||
|
||||
double frame_pts = mp_aframe_get_pts(da->current_frame);
|
||||
if (frame_pts != MP_NOPTS_VALUE) {
|
||||
if (da->pts != MP_NOPTS_VALUE)
|
||||
MP_STATS(da, "value %f audio-pts-err", da->pts - frame_pts);
|
||||
|
||||
// Keep the interpolated timestamp if it doesn't deviate more
|
||||
// than 1 ms from the real one. (MKV rounded timestamps.)
|
||||
if (da->pts == MP_NOPTS_VALUE || fabs(da->pts - frame_pts) > 0.001)
|
||||
da->pts = frame_pts;
|
||||
}
|
||||
|
||||
if (da->pts == MP_NOPTS_VALUE && da->header->missing_timestamps)
|
||||
da->pts = 0;
|
||||
|
||||
mp_aframe_set_pts(da->current_frame, da->pts);
|
||||
|
||||
if (da->pts != MP_NOPTS_VALUE)
|
||||
da->pts += mp_aframe_duration(da->current_frame);
|
||||
}
|
||||
|
||||
static bool is_new_segment(struct dec_audio *da, struct demux_packet *p)
|
||||
{
|
||||
return p->segmented &&
|
||||
(p->start != da->start || p->end != da->end || p->codec != da->codec);
|
||||
}
|
||||
|
||||
void audio_work(struct dec_audio *da)
|
||||
{
|
||||
if (da->current_frame || !da->ad_driver)
|
||||
return;
|
||||
|
||||
if (!da->packet && !da->new_segment &&
|
||||
demux_read_packet_async(da->header, &da->packet) == 0)
|
||||
{
|
||||
da->current_state = DATA_WAIT;
|
||||
return;
|
||||
}
|
||||
|
||||
if (da->packet && is_new_segment(da, da->packet)) {
|
||||
assert(!da->new_segment);
|
||||
da->new_segment = da->packet;
|
||||
da->packet = NULL;
|
||||
}
|
||||
|
||||
if (da->ad_driver->send_packet(da, da->packet)) {
|
||||
if (da->recorder_sink)
|
||||
mp_recorder_feed_packet(da->recorder_sink, da->packet);
|
||||
|
||||
talloc_free(da->packet);
|
||||
da->packet = NULL;
|
||||
}
|
||||
|
||||
bool progress = da->ad_driver->receive_frame(da, &da->current_frame);
|
||||
|
||||
da->current_state = da->current_frame ? DATA_OK : DATA_AGAIN;
|
||||
if (!progress)
|
||||
da->current_state = DATA_EOF;
|
||||
|
||||
fix_audio_pts(da);
|
||||
|
||||
bool segment_end = da->current_state == DATA_EOF;
|
||||
|
||||
if (da->current_frame) {
|
||||
mp_aframe_clip_timestamps(da->current_frame, da->start, da->end);
|
||||
double frame_pts = mp_aframe_get_pts(da->current_frame);
|
||||
if (frame_pts != MP_NOPTS_VALUE && da->start != MP_NOPTS_VALUE)
|
||||
segment_end = frame_pts >= da->end;
|
||||
if (mp_aframe_get_size(da->current_frame) == 0) {
|
||||
talloc_free(da->current_frame);
|
||||
da->current_frame = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// If there's a new segment, start it as soon as we're drained/finished.
|
||||
if (segment_end && da->new_segment) {
|
||||
struct demux_packet *new_segment = da->new_segment;
|
||||
da->new_segment = NULL;
|
||||
|
||||
if (da->codec == new_segment->codec) {
|
||||
audio_reset_decoding(da);
|
||||
} else {
|
||||
da->codec = new_segment->codec;
|
||||
da->ad_driver->uninit(da);
|
||||
da->ad_driver = NULL;
|
||||
audio_init_best_codec(da);
|
||||
}
|
||||
|
||||
da->start = new_segment->start;
|
||||
da->end = new_segment->end;
|
||||
|
||||
da->packet = new_segment;
|
||||
da->current_state = DATA_AGAIN;
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch an audio frame decoded with audio_work(). Returns one of:
|
||||
// DATA_OK: *out_frame is set to a new image
|
||||
// DATA_WAIT: waiting for demuxer; will receive a wakeup signal
|
||||
// DATA_EOF: end of file, no more frames to be expected
|
||||
// DATA_AGAIN: dropped frame or something similar
|
||||
int audio_get_frame(struct dec_audio *da, struct mp_aframe **out_frame)
|
||||
{
|
||||
*out_frame = NULL;
|
||||
if (da->current_frame) {
|
||||
*out_frame = da->current_frame;
|
||||
da->current_frame = NULL;
|
||||
return DATA_OK;
|
||||
}
|
||||
if (da->current_state == DATA_OK)
|
||||
return DATA_AGAIN;
|
||||
return da->current_state;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MPLAYER_DEC_AUDIO_H
|
||||
#define MPLAYER_DEC_AUDIO_H
|
||||
|
||||
#include "audio/chmap.h"
|
||||
#include "audio/aframe.h"
|
||||
#include "demux/demux.h"
|
||||
#include "demux/stheader.h"
|
||||
|
||||
struct mp_decoder_list;
|
||||
|
||||
struct dec_audio {
|
||||
struct mp_log *log;
|
||||
struct MPOpts *opts;
|
||||
struct mpv_global *global;
|
||||
const struct ad_functions *ad_driver;
|
||||
struct sh_stream *header;
|
||||
struct mp_codec_params *codec;
|
||||
char *decoder_desc;
|
||||
|
||||
bool try_spdif;
|
||||
|
||||
struct mp_recorder_sink *recorder_sink;
|
||||
|
||||
// For free use by the ad_driver
|
||||
void *priv;
|
||||
|
||||
// Strictly internal (dec_audio.c).
|
||||
|
||||
double pts; // endpts of previous frame
|
||||
double start, end;
|
||||
struct demux_packet *packet;
|
||||
struct demux_packet *new_segment;
|
||||
struct mp_aframe *current_frame;
|
||||
int current_state;
|
||||
};
|
||||
|
||||
struct mp_decoder_list *audio_decoder_list(void);
|
||||
int audio_init_best_codec(struct dec_audio *d_audio);
|
||||
void audio_uninit(struct dec_audio *d_audio);
|
||||
|
||||
void audio_work(struct dec_audio *d_audio);
|
||||
int audio_get_frame(struct dec_audio *d_audio, struct mp_aframe **out_frame);
|
||||
|
||||
void audio_reset_decoding(struct dec_audio *d_audio);
|
||||
|
||||
// ad_spdif.c
|
||||
struct mp_decoder_list *select_spdif_codec(const char *codec, const char *pref);
|
||||
|
||||
#endif /* MPLAYER_DEC_AUDIO_H */
|
||||
@@ -0,0 +1,821 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/global.h"
|
||||
|
||||
#include "options/m_option.h"
|
||||
#include "options/m_config.h"
|
||||
|
||||
#include "audio/audio_buffer.h"
|
||||
#include "af.h"
|
||||
|
||||
// Static list of filters
|
||||
extern const struct af_info af_info_format;
|
||||
extern const struct af_info af_info_lavcac3enc;
|
||||
extern const struct af_info af_info_lavrresample;
|
||||
extern const struct af_info af_info_scaletempo;
|
||||
extern const struct af_info af_info_lavfi;
|
||||
extern const struct af_info af_info_lavfi_bridge;
|
||||
extern const struct af_info af_info_rubberband;
|
||||
|
||||
static const struct af_info *const filter_list[] = {
|
||||
&af_info_format,
|
||||
&af_info_lavcac3enc,
|
||||
&af_info_lavrresample,
|
||||
#if HAVE_RUBBERBAND
|
||||
&af_info_rubberband,
|
||||
#endif
|
||||
&af_info_scaletempo,
|
||||
&af_info_lavfi,
|
||||
&af_info_lavfi_bridge,
|
||||
NULL
|
||||
};
|
||||
|
||||
static bool get_desc(struct m_obj_desc *dst, int index)
|
||||
{
|
||||
if (index >= MP_ARRAY_SIZE(filter_list) - 1)
|
||||
return false;
|
||||
const struct af_info *af = filter_list[index];
|
||||
*dst = (struct m_obj_desc) {
|
||||
.name = af->name,
|
||||
.description = af->info,
|
||||
.priv_size = af->priv_size,
|
||||
.priv_defaults = af->priv_defaults,
|
||||
.options = af->options,
|
||||
.p = af,
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
const struct m_obj_list af_obj_list = {
|
||||
.get_desc = get_desc,
|
||||
.description = "audio filters",
|
||||
.allow_disable_entries = true,
|
||||
.allow_unknown_entries = true,
|
||||
.aliases = {
|
||||
{"force", "format"},
|
||||
{0}
|
||||
},
|
||||
};
|
||||
|
||||
static void af_forget_frames(struct af_instance *af)
|
||||
{
|
||||
for (int n = 0; n < af->num_out_queued; n++)
|
||||
talloc_free(af->out_queued[n]);
|
||||
af->num_out_queued = 0;
|
||||
}
|
||||
|
||||
static void af_chain_forget_frames(struct af_stream *s)
|
||||
{
|
||||
for (struct af_instance *cur = s->first; cur; cur = cur->next)
|
||||
af_forget_frames(cur);
|
||||
}
|
||||
|
||||
static void af_copy_unset_fields(struct mp_audio *dst, struct mp_audio *src)
|
||||
{
|
||||
if (dst->format == AF_FORMAT_UNKNOWN)
|
||||
mp_audio_set_format(dst, src->format);
|
||||
if (dst->nch == 0)
|
||||
mp_audio_set_channels(dst, &src->channels);
|
||||
if (dst->rate == 0)
|
||||
dst->rate = src->rate;
|
||||
}
|
||||
|
||||
static int input_control(struct af_instance* af, int cmd, void* arg)
|
||||
{
|
||||
switch (cmd) {
|
||||
case AF_CONTROL_REINIT:
|
||||
assert(arg == &((struct af_stream *)af->priv)->input);
|
||||
return AF_OK;
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static int output_control(struct af_instance* af, int cmd, void* arg)
|
||||
{
|
||||
struct af_stream *s = af->priv;
|
||||
struct mp_audio *output = &s->output;
|
||||
struct mp_audio *filter_output = &s->filter_output;
|
||||
|
||||
switch (cmd) {
|
||||
case AF_CONTROL_REINIT: {
|
||||
struct mp_audio *in = arg;
|
||||
struct mp_audio orig_in = *in;
|
||||
|
||||
*filter_output = *output;
|
||||
af_copy_unset_fields(filter_output, in);
|
||||
*in = *filter_output;
|
||||
return mp_audio_config_equals(in, &orig_in) ? AF_OK : AF_FALSE;
|
||||
}
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static int dummy_filter(struct af_instance *af, struct mp_audio *frame)
|
||||
{
|
||||
af_add_output_frame(af, frame);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Function for creating a new filter of type name.The name may
|
||||
contain the commandline parameters for the filter */
|
||||
static struct af_instance *af_create(struct af_stream *s, char *name,
|
||||
char **args)
|
||||
{
|
||||
const char *lavfi_name = NULL;
|
||||
char **lavfi_args = NULL;
|
||||
struct m_obj_desc desc;
|
||||
if (!m_obj_list_find(&desc, &af_obj_list, bstr0(name))) {
|
||||
if (!m_obj_list_find(&desc, &af_obj_list, bstr0("lavfi-bridge"))) {
|
||||
MP_ERR(s, "Couldn't find audio filter '%s'.\n", name);
|
||||
return NULL;
|
||||
}
|
||||
lavfi_name = name;
|
||||
lavfi_args = args;
|
||||
args = NULL;
|
||||
if (strncmp(lavfi_name, "lavfi-", 6) == 0)
|
||||
lavfi_name += 6;
|
||||
}
|
||||
MP_VERBOSE(s, "Adding filter %s \n", name);
|
||||
|
||||
struct af_instance *af = talloc_zero(NULL, struct af_instance);
|
||||
*af = (struct af_instance) {
|
||||
.full_name = talloc_strdup(af, name),
|
||||
.info = desc.p,
|
||||
.data = talloc_zero(af, struct mp_audio),
|
||||
.log = mp_log_new(af, s->log, name),
|
||||
.opts = s->opts,
|
||||
.global = s->global,
|
||||
.out_pool = mp_audio_pool_create(af),
|
||||
};
|
||||
struct m_config *config =
|
||||
m_config_from_obj_desc_and_args(af, s->log, NULL, &desc,
|
||||
name, s->opts->af_defs, args);
|
||||
if (!config)
|
||||
goto error;
|
||||
if (lavfi_name) {
|
||||
// Pass the filter arguments as proper sub-options to the bridge filter.
|
||||
struct m_config_option *name_opt = m_config_get_co(config, bstr0("name"));
|
||||
assert(name_opt);
|
||||
assert(name_opt->opt->type == &m_option_type_string);
|
||||
if (m_config_set_option_raw(config, name_opt, &lavfi_name, 0) < 0)
|
||||
goto error;
|
||||
struct m_config_option *opts = m_config_get_co(config, bstr0("opts"));
|
||||
assert(opts);
|
||||
assert(opts->opt->type == &m_option_type_keyvalue_list);
|
||||
if (m_config_set_option_raw(config, opts, &lavfi_args, 0) < 0)
|
||||
goto error;
|
||||
af->full_name = talloc_asprintf(af, "%s (lavfi)", af->full_name);
|
||||
}
|
||||
af->priv = config->optstruct;
|
||||
|
||||
// Initialize the new filter
|
||||
if (af->info->open(af) != AF_OK)
|
||||
goto error;
|
||||
|
||||
return af;
|
||||
|
||||
error:
|
||||
MP_ERR(s, "Couldn't create or open audio filter '%s'\n", name);
|
||||
talloc_free(af);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Create and insert a new filter of type name before the filter in the
|
||||
argument. This function can be called during runtime, the return
|
||||
value is the new filter */
|
||||
static struct af_instance *af_prepend(struct af_stream *s,
|
||||
struct af_instance *af,
|
||||
char *name, char **args)
|
||||
{
|
||||
if (!af)
|
||||
af = s->last;
|
||||
if (af == s->first)
|
||||
af = s->first->next;
|
||||
// Create the new filter and make sure it is OK
|
||||
struct af_instance *new = af_create(s, name, args);
|
||||
if (!new)
|
||||
return NULL;
|
||||
// Update pointers
|
||||
new->next = af;
|
||||
new->prev = af->prev;
|
||||
af->prev = new;
|
||||
new->prev->next = new;
|
||||
return new;
|
||||
}
|
||||
|
||||
// Uninit and remove the filter "af"
|
||||
static void af_remove(struct af_stream *s, struct af_instance *af)
|
||||
{
|
||||
if (!af)
|
||||
return;
|
||||
|
||||
if (af == s->first || af == s->last)
|
||||
return;
|
||||
|
||||
// Print friendly message
|
||||
MP_VERBOSE(s, "Removing filter %s \n", af->info->name);
|
||||
|
||||
// Detach pointers
|
||||
af->prev->next = af->next;
|
||||
af->next->prev = af->prev;
|
||||
|
||||
if (af->uninit)
|
||||
af->uninit(af);
|
||||
af_forget_frames(af);
|
||||
talloc_free(af);
|
||||
}
|
||||
|
||||
static void remove_auto_inserted_filters(struct af_stream *s)
|
||||
{
|
||||
repeat:
|
||||
for (struct af_instance *af = s->first; af; af = af->next) {
|
||||
if (af->auto_inserted) {
|
||||
af_remove(s, af);
|
||||
goto repeat;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void af_print_filter_chain(struct af_stream *s, struct af_instance *at,
|
||||
int msg_level)
|
||||
{
|
||||
MP_MSG(s, msg_level, "Audio filter chain:\n");
|
||||
|
||||
struct af_instance *af = s->first;
|
||||
while (af) {
|
||||
char b[128] = {0};
|
||||
mp_snprintf_cat(b, sizeof(b), " [%s] ", af->full_name);
|
||||
if (af->label)
|
||||
mp_snprintf_cat(b, sizeof(b), "\"%s\" ", af->label);
|
||||
if (af->data)
|
||||
mp_snprintf_cat(b, sizeof(b), "%s", mp_audio_config_to_str(af->data));
|
||||
if (af->auto_inserted)
|
||||
mp_snprintf_cat(b, sizeof(b), " [a]");
|
||||
if (af == at)
|
||||
mp_snprintf_cat(b, sizeof(b), " <-");
|
||||
MP_MSG(s, msg_level, "%s\n", b);
|
||||
|
||||
af = af->next;
|
||||
}
|
||||
|
||||
MP_MSG(s, msg_level, " [ao] %s\n", mp_audio_config_to_str(&s->output));
|
||||
}
|
||||
|
||||
static void reset_formats(struct af_stream *s)
|
||||
{
|
||||
struct mp_audio none = {0};
|
||||
for (struct af_instance *af = s->first; af; af = af->next) {
|
||||
if (af != s->first && af != s->last)
|
||||
mp_audio_copy_config(af->data, &none);
|
||||
}
|
||||
}
|
||||
|
||||
static int filter_reinit(struct af_instance *af)
|
||||
{
|
||||
struct af_instance *prev = af->prev;
|
||||
assert(prev);
|
||||
|
||||
// Check if this is the first filter
|
||||
struct mp_audio in = *prev->data;
|
||||
// Reset just in case...
|
||||
mp_audio_set_null_data(&in);
|
||||
|
||||
if (!mp_audio_config_valid(&in))
|
||||
return AF_ERROR;
|
||||
|
||||
af->fmt_in = in;
|
||||
int rv = af->control(af, AF_CONTROL_REINIT, &in);
|
||||
if (rv == AF_OK && !mp_audio_config_equals(&in, prev->data))
|
||||
rv = AF_FALSE; // conversion filter needed
|
||||
if (rv == AF_FALSE)
|
||||
af->fmt_in = in;
|
||||
|
||||
if (rv == AF_OK) {
|
||||
if (!mp_audio_config_valid(af->data))
|
||||
return AF_ERROR;
|
||||
af->fmt_out = *af->data;
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static int filter_reinit_with_conversion(struct af_stream *s, struct af_instance *af)
|
||||
{
|
||||
int rv = filter_reinit(af);
|
||||
|
||||
// Conversion filter is needed
|
||||
if (rv == AF_FALSE) {
|
||||
// First try if we can change the output format of the previous
|
||||
// filter to the input format the current filter is expecting.
|
||||
struct mp_audio in = af->fmt_in;
|
||||
if (af->prev != s->first && !mp_audio_config_equals(af->prev->data, &in)) {
|
||||
// This should have been successful (because it succeeded
|
||||
// before), even if just reverting to the old output format.
|
||||
mp_audio_copy_config(af->prev->data, &in);
|
||||
rv = filter_reinit(af->prev);
|
||||
if (rv != AF_OK)
|
||||
return rv;
|
||||
}
|
||||
if (!mp_audio_config_equals(af->prev->data, &in)) {
|
||||
// Retry with conversion filter added.
|
||||
struct af_instance *new =
|
||||
af_prepend(s, af, "lavrresample", NULL);
|
||||
if (!new)
|
||||
return AF_ERROR;
|
||||
new->auto_inserted = true;
|
||||
mp_audio_copy_config(new->data, &in);
|
||||
rv = filter_reinit(new);
|
||||
if (rv != AF_OK)
|
||||
af_remove(s, new);
|
||||
}
|
||||
if (rv == AF_OK)
|
||||
rv = filter_reinit(af);
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static int af_find_output_conversion(struct af_stream *s, struct mp_audio *cfg)
|
||||
{
|
||||
assert(mp_audio_config_valid(&s->output));
|
||||
assert(s->initialized > 0);
|
||||
|
||||
if (mp_chmap_equals_reordered(&s->input.channels, &s->output.channels))
|
||||
return AF_ERROR;
|
||||
|
||||
// Heuristic to detect point of conversion. If it looks like something
|
||||
// more complicated is going on, better bail out.
|
||||
// We expect that the last filter converts channels.
|
||||
struct af_instance *conv = s->last->prev;
|
||||
if (!conv->auto_inserted)
|
||||
return AF_ERROR;
|
||||
if (!(mp_chmap_equals_reordered(&conv->fmt_in.channels, &s->input.channels) &&
|
||||
mp_chmap_equals_reordered(&conv->fmt_out.channels, &s->output.channels)))
|
||||
return AF_ERROR;
|
||||
// Also, should be the only one which does auto conversion.
|
||||
for (struct af_instance *af = s->first->next; af != s->last; af = af->next)
|
||||
{
|
||||
if (af != conv && af->auto_inserted &&
|
||||
!mp_chmap_equals_reordered(&af->fmt_in.channels, &af->fmt_out.channels))
|
||||
return AF_ERROR;
|
||||
}
|
||||
// And not if it's the only filter.
|
||||
if (conv->prev == s->first && conv->next == s->last)
|
||||
return AF_ERROR;
|
||||
|
||||
*cfg = s->output;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
// Return AF_OK on success or AF_ERROR on failure.
|
||||
static int af_do_reinit(struct af_stream *s, bool second_pass)
|
||||
{
|
||||
struct mp_audio convert_early = {0};
|
||||
if (second_pass) {
|
||||
// If a channel conversion happens, and it is done by an auto-inserted
|
||||
// filter, then insert a filter to convert it early. Otherwise, do
|
||||
// nothing and return immediately.
|
||||
if (af_find_output_conversion(s, &convert_early) != AF_OK)
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
remove_auto_inserted_filters(s);
|
||||
af_chain_forget_frames(s);
|
||||
reset_formats(s);
|
||||
s->first->fmt_in = s->first->fmt_out = s->input;
|
||||
|
||||
if (mp_audio_config_valid(&convert_early)) {
|
||||
struct af_instance *new = af_prepend(s, s->first, "lavrresample", NULL);
|
||||
if (!new)
|
||||
return AF_ERROR;
|
||||
new->auto_inserted = true;
|
||||
mp_audio_copy_config(new->data, &convert_early);
|
||||
int rv = filter_reinit(new);
|
||||
if (rv != AF_DETACH && rv != AF_OK)
|
||||
return AF_ERROR;
|
||||
MP_VERBOSE(s, "Moving up output conversion.\n");
|
||||
}
|
||||
|
||||
// Start with the second filter, as the first filter is the special input
|
||||
// filter which needs no initialization.
|
||||
struct af_instance *af = s->first->next;
|
||||
while (af) {
|
||||
int rv = filter_reinit_with_conversion(s, af);
|
||||
|
||||
switch (rv) {
|
||||
case AF_OK:
|
||||
af = af->next;
|
||||
break;
|
||||
case AF_FALSE: {
|
||||
// If the format conversion is (probably) caused by spdif, then
|
||||
// (as a feature) drop the filter, instead of failing hard.
|
||||
int fmt_in1 = af->prev->data->format;
|
||||
int fmt_in2 = af->fmt_in.format;
|
||||
if (af_fmt_is_valid(fmt_in1) && af_fmt_is_valid(fmt_in2)) {
|
||||
bool spd1 = af_fmt_is_spdif(fmt_in1);
|
||||
bool spd2 = af_fmt_is_spdif(fmt_in2);
|
||||
if (spd1 != spd2 && af->next) {
|
||||
MP_WARN(af, "Filter %s apparently cannot be used due to "
|
||||
"spdif passthrough - removing it.\n",
|
||||
af->info->name);
|
||||
struct af_instance *aft = af->prev;
|
||||
af_remove(s, af);
|
||||
af = aft->next;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
goto negotiate_error;
|
||||
}
|
||||
case AF_DETACH: { // Filter is redundant and wants to be unloaded
|
||||
struct af_instance *aft = af->prev; // never NULL
|
||||
af_remove(s, af);
|
||||
af = aft->next;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
MP_ERR(s, "Reinitialization did not work, "
|
||||
"audio filter '%s' returned error code %i\n",
|
||||
af->info->name, rv);
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
/* Set previously unset fields in s->output to those of the filter chain
|
||||
* output. This is used to make the output format fixed, and even if you
|
||||
* insert new filters or change the input format, the output format won't
|
||||
* change. (Audio outputs generally can't change format at runtime.) */
|
||||
af_copy_unset_fields(&s->output, &s->filter_output);
|
||||
if (mp_audio_config_equals(&s->output, &s->filter_output)) {
|
||||
s->initialized = 1;
|
||||
af_print_filter_chain(s, NULL, MSGL_V);
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
goto error;
|
||||
|
||||
negotiate_error:
|
||||
MP_ERR(s, "Unable to convert audio input format to output format.\n");
|
||||
error:
|
||||
s->initialized = -1;
|
||||
af_print_filter_chain(s, af, MSGL_ERR);
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
static int af_reinit(struct af_stream *s)
|
||||
{
|
||||
int r = af_do_reinit(s, false);
|
||||
if (r == AF_OK && mp_audio_config_valid(&s->output)) {
|
||||
r = af_do_reinit(s, true);
|
||||
if (r != AF_OK) {
|
||||
MP_ERR(s, "Failed second pass filter negotiation.\n");
|
||||
r = af_do_reinit(s, false);
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
// Uninit and remove all filters
|
||||
void af_uninit(struct af_stream *s)
|
||||
{
|
||||
while (s->first->next && s->first->next != s->last)
|
||||
af_remove(s, s->first->next);
|
||||
af_chain_forget_frames(s);
|
||||
s->initialized = 0;
|
||||
}
|
||||
|
||||
struct af_stream *af_new(struct mpv_global *global)
|
||||
{
|
||||
struct af_stream *s = talloc_zero(NULL, struct af_stream);
|
||||
s->log = mp_log_new(s, global->log, "!af");
|
||||
|
||||
static const struct af_info in = { .name = "in" };
|
||||
s->first = talloc(s, struct af_instance);
|
||||
*s->first = (struct af_instance) {
|
||||
.full_name = "in",
|
||||
.info = &in,
|
||||
.log = s->log,
|
||||
.control = input_control,
|
||||
.filter_frame = dummy_filter,
|
||||
.priv = s,
|
||||
.data = &s->input,
|
||||
};
|
||||
|
||||
static const struct af_info out = { .name = "out" };
|
||||
s->last = talloc(s, struct af_instance);
|
||||
*s->last = (struct af_instance) {
|
||||
.full_name = "out",
|
||||
.info = &out,
|
||||
.log = s->log,
|
||||
.control = output_control,
|
||||
.filter_frame = dummy_filter,
|
||||
.priv = s,
|
||||
.data = &s->filter_output,
|
||||
};
|
||||
|
||||
s->first->next = s->last;
|
||||
s->last->prev = s->first;
|
||||
s->opts = global->opts;
|
||||
s->global = global;
|
||||
return s;
|
||||
}
|
||||
|
||||
void af_destroy(struct af_stream *s)
|
||||
{
|
||||
af_uninit(s);
|
||||
talloc_free(s);
|
||||
}
|
||||
|
||||
/* Initialize the stream "s". This function creates a new filter list
|
||||
if necessary according to the values set in input and output. Input
|
||||
and output should contain the format of the current movie and the
|
||||
format of the preferred output respectively. The function is
|
||||
reentrant i.e. if called with an already initialized stream the
|
||||
stream will be reinitialized.
|
||||
If one of the preferred output parameters is 0 the one that needs
|
||||
no conversion is used (i.e. the output format in the last filter).
|
||||
The return value is 0 if success and -1 if failure */
|
||||
int af_init(struct af_stream *s)
|
||||
{
|
||||
// Precaution in case caller is misbehaving
|
||||
mp_audio_set_null_data(&s->input);
|
||||
mp_audio_set_null_data(&s->output);
|
||||
|
||||
// Check if this is the first call
|
||||
if (s->first->next == s->last) {
|
||||
// Add all filters in the list (if there are any)
|
||||
struct m_obj_settings *list = s->opts->af_settings;
|
||||
for (int i = 0; list && list[i].name; i++) {
|
||||
if (!list[i].enabled)
|
||||
continue;
|
||||
struct af_instance *af =
|
||||
af_prepend(s, s->last, list[i].name, list[i].attribs);
|
||||
if (!af) {
|
||||
af_uninit(s);
|
||||
s->initialized = -1;
|
||||
return -1;
|
||||
}
|
||||
af->label = talloc_strdup(af, list[i].label);
|
||||
}
|
||||
}
|
||||
|
||||
if (af_reinit(s) != AF_OK) {
|
||||
// Something is stuffed audio out will not work
|
||||
MP_ERR(s, "Could not create audio filter chain.\n");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Add filter during execution. This function adds the filter "name"
|
||||
to the stream s. The filter will be inserted somewhere nice in the
|
||||
list of filters. The return value is a pointer to the new filter,
|
||||
If the filter couldn't be added the return value is NULL. */
|
||||
struct af_instance *af_add(struct af_stream *s, char *name, char *label,
|
||||
char **args)
|
||||
{
|
||||
assert(label);
|
||||
|
||||
if (af_find_by_label(s, label))
|
||||
return NULL;
|
||||
|
||||
struct af_instance *new = af_prepend(s, s->last, name, args);
|
||||
if (!new)
|
||||
return NULL;
|
||||
new->label = talloc_strdup(new, label);
|
||||
|
||||
// Reinitialize the filter list
|
||||
if (af_reinit(s) != AF_OK) {
|
||||
af_remove_by_label(s, label);
|
||||
return NULL;
|
||||
}
|
||||
return af_find_by_label(s, label);
|
||||
}
|
||||
|
||||
struct af_instance *af_find_by_label(struct af_stream *s, char *label)
|
||||
{
|
||||
for (struct af_instance *af = s->first; af; af = af->next) {
|
||||
if (af->label && strcmp(af->label, label) == 0)
|
||||
return af;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Remove the first filter that matches this name. Return number of filters
|
||||
* removed (0, 1), or a negative error code if reinit after removing failed.
|
||||
*/
|
||||
int af_remove_by_label(struct af_stream *s, char *label)
|
||||
{
|
||||
struct af_instance *af = af_find_by_label(s, label);
|
||||
if (!af)
|
||||
return 0;
|
||||
af_remove(s, af);
|
||||
if (af_reinit(s) != AF_OK) {
|
||||
af_uninit(s);
|
||||
af_init(s);
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Calculate the total delay [seconds of output] caused by the filters */
|
||||
double af_calc_delay(struct af_stream *s)
|
||||
{
|
||||
struct af_instance *af = s->first;
|
||||
double delay = 0.0;
|
||||
while (af) {
|
||||
delay += af->delay;
|
||||
for (int n = 0; n < af->num_out_queued; n++)
|
||||
delay += af->out_queued[n]->samples / (double)af->data->rate;
|
||||
af = af->next;
|
||||
}
|
||||
return delay;
|
||||
}
|
||||
|
||||
/* Send control to all filters, starting with the last until one accepts the
|
||||
* command with AF_OK. Return the accepting filter. */
|
||||
struct af_instance *af_control_any_rev(struct af_stream *s, int cmd, void *arg)
|
||||
{
|
||||
int res = AF_UNKNOWN;
|
||||
struct af_instance *filt = s->last;
|
||||
while (filt) {
|
||||
res = filt->control(filt, cmd, arg);
|
||||
if (res == AF_OK)
|
||||
return filt;
|
||||
filt = filt->prev;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Send control to all filters. Never stop, even if a filter returns AF_OK. */
|
||||
void af_control_all(struct af_stream *s, int cmd, void *arg)
|
||||
{
|
||||
for (struct af_instance *af = s->first; af; af = af->next)
|
||||
af->control(af, cmd, arg);
|
||||
}
|
||||
|
||||
int af_control_by_label(struct af_stream *s, int cmd, void *arg, bstr label)
|
||||
{
|
||||
char *label_str = bstrdup0(NULL, label);
|
||||
struct af_instance *cur = af_find_by_label(s, label_str);
|
||||
talloc_free(label_str);
|
||||
if (cur) {
|
||||
return cur->control ? cur->control(cur, cmd, arg) : CONTROL_NA;
|
||||
} else {
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
int af_send_command(struct af_stream *s, char *label, char *cmd, char *arg)
|
||||
{
|
||||
char *args[2] = {cmd, arg};
|
||||
if (strcmp(label, "all") == 0) {
|
||||
af_control_all(s, AF_CONTROL_COMMAND, args);
|
||||
return 0;
|
||||
} else {
|
||||
return af_control_by_label(s, AF_CONTROL_COMMAND, args, bstr0(label));
|
||||
}
|
||||
}
|
||||
|
||||
// Used by filters to add a filtered frame to the output queue.
|
||||
// Ownership of frame is transferred from caller to the filter chain.
|
||||
void af_add_output_frame(struct af_instance *af, struct mp_audio *frame)
|
||||
{
|
||||
if (frame) {
|
||||
assert(mp_audio_config_equals(&af->fmt_out, frame));
|
||||
MP_TARRAY_APPEND(af, af->out_queued, af->num_out_queued, frame);
|
||||
}
|
||||
}
|
||||
|
||||
static bool af_has_output_frame(struct af_instance *af)
|
||||
{
|
||||
if (!af->num_out_queued && af->filter_out) {
|
||||
if (af->filter_out(af) < 0)
|
||||
MP_ERR(af, "Error filtering frame.\n");
|
||||
}
|
||||
return af->num_out_queued > 0;
|
||||
}
|
||||
|
||||
static struct mp_audio *af_dequeue_output_frame(struct af_instance *af)
|
||||
{
|
||||
struct mp_audio *res = NULL;
|
||||
if (af_has_output_frame(af)) {
|
||||
res = af->out_queued[0];
|
||||
MP_TARRAY_REMOVE_AT(af->out_queued, af->num_out_queued, 0);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static void read_remaining(struct af_instance *af)
|
||||
{
|
||||
int num_frames;
|
||||
do {
|
||||
num_frames = af->num_out_queued;
|
||||
if (!af->filter_out || af->filter_out(af) < 0)
|
||||
break;
|
||||
} while (num_frames != af->num_out_queued);
|
||||
}
|
||||
|
||||
static int af_do_filter(struct af_instance *af, struct mp_audio *frame)
|
||||
{
|
||||
if (frame)
|
||||
assert(mp_audio_config_equals(&af->fmt_in, frame));
|
||||
int r = af->filter_frame(af, frame);
|
||||
if (r < 0)
|
||||
MP_ERR(af, "Error filtering frame.\n");
|
||||
return r;
|
||||
}
|
||||
|
||||
// Input a frame into the filter chain. Ownership of frame is transferred.
|
||||
// Return >= 0 on success, < 0 on failure (even if output frames were produced)
|
||||
int af_filter_frame(struct af_stream *s, struct mp_audio *frame)
|
||||
{
|
||||
assert(frame);
|
||||
if (s->initialized < 1) {
|
||||
talloc_free(frame);
|
||||
return -1;
|
||||
}
|
||||
return af_do_filter(s->first, frame);
|
||||
}
|
||||
|
||||
// Output the next queued frame (if any) from the full filter chain.
|
||||
// The frame can be retrieved with af_read_output_frame().
|
||||
// eof: if set, assume there's no more input i.e. af_filter_frame() will
|
||||
// not be called (until reset) - flush all internally delayed frames
|
||||
// returns: -1: error, 0: no output, 1: output available
|
||||
int af_output_frame(struct af_stream *s, bool eof)
|
||||
{
|
||||
if (s->last->num_out_queued)
|
||||
return 1;
|
||||
if (s->initialized < 1)
|
||||
return -1;
|
||||
while (1) {
|
||||
struct af_instance *last = NULL;
|
||||
for (struct af_instance * cur = s->first; cur; cur = cur->next) {
|
||||
// Flush remaining frames on EOF, but do that only if the previous
|
||||
// filters have been flushed (i.e. they have no more output).
|
||||
if (eof && !last) {
|
||||
read_remaining(cur);
|
||||
int r = af_do_filter(cur, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
if (af_has_output_frame(cur))
|
||||
last = cur;
|
||||
}
|
||||
if (!last)
|
||||
return 0;
|
||||
if (!last->next)
|
||||
return 1;
|
||||
int r = af_do_filter(last->next, af_dequeue_output_frame(last));
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
struct mp_audio *af_read_output_frame(struct af_stream *s)
|
||||
{
|
||||
if (!s->last->num_out_queued)
|
||||
af_output_frame(s, false);
|
||||
return af_dequeue_output_frame(s->last);
|
||||
}
|
||||
|
||||
void af_unread_output_frame(struct af_stream *s, struct mp_audio *frame)
|
||||
{
|
||||
struct af_instance *af = s->last;
|
||||
MP_TARRAY_INSERT_AT(af, af->out_queued, af->num_out_queued, 0, frame);
|
||||
}
|
||||
|
||||
// Make sure the caller can change data referenced by the frame.
|
||||
// Return negative error code on failure (i.e. you can't write).
|
||||
int af_make_writeable(struct af_instance *af, struct mp_audio *frame)
|
||||
{
|
||||
return mp_audio_pool_make_writeable(af->out_pool, frame);
|
||||
}
|
||||
|
||||
void af_seek_reset(struct af_stream *s)
|
||||
{
|
||||
af_control_all(s, AF_CONTROL_RESET, NULL);
|
||||
af_chain_forget_frames(s);
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MPLAYER_AF_H
|
||||
#define MPLAYER_AF_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "config.h"
|
||||
#if !(HAVE_LIBAF && HAVE_GPL)
|
||||
#error "libaf/GPL disabled"
|
||||
#endif
|
||||
|
||||
#include "options/options.h"
|
||||
#include "audio/format.h"
|
||||
#include "audio/chmap.h"
|
||||
#include "audio/audio.h"
|
||||
#include "common/msg.h"
|
||||
#include "common/common.h"
|
||||
|
||||
struct af_instance;
|
||||
struct mpv_global;
|
||||
|
||||
// Number of channels
|
||||
#define AF_NCH MP_NUM_CHANNELS
|
||||
|
||||
// Flags for af->filter()
|
||||
#define AF_FILTER_FLAG_EOF 1
|
||||
|
||||
/* Audio filter information not specific for current instance, but for
|
||||
a specific filter */
|
||||
struct af_info {
|
||||
const char *info;
|
||||
const char *name;
|
||||
int (*open)(struct af_instance *vf);
|
||||
int priv_size;
|
||||
const void *priv_defaults;
|
||||
const struct m_option *options;
|
||||
};
|
||||
|
||||
// Linked list of audio filters
|
||||
struct af_instance {
|
||||
const struct af_info *info;
|
||||
char *full_name;
|
||||
struct mp_log *log;
|
||||
struct MPOpts *opts;
|
||||
struct mpv_global *global;
|
||||
int (*control)(struct af_instance *af, int cmd, void *arg);
|
||||
void (*uninit)(struct af_instance *af);
|
||||
/* Feed a frame. The frame is NULL if EOF was reached, and the filter
|
||||
* should drain all remaining buffered data.
|
||||
* Use af_add_output_frame() to output data. The optional filter_out
|
||||
* callback can be set to produce output frames gradually.
|
||||
*/
|
||||
int (*filter_frame)(struct af_instance *af, struct mp_audio *frame);
|
||||
int (*filter_out)(struct af_instance *af);
|
||||
void *priv;
|
||||
struct mp_audio *data; // configuration and buffer for outgoing data stream
|
||||
|
||||
struct af_instance *next;
|
||||
struct af_instance *prev;
|
||||
double delay; /* Delay caused by the filter, in seconds of audio consumed
|
||||
* without corresponding output */
|
||||
bool auto_inserted; // inserted by af.c, such as conversion filters
|
||||
char *label;
|
||||
|
||||
struct mp_audio fmt_in, fmt_out;
|
||||
|
||||
struct mp_audio **out_queued;
|
||||
int num_out_queued;
|
||||
|
||||
struct mp_audio_pool *out_pool;
|
||||
};
|
||||
|
||||
// Current audio stream
|
||||
struct af_stream {
|
||||
int initialized; // 0: no, 1: yes, -1: attempted to, but failed
|
||||
|
||||
// The first and last filter in the list
|
||||
struct af_instance *first;
|
||||
struct af_instance *last;
|
||||
// The user sets the input format (what the decoder outputs), and sets some
|
||||
// or all fields in output to the output format the AO accepts.
|
||||
struct mp_audio input;
|
||||
struct mp_audio output;
|
||||
struct mp_audio filter_output;
|
||||
|
||||
struct mp_log *log;
|
||||
struct MPOpts *opts;
|
||||
struct mpv_global *global;
|
||||
};
|
||||
|
||||
// Return values
|
||||
#define AF_DETACH (CONTROL_OK + 1)
|
||||
#define AF_OK CONTROL_OK
|
||||
#define AF_TRUE CONTROL_TRUE
|
||||
#define AF_FALSE CONTROL_FALSE
|
||||
#define AF_UNKNOWN CONTROL_UNKNOWN
|
||||
#define AF_ERROR CONTROL_ERROR
|
||||
|
||||
// Parameters for af_control_*
|
||||
enum af_control {
|
||||
AF_CONTROL_REINIT = 1,
|
||||
AF_CONTROL_RESET,
|
||||
AF_CONTROL_SET_PLAYBACK_SPEED,
|
||||
AF_CONTROL_SET_PLAYBACK_SPEED_RESAMPLE,
|
||||
AF_CONTROL_GET_METADATA,
|
||||
AF_CONTROL_COMMAND,
|
||||
};
|
||||
|
||||
// Argument for AF_CONTROL_SET_PAN_LEVEL
|
||||
typedef struct af_control_ext_s {
|
||||
void* arg; // Argument
|
||||
int ch; // Chanel number
|
||||
} af_control_ext_t;
|
||||
|
||||
struct af_stream *af_new(struct mpv_global *global);
|
||||
void af_destroy(struct af_stream *s);
|
||||
int af_init(struct af_stream *s);
|
||||
void af_uninit(struct af_stream *s);
|
||||
struct af_instance *af_add(struct af_stream *s, char *name, char *label,
|
||||
char **args);
|
||||
int af_remove_by_label(struct af_stream *s, char *label);
|
||||
struct af_instance *af_find_by_label(struct af_stream *s, char *label);
|
||||
struct af_instance *af_control_any_rev(struct af_stream *s, int cmd, void *arg);
|
||||
void af_control_all(struct af_stream *s, int cmd, void *arg);
|
||||
int af_control_by_label(struct af_stream *s, int cmd, void *arg, bstr label);
|
||||
void af_seek_reset(struct af_stream *s);
|
||||
int af_send_command(struct af_stream *s, char *label, char *cmd, char *arg);
|
||||
|
||||
void af_add_output_frame(struct af_instance *af, struct mp_audio *frame);
|
||||
int af_filter_frame(struct af_stream *s, struct mp_audio *frame);
|
||||
int af_output_frame(struct af_stream *s, bool eof);
|
||||
struct mp_audio *af_read_output_frame(struct af_stream *s);
|
||||
void af_unread_output_frame(struct af_stream *s, struct mp_audio *frame);
|
||||
int af_make_writeable(struct af_instance *af, struct mp_audio *frame);
|
||||
|
||||
double af_calc_delay(struct af_stream *s);
|
||||
|
||||
int af_test_output(struct af_instance *af, struct mp_audio *out);
|
||||
|
||||
int af_from_ms(int n, float *in, int *out, int rate, float mi, float ma);
|
||||
float af_softclip(float a);
|
||||
|
||||
#endif /* MPLAYER_AF_H */
|
||||
+87
-94
@@ -15,14 +15,18 @@
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "audio/aframe.h"
|
||||
#include "audio/format.h"
|
||||
#include "filters/f_autoconvert.h"
|
||||
#include "filters/filter_internal.h"
|
||||
#include "filters/user_filters.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <libavutil/common.h>
|
||||
|
||||
#include "options/m_option.h"
|
||||
|
||||
struct f_opts {
|
||||
#include "audio/format.h"
|
||||
#include "af.h"
|
||||
|
||||
struct priv {
|
||||
struct m_config *config;
|
||||
|
||||
int in_format;
|
||||
int in_srate;
|
||||
struct m_channels in_channels;
|
||||
@@ -33,109 +37,98 @@ struct f_opts {
|
||||
int fail;
|
||||
};
|
||||
|
||||
struct priv {
|
||||
struct f_opts *opts;
|
||||
struct mp_pin *in_pin;
|
||||
};
|
||||
|
||||
static void process(struct mp_filter *f)
|
||||
static void force_in_params(struct af_instance *af, struct mp_audio *in)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
struct priv *priv = af->priv;
|
||||
|
||||
if (!mp_pin_can_transfer_data(f->ppins[1], p->in_pin))
|
||||
return;
|
||||
if (priv->in_format != AF_FORMAT_UNKNOWN)
|
||||
mp_audio_set_format(in, priv->in_format);
|
||||
|
||||
struct mp_frame frame = mp_pin_out_read(p->in_pin);
|
||||
if (priv->in_channels.num_chmaps > 0)
|
||||
mp_audio_set_channels(in, &priv->in_channels.chmaps[0]);
|
||||
|
||||
if (p->opts->fail) {
|
||||
MP_ERR(f, "Failing on purpose.\n");
|
||||
goto error;
|
||||
}
|
||||
if (priv->in_srate)
|
||||
in->rate = priv->in_srate;
|
||||
}
|
||||
|
||||
if (frame.type == MP_FRAME_EOF) {
|
||||
mp_pin_in_write(f->ppins[1], frame);
|
||||
return;
|
||||
}
|
||||
static void force_out_params(struct af_instance *af, struct mp_audio *out)
|
||||
{
|
||||
struct priv *priv = af->priv;
|
||||
|
||||
if (frame.type != MP_FRAME_AUDIO) {
|
||||
MP_ERR(f, "audio frame expected\n");
|
||||
goto error;
|
||||
}
|
||||
if (priv->out_format != AF_FORMAT_UNKNOWN)
|
||||
mp_audio_set_format(out, priv->out_format);
|
||||
|
||||
struct mp_aframe *in = frame.data;
|
||||
if (priv->out_channels.num_chmaps > 0)
|
||||
mp_audio_set_channels(out, &priv->out_channels.chmaps[0]);
|
||||
|
||||
if (p->opts->out_channels.num_chmaps > 0) {
|
||||
if (!mp_aframe_set_chmap(in, &p->opts->out_channels.chmaps[0])) {
|
||||
MP_ERR(f, "could not force output channels\n");
|
||||
goto error;
|
||||
if (priv->out_srate)
|
||||
out->rate = priv->out_srate;
|
||||
}
|
||||
|
||||
static int control(struct af_instance *af, int cmd, void *arg)
|
||||
{
|
||||
struct priv *priv = af->priv;
|
||||
|
||||
switch (cmd) {
|
||||
case AF_CONTROL_REINIT: {
|
||||
struct mp_audio *in = arg;
|
||||
struct mp_audio orig_in = *in;
|
||||
struct mp_audio *out = af->data;
|
||||
|
||||
force_in_params(af, in);
|
||||
mp_audio_copy_config(out, in);
|
||||
force_out_params(af, out);
|
||||
|
||||
if (in->nch != out->nch || in->bps != out->bps) {
|
||||
MP_ERR(af, "Forced input/output formats are incompatible.\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
if (priv->fail) {
|
||||
MP_ERR(af, "Failing on purpose.\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
return mp_audio_config_equals(in, &orig_in) ? AF_OK : AF_FALSE;
|
||||
}
|
||||
|
||||
if (p->opts->out_srate)
|
||||
mp_aframe_set_rate(in, p->opts->out_srate);
|
||||
|
||||
mp_pin_in_write(f->ppins[1], frame);
|
||||
return;
|
||||
|
||||
error:
|
||||
mp_frame_unref(&frame);
|
||||
mp_filter_internal_mark_failed(f);
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static const struct mp_filter_info af_format_filter = {
|
||||
.name = "format",
|
||||
.priv_size = sizeof(struct priv),
|
||||
.process = process,
|
||||
};
|
||||
|
||||
static struct mp_filter *af_format_create(struct mp_filter *parent,
|
||||
void *options)
|
||||
static int filter(struct af_instance *af, struct mp_audio *data)
|
||||
{
|
||||
struct mp_filter *f = mp_filter_create(parent, &af_format_filter);
|
||||
if (!f) {
|
||||
talloc_free(options);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct priv *p = f->priv;
|
||||
p->opts = talloc_steal(p, options);
|
||||
|
||||
mp_filter_add_pin(f, MP_PIN_IN, "in");
|
||||
mp_filter_add_pin(f, MP_PIN_OUT, "out");
|
||||
|
||||
struct mp_autoconvert *conv = mp_autoconvert_create(f);
|
||||
if (!conv)
|
||||
abort();
|
||||
|
||||
if (p->opts->in_format)
|
||||
mp_autoconvert_add_afmt(conv, p->opts->in_format);
|
||||
if (p->opts->in_srate)
|
||||
mp_autoconvert_add_srate(conv, p->opts->in_srate);
|
||||
if (p->opts->in_channels.num_chmaps > 0)
|
||||
mp_autoconvert_add_chmap(conv, &p->opts->in_channels.chmaps[0]);
|
||||
|
||||
mp_pin_connect(conv->f->pins[0], f->ppins[0]);
|
||||
p->in_pin = conv->f->pins[1];
|
||||
|
||||
return f;
|
||||
if (data)
|
||||
mp_audio_copy_config(data, af->data);
|
||||
af_add_output_frame(af, data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct f_opts
|
||||
static int af_open(struct af_instance *af)
|
||||
{
|
||||
af->control = control;
|
||||
af->filter_frame = filter;
|
||||
|
||||
const struct mp_user_filter_entry af_format = {
|
||||
.desc = {
|
||||
.name = "format",
|
||||
.description = "Force audio format",
|
||||
.priv_size = sizeof(struct f_opts),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_AUDIOFORMAT("format", in_format, 0),
|
||||
OPT_INTRANGE("srate", in_srate, 0, 1000, 8*48000),
|
||||
OPT_CHANNELS("channels", in_channels, 0, .min = 1),
|
||||
OPT_INTRANGE("out-srate", out_srate, 0, 1000, 8*48000),
|
||||
OPT_CHANNELS("out-channels", out_channels, 0, .min = 1),
|
||||
OPT_FLAG("fail", fail, 0),
|
||||
{0}
|
||||
},
|
||||
force_in_params(af, af->data);
|
||||
force_out_params(af, af->data);
|
||||
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
const struct af_info af_info_format = {
|
||||
.info = "Force audio format",
|
||||
.name = "format",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_AUDIOFORMAT("format", in_format, 0),
|
||||
OPT_INTRANGE("srate", in_srate, 0, 1000, 8*48000),
|
||||
OPT_CHANNELS("channels", in_channels, 0, .min = 1),
|
||||
OPT_AUDIOFORMAT("out-format", out_format, 0),
|
||||
OPT_INTRANGE("out-srate", out_srate, 0, 1000, 8*48000),
|
||||
OPT_CHANNELS("out-channels", out_channels, 0, .min = 1),
|
||||
OPT_FLAG("fail", fail, 0),
|
||||
{0}
|
||||
},
|
||||
.create = af_format_create,
|
||||
};
|
||||
+280
-234
@@ -31,17 +31,14 @@
|
||||
#include <libavutil/bswap.h>
|
||||
#include <libavutil/mem.h>
|
||||
|
||||
#include "audio/aframe.h"
|
||||
#include "audio/chmap_sel.h"
|
||||
#include "audio/fmt-conversion.h"
|
||||
#include "audio/format.h"
|
||||
#include "config.h"
|
||||
|
||||
#include "common/av_common.h"
|
||||
#include "common/common.h"
|
||||
#include "filters/f_autoconvert.h"
|
||||
#include "filters/f_utils.h"
|
||||
#include "filters/filter_internal.h"
|
||||
#include "filters/user_filters.h"
|
||||
#include "options/m_option.h"
|
||||
#include "af.h"
|
||||
#include "audio/audio_buffer.h"
|
||||
#include "audio/chmap_sel.h"
|
||||
#include "audio/fmt-conversion.h"
|
||||
|
||||
|
||||
#define AC3_MAX_CHANNELS 6
|
||||
@@ -52,89 +49,173 @@ const uint16_t ac3_bitrate_tab[19] = {
|
||||
160, 192, 224, 256, 320, 384, 448, 512, 576, 640
|
||||
};
|
||||
|
||||
struct f_opts {
|
||||
int add_iec61937_header;
|
||||
int bit_rate;
|
||||
int min_channel_num;
|
||||
char *encoder;
|
||||
char **avopts;
|
||||
};
|
||||
|
||||
struct priv {
|
||||
struct f_opts *opts;
|
||||
|
||||
struct mp_pin *in_pin;
|
||||
struct mp_aframe *cur_format;
|
||||
struct mp_aframe *in_frame;
|
||||
struct mp_aframe_pool *out_pool;
|
||||
|
||||
// Data for specific instances of this filter
|
||||
typedef struct af_ac3enc_s {
|
||||
struct AVCodec *lavc_acodec;
|
||||
struct AVCodecContext *lavc_actx;
|
||||
int bit_rate;
|
||||
struct mp_audio *input; // frame passed to libavcodec
|
||||
struct mp_audio *pending; // unconsumed input data
|
||||
int in_samples; // samples of input per AC3 frame
|
||||
int out_samples; // upper bound on encoded output per AC3 frame
|
||||
};
|
||||
int64_t encoder_buffered;
|
||||
|
||||
static bool reinit(struct mp_filter *f)
|
||||
int cfg_add_iec61937_header;
|
||||
int cfg_bit_rate;
|
||||
int cfg_min_channel_num;
|
||||
char *cfg_encoder;
|
||||
char **cfg_avopts;
|
||||
} af_ac3enc_t;
|
||||
|
||||
// fmt carries the input format. Change it to the best next-possible format
|
||||
// the encoder likely accepts.
|
||||
static void select_encode_format(AVCodecContext *c, struct mp_audio *fmt)
|
||||
{
|
||||
struct priv *s = f->priv;
|
||||
int formats[AF_FORMAT_COUNT];
|
||||
af_get_best_sample_formats(fmt->format, formats);
|
||||
|
||||
mp_aframe_reset(s->cur_format);
|
||||
for (int n = 0; formats[n]; n++) {
|
||||
const enum AVSampleFormat *lf = c->codec->sample_fmts;
|
||||
for (int i = 0; lf && lf[i] != AV_SAMPLE_FMT_NONE; i++) {
|
||||
int mpfmt = af_from_avformat(lf[i]);
|
||||
if (mpfmt && mpfmt == formats[n]) {
|
||||
mp_audio_set_format(fmt, mpfmt);
|
||||
goto done_fmt;
|
||||
}
|
||||
}
|
||||
}
|
||||
done_fmt: ;
|
||||
|
||||
int rate =
|
||||
af_select_best_samplerate(fmt->rate, c->codec->supported_samplerates);
|
||||
if (rate > 0)
|
||||
fmt->rate = rate;
|
||||
|
||||
struct mp_chmap_sel sel = {0};
|
||||
const uint64_t *lch = c->codec->channel_layouts;
|
||||
for (int n = 0; lch && lch[n]; n++) {
|
||||
struct mp_chmap chmap = {0};
|
||||
mp_chmap_from_lavc(&chmap, lch[n]);
|
||||
mp_chmap_sel_add_map(&sel, &chmap);
|
||||
}
|
||||
struct mp_chmap res = fmt->channels;
|
||||
mp_chmap_sel_adjust(&sel, &res);
|
||||
if (!mp_chmap_is_empty(&res))
|
||||
mp_audio_set_channels(fmt, &res);
|
||||
}
|
||||
|
||||
// Initialization and runtime control
|
||||
static int control(struct af_instance *af, int cmd, void *arg)
|
||||
{
|
||||
af_ac3enc_t *s = af->priv;
|
||||
static const int default_bit_rate[AC3_MAX_CHANNELS+1] = \
|
||||
{0, 96000, 192000, 256000, 384000, 448000, 448000};
|
||||
|
||||
if (s->opts->add_iec61937_header) {
|
||||
s->out_samples = AC3_FRAME_SIZE;
|
||||
} else {
|
||||
s->out_samples = AC3_MAX_CODED_FRAME_SIZE /
|
||||
mp_aframe_get_sstride(s->in_frame);
|
||||
switch (cmd){
|
||||
case AF_CONTROL_REINIT: {
|
||||
struct mp_audio *in = arg;
|
||||
struct mp_audio orig_in = *in;
|
||||
|
||||
if (!af_fmt_is_pcm(in->format) || in->nch < s->cfg_min_channel_num)
|
||||
return AF_DETACH;
|
||||
|
||||
// At least currently, the AC3 encoder doesn't export sample rates.
|
||||
in->rate = 48000;
|
||||
select_encode_format(s->lavc_actx, in);
|
||||
|
||||
af->data->rate = in->rate;
|
||||
mp_audio_set_format(af->data, AF_FORMAT_S_AC3);
|
||||
mp_audio_set_num_channels(af->data, 2);
|
||||
|
||||
if (!mp_audio_config_equals(in, &orig_in))
|
||||
return AF_FALSE;
|
||||
|
||||
if (s->cfg_add_iec61937_header) {
|
||||
s->out_samples = AC3_FRAME_SIZE;
|
||||
} else {
|
||||
s->out_samples = AC3_MAX_CODED_FRAME_SIZE / af->data->sstride;
|
||||
}
|
||||
|
||||
mp_audio_copy_config(s->input, in);
|
||||
|
||||
talloc_free(s->pending);
|
||||
s->pending = NULL;
|
||||
|
||||
MP_DBG(af, "reinit: %d, %d, %d.\n", in->nch, in->rate, s->in_samples);
|
||||
|
||||
int bit_rate = s->bit_rate ? s->bit_rate : default_bit_rate[in->nch];
|
||||
|
||||
if (s->lavc_actx->channels != in->nch ||
|
||||
s->lavc_actx->sample_rate != in->rate ||
|
||||
s->lavc_actx->bit_rate != bit_rate)
|
||||
{
|
||||
avcodec_close(s->lavc_actx);
|
||||
|
||||
// Put sample parameters
|
||||
s->lavc_actx->sample_fmt = af_to_avformat(in->format);
|
||||
s->lavc_actx->channels = in->nch;
|
||||
s->lavc_actx->channel_layout = mp_chmap_to_lavc(&in->channels);
|
||||
s->lavc_actx->sample_rate = in->rate;
|
||||
s->lavc_actx->bit_rate = bit_rate;
|
||||
|
||||
if (avcodec_open2(s->lavc_actx, s->lavc_acodec, NULL) < 0) {
|
||||
MP_ERR(af, "Couldn't open codec %s, br=%d.\n", "ac3", bit_rate);
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
if (s->lavc_actx->frame_size < 1) {
|
||||
MP_ERR(af, "encoder didn't specify input frame size\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
}
|
||||
s->in_samples = s->lavc_actx->frame_size;
|
||||
mp_audio_realloc(s->input, s->in_samples);
|
||||
s->input->samples = 0;
|
||||
s->encoder_buffered = 0;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
int format = mp_aframe_get_format(s->in_frame);
|
||||
int rate = mp_aframe_get_rate(s->in_frame);
|
||||
struct mp_chmap chmap = {0};
|
||||
mp_aframe_get_chmap(s->in_frame, &chmap);
|
||||
|
||||
int bit_rate = s->bit_rate;
|
||||
if (!bit_rate && chmap.num < AC3_MAX_CHANNELS + 1)
|
||||
bit_rate = default_bit_rate[chmap.num];
|
||||
|
||||
avcodec_close(s->lavc_actx);
|
||||
|
||||
// Put sample parameters
|
||||
s->lavc_actx->sample_fmt = af_to_avformat(format);
|
||||
s->lavc_actx->channels = chmap.num;
|
||||
s->lavc_actx->channel_layout = mp_chmap_to_lavc(&chmap);
|
||||
s->lavc_actx->sample_rate = rate;
|
||||
s->lavc_actx->bit_rate = bit_rate;
|
||||
|
||||
if (avcodec_open2(s->lavc_actx, s->lavc_acodec, NULL) < 0) {
|
||||
MP_ERR(f, "Couldn't open codec %s, br=%d.\n", "ac3", bit_rate);
|
||||
return false;
|
||||
case AF_CONTROL_RESET:
|
||||
if (avcodec_is_open(s->lavc_actx))
|
||||
avcodec_flush_buffers(s->lavc_actx);
|
||||
talloc_free(s->pending);
|
||||
s->pending = NULL;
|
||||
s->input->samples = 0;
|
||||
s->encoder_buffered = 0;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
if (s->lavc_actx->frame_size < 1) {
|
||||
MP_ERR(f, "encoder didn't specify input frame size\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
mp_aframe_config_copy(s->cur_format, s->in_frame);
|
||||
return true;
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static void reset(struct mp_filter *f)
|
||||
// Deallocate memory
|
||||
static void uninit(struct af_instance* af)
|
||||
{
|
||||
struct priv *s = f->priv;
|
||||
af_ac3enc_t *s = af->priv;
|
||||
|
||||
TA_FREEP(&s->in_frame);
|
||||
if (s) {
|
||||
avcodec_free_context(&s->lavc_actx);
|
||||
talloc_free(s->pending);
|
||||
}
|
||||
}
|
||||
|
||||
static void destroy(struct mp_filter *f)
|
||||
static void update_delay(struct af_instance *af)
|
||||
{
|
||||
struct priv *s = f->priv;
|
||||
af_ac3enc_t *s = af->priv;
|
||||
af->delay = ((s->pending ? s->pending->samples : 0) + s->input->samples +
|
||||
s->encoder_buffered) / (double)s->input->rate;
|
||||
}
|
||||
|
||||
reset(f);
|
||||
avcodec_free_context(&s->lavc_actx);
|
||||
static int filter_frame(struct af_instance *af, struct mp_audio *audio)
|
||||
{
|
||||
af_ac3enc_t *s = af->priv;
|
||||
|
||||
// filter_output must have been called until no output was produced.
|
||||
if (s->pending && s->pending->samples)
|
||||
MP_ERR(af, "broken data flow\n");
|
||||
|
||||
talloc_free(s->pending);
|
||||
s->pending = audio;
|
||||
update_delay(af);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void swap_16(uint16_t *ptr, size_t size)
|
||||
@@ -143,84 +224,105 @@ static void swap_16(uint16_t *ptr, size_t size)
|
||||
ptr[n] = av_bswap16(ptr[n]);
|
||||
}
|
||||
|
||||
static void process(struct mp_filter *f)
|
||||
// Copy data from input frame to encode frame (because libavcodec wants a full
|
||||
// AC3 frame for encoding, while filter input frames can be smaller or larger).
|
||||
// Return true if the frame is complete.
|
||||
static bool fill_buffer(struct af_instance *af)
|
||||
{
|
||||
struct priv *s = f->priv;
|
||||
af_ac3enc_t *s = af->priv;
|
||||
|
||||
if (!mp_pin_in_needs_data(f->ppins[1]))
|
||||
return;
|
||||
af->delay = 0;
|
||||
|
||||
if (s->pending) {
|
||||
if (!mp_audio_is_writeable(s->input))
|
||||
assert(s->input->samples == 0); // we can't have sent a partial frame
|
||||
mp_audio_realloc_min(s->input, s->in_samples);
|
||||
int copy = MPMIN(s->in_samples - s->input->samples, s->pending->samples);
|
||||
s->input->samples += copy;
|
||||
mp_audio_copy(s->input, s->input->samples - copy, s->pending, 0, copy);
|
||||
mp_audio_skip_samples(s->pending, copy);
|
||||
}
|
||||
update_delay(af);
|
||||
return s->input->samples >= s->in_samples;
|
||||
}
|
||||
|
||||
// Return <0 on error, 0 on need more input, 1 on success (and *frame set).
|
||||
// To actually advance the read pointer, set s->input->samples=0 afterwards.
|
||||
static int read_input_frame(struct af_instance *af, AVFrame *frame)
|
||||
{
|
||||
af_ac3enc_t *s = af->priv;
|
||||
if (!fill_buffer(af))
|
||||
return 0; // need more input
|
||||
|
||||
if (mp_audio_to_avframe(s->input, frame) < 0)
|
||||
return -1;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int filter_out(struct af_instance *af)
|
||||
{
|
||||
af_ac3enc_t *s = af->priv;
|
||||
|
||||
if (!s->pending)
|
||||
return 0;
|
||||
|
||||
AVFrame *frame = av_frame_alloc();
|
||||
if (!frame) {
|
||||
MP_FATAL(af, "Could not allocate memory \n");
|
||||
return -1;
|
||||
}
|
||||
int err = -1;
|
||||
|
||||
bool err = true;
|
||||
struct mp_aframe *out = NULL;
|
||||
AVPacket pkt = {0};
|
||||
av_init_packet(&pkt);
|
||||
|
||||
// Send input as long as it wants.
|
||||
while (1) {
|
||||
if (avcodec_is_open(s->lavc_actx)) {
|
||||
int lavc_ret = avcodec_receive_packet(s->lavc_actx, &pkt);
|
||||
if (lavc_ret >= 0)
|
||||
break;
|
||||
if (lavc_ret < 0 && lavc_ret != AVERROR(EAGAIN)) {
|
||||
MP_FATAL(f, "Encode failed (receive).\n");
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
AVFrame *frame = NULL;
|
||||
struct mp_frame input = mp_pin_out_read(s->in_pin);
|
||||
// The following code assumes no sample data buffering in the encoder.
|
||||
if (input.type == MP_FRAME_EOF) {
|
||||
mp_pin_in_write(f->ppins[1], input);
|
||||
return;
|
||||
} else if (input.type == MP_FRAME_AUDIO) {
|
||||
TA_FREEP(&s->in_frame);
|
||||
s->in_frame = input.data;
|
||||
frame = mp_frame_to_av(input, NULL);
|
||||
if (!frame)
|
||||
goto done;
|
||||
if (mp_aframe_get_channels(s->in_frame) < s->opts->min_channel_num) {
|
||||
// Just pass it through.
|
||||
s->in_frame = NULL;
|
||||
mp_pin_in_write(f->ppins[1], input);
|
||||
return;
|
||||
}
|
||||
if (!mp_aframe_config_equals(s->in_frame, s->cur_format)) {
|
||||
if (!reinit(f))
|
||||
goto done;
|
||||
}
|
||||
} else if (input.type) {
|
||||
err = read_input_frame(af, frame);
|
||||
if (err < 0)
|
||||
goto done;
|
||||
} else {
|
||||
return; // no data yet
|
||||
}
|
||||
if (err == 0)
|
||||
break;
|
||||
err = -1;
|
||||
int lavc_ret = avcodec_send_frame(s->lavc_actx, frame);
|
||||
av_frame_free(&frame);
|
||||
if (lavc_ret < 0 && lavc_ret != AVERROR(EAGAIN)) {
|
||||
MP_FATAL(f, "Encode failed (send).\n");
|
||||
// On EAGAIN, we're supposed to read remaining output.
|
||||
if (lavc_ret == AVERROR(EAGAIN))
|
||||
break;
|
||||
if (lavc_ret < 0) {
|
||||
MP_FATAL(af, "Encode failed.\n");
|
||||
goto done;
|
||||
}
|
||||
s->encoder_buffered += s->input->samples;
|
||||
s->input->samples = 0;
|
||||
}
|
||||
int lavc_ret = avcodec_receive_packet(s->lavc_actx, &pkt);
|
||||
if (lavc_ret == AVERROR(EAGAIN)) {
|
||||
// Need to buffer more input.
|
||||
err = 0;
|
||||
goto done;
|
||||
}
|
||||
if (lavc_ret < 0) {
|
||||
MP_FATAL(af, "Encode failed.\n");
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (!s->in_frame)
|
||||
MP_DBG(af, "avcodec_encode_audio got %d, pending %d.\n",
|
||||
pkt.size, s->pending->samples + s->input->samples);
|
||||
|
||||
s->encoder_buffered -= AC3_FRAME_SIZE;
|
||||
|
||||
struct mp_audio *out =
|
||||
mp_audio_pool_get(af->out_pool, af->data, s->out_samples);
|
||||
if (!out)
|
||||
goto done;
|
||||
|
||||
out = mp_aframe_create();
|
||||
mp_aframe_set_format(out, AF_FORMAT_S_AC3);
|
||||
mp_aframe_set_chmap(out, &(struct mp_chmap)MP_CHMAP_INIT_STEREO);
|
||||
mp_aframe_set_rate(out, 48000);
|
||||
|
||||
if (mp_aframe_pool_allocate(s->out_pool, out, s->out_samples) < 0)
|
||||
goto done;
|
||||
|
||||
int sstride = mp_aframe_get_sstride(out);
|
||||
|
||||
mp_aframe_copy_attributes(out, s->in_frame);
|
||||
mp_audio_copy_attributes(out, s->pending);
|
||||
|
||||
int frame_size = pkt.size;
|
||||
int header_len = 0;
|
||||
char hdr[8];
|
||||
|
||||
if (s->opts->add_iec61937_header && pkt.size > 5) {
|
||||
if (s->cfg_add_iec61937_header && pkt.size > 5) {
|
||||
int bsmod = pkt.data[5] & 0x7;
|
||||
int len = frame_size;
|
||||
|
||||
@@ -234,69 +336,48 @@ static void process(struct mp_filter *f)
|
||||
AV_WL16(hdr + 6, len << 3); // number of bits in payload
|
||||
}
|
||||
|
||||
if (frame_size > s->out_samples * sstride)
|
||||
if (frame_size > out->samples * out->sstride)
|
||||
abort();
|
||||
|
||||
uint8_t **planes = mp_aframe_get_data_rw(out);
|
||||
if (!planes)
|
||||
goto done;
|
||||
char *buf = planes[0];
|
||||
char *buf = (char *)out->planes[0];
|
||||
memcpy(buf, hdr, header_len);
|
||||
memcpy(buf + header_len, pkt.data, pkt.size);
|
||||
memset(buf + header_len + pkt.size, 0,
|
||||
frame_size - (header_len + pkt.size));
|
||||
swap_16((uint16_t *)(buf + header_len), pkt.size / 2);
|
||||
mp_aframe_set_size(out, frame_size / sstride);
|
||||
mp_pin_in_write(f->ppins[1], MAKE_FRAME(MP_FRAME_AUDIO, out));
|
||||
out = NULL;
|
||||
out->samples = frame_size / out->sstride;
|
||||
af_add_output_frame(af, out);
|
||||
|
||||
err = 0;
|
||||
done:
|
||||
av_packet_unref(&pkt);
|
||||
talloc_free(out);
|
||||
if (err)
|
||||
mp_filter_internal_mark_failed(f);
|
||||
av_frame_free(&frame);
|
||||
update_delay(af);
|
||||
return err;
|
||||
}
|
||||
|
||||
static const struct mp_filter_info af_lavcac3enc_filter = {
|
||||
.name = "lavcac3enc",
|
||||
.priv_size = sizeof(struct priv),
|
||||
.process = process,
|
||||
.reset = reset,
|
||||
.destroy = destroy,
|
||||
};
|
||||
static int af_open(struct af_instance* af){
|
||||
|
||||
static struct mp_filter *af_lavcac3enc_create(struct mp_filter *parent,
|
||||
void *options)
|
||||
{
|
||||
struct mp_filter *f = mp_filter_create(parent, &af_lavcac3enc_filter);
|
||||
if (!f) {
|
||||
talloc_free(options);
|
||||
return NULL;
|
||||
}
|
||||
af_ac3enc_t *s = af->priv;
|
||||
af->control=control;
|
||||
af->uninit=uninit;
|
||||
af->filter_frame = filter_frame;
|
||||
af->filter_out = filter_out;
|
||||
|
||||
mp_filter_add_pin(f, MP_PIN_IN, "in");
|
||||
mp_filter_add_pin(f, MP_PIN_OUT, "out");
|
||||
|
||||
struct priv *s = f->priv;
|
||||
s->opts = talloc_steal(s, options);
|
||||
s->cur_format = talloc_steal(s, mp_aframe_create());
|
||||
s->out_pool = mp_aframe_pool_create(s);
|
||||
|
||||
s->lavc_acodec = avcodec_find_encoder_by_name(s->opts->encoder);
|
||||
s->lavc_acodec = avcodec_find_encoder_by_name(s->cfg_encoder);
|
||||
if (!s->lavc_acodec) {
|
||||
MP_ERR(f, "Couldn't find encoder %s.\n", s->opts->encoder);
|
||||
goto error;
|
||||
MP_ERR(af, "Couldn't find encoder %s.\n", s->cfg_encoder);
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
s->lavc_actx = avcodec_alloc_context3(s->lavc_acodec);
|
||||
if (!s->lavc_actx) {
|
||||
MP_ERR(f, "Audio LAVC, couldn't allocate context!\n");
|
||||
goto error;
|
||||
MP_ERR(af, "Audio LAVC, couldn't allocate context!\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
if (mp_set_avopts(f->log, s->lavc_actx, s->opts->avopts) < 0)
|
||||
goto error;
|
||||
if (mp_set_avopts(af->log, s->lavc_actx, s->cfg_avopts) < 0)
|
||||
return AF_ERROR;
|
||||
|
||||
// For this one, we require the decoder to expert lists of all supported
|
||||
// parameters. (Not all decoders do that, but the ones we're interested
|
||||
@@ -304,85 +385,50 @@ static struct mp_filter *af_lavcac3enc_create(struct mp_filter *parent,
|
||||
if (!s->lavc_acodec->sample_fmts ||
|
||||
!s->lavc_acodec->channel_layouts)
|
||||
{
|
||||
MP_ERR(f, "Audio encoder doesn't list supported parameters.\n");
|
||||
goto error;
|
||||
MP_ERR(af, "Audio encoder doesn't list supported parameters.\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
if (s->opts->bit_rate) {
|
||||
s->input = talloc_zero(s, struct mp_audio);
|
||||
|
||||
if (s->cfg_bit_rate) {
|
||||
int i;
|
||||
for (i = 0; i < 19; i++) {
|
||||
if (ac3_bitrate_tab[i] == s->opts->bit_rate) {
|
||||
if (ac3_bitrate_tab[i] == s->cfg_bit_rate) {
|
||||
s->bit_rate = ac3_bitrate_tab[i] * 1000;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i >= 19) {
|
||||
MP_WARN(f, "unable set unsupported bitrate %d, using default "
|
||||
MP_WARN(af, "unable set unsupported bitrate %d, using default "
|
||||
"bitrate (check manpage to see supported bitrates).\n",
|
||||
s->opts->bit_rate);
|
||||
s->cfg_bit_rate);
|
||||
}
|
||||
}
|
||||
|
||||
struct mp_autoconvert *conv = mp_autoconvert_create(f);
|
||||
if (!conv)
|
||||
abort();
|
||||
|
||||
const enum AVSampleFormat *lf = s->lavc_acodec->sample_fmts;
|
||||
for (int i = 0; lf && lf[i] != AV_SAMPLE_FMT_NONE; i++) {
|
||||
int mpfmt = af_from_avformat(lf[i]);
|
||||
if (mpfmt)
|
||||
mp_autoconvert_add_afmt(conv, mpfmt);
|
||||
}
|
||||
|
||||
const uint64_t *lch = s->lavc_acodec->channel_layouts;
|
||||
for (int n = 0; lch && lch[n]; n++) {
|
||||
struct mp_chmap chmap = {0};
|
||||
mp_chmap_from_lavc(&chmap, lch[n]);
|
||||
if (mp_chmap_is_valid(&chmap))
|
||||
mp_autoconvert_add_chmap(conv, &chmap);
|
||||
}
|
||||
|
||||
// At least currently, the AC3 encoder doesn't export sample rates.
|
||||
mp_autoconvert_add_srate(conv, 48000);
|
||||
|
||||
mp_pin_connect(conv->f->pins[0], f->ppins[0]);
|
||||
|
||||
struct mp_filter *fs = mp_fixed_aframe_size_create(f, AC3_FRAME_SIZE, true);
|
||||
if (!fs)
|
||||
abort();
|
||||
|
||||
mp_pin_connect(fs->pins[0], conv->f->pins[1]);
|
||||
s->in_pin = fs->pins[1];
|
||||
|
||||
return f;
|
||||
|
||||
error:
|
||||
talloc_free(f);
|
||||
return NULL;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct f_opts
|
||||
#define OPT_BASE_STRUCT struct af_ac3enc_s
|
||||
|
||||
const struct mp_user_filter_entry af_lavcac3enc = {
|
||||
.desc = {
|
||||
.description = "runtime encode to ac3 using libavcodec",
|
||||
.name = "lavcac3enc",
|
||||
.priv_size = sizeof(OPT_BASE_STRUCT),
|
||||
.priv_defaults = &(const OPT_BASE_STRUCT) {
|
||||
.add_iec61937_header = 1,
|
||||
.bit_rate = 640,
|
||||
.min_channel_num = 3,
|
||||
.encoder = "ac3",
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_FLAG("tospdif", add_iec61937_header, 0),
|
||||
OPT_CHOICE_OR_INT("bitrate", bit_rate, 0, 32, 640,
|
||||
({"auto", 0}, {"default", 0})),
|
||||
OPT_INTRANGE("minch", min_channel_num, 0, 2, 6),
|
||||
OPT_STRING("encoder", encoder, 0),
|
||||
OPT_KEYVALUELIST("o", avopts, 0),
|
||||
{0}
|
||||
},
|
||||
const struct af_info af_info_lavcac3enc = {
|
||||
.info = "runtime encode to ac3 using libavcodec",
|
||||
.name = "lavcac3enc",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(struct af_ac3enc_s),
|
||||
.priv_defaults = &(const struct af_ac3enc_s){
|
||||
.cfg_add_iec61937_header = 1,
|
||||
.cfg_bit_rate = 640,
|
||||
.cfg_min_channel_num = 3,
|
||||
.cfg_encoder = "ac3",
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_FLAG("tospdif", cfg_add_iec61937_header, 0),
|
||||
OPT_CHOICE_OR_INT("bitrate", cfg_bit_rate, 0, 32, 640,
|
||||
({"auto", 0}, {"default", 0})),
|
||||
OPT_INTRANGE("minch", cfg_min_channel_num, 0, 2, 6),
|
||||
OPT_STRING("encoder", cfg_encoder, 0),
|
||||
OPT_KEYVALUELIST("o", cfg_avopts, 0),
|
||||
{0}
|
||||
},
|
||||
.create = af_lavcac3enc_create,
|
||||
};
|
||||
@@ -0,0 +1,413 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* Filter graph creation code taken from FFmpeg ffplay.c (LGPL 2.1 or later)
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <libavutil/avstring.h>
|
||||
#include <libavutil/mem.h>
|
||||
#include <libavutil/mathematics.h>
|
||||
#include <libavutil/rational.h>
|
||||
#include <libavutil/samplefmt.h>
|
||||
#include <libavutil/time.h>
|
||||
#include <libavutil/opt.h>
|
||||
#include <libavfilter/avfilter.h>
|
||||
#include <libavfilter/buffersink.h>
|
||||
#include <libavfilter/buffersrc.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "audio/format.h"
|
||||
#include "audio/fmt-conversion.h"
|
||||
#include "af.h"
|
||||
|
||||
#include "common/av_common.h"
|
||||
#include "common/tags.h"
|
||||
|
||||
#include "options/m_option.h"
|
||||
|
||||
// FFmpeg and Libav have slightly different APIs, just enough to cause us
|
||||
// unnecessary pain. <Expletive deleted.>
|
||||
#if LIBAVFILTER_VERSION_MICRO < 100
|
||||
#define graph_parse(graph, filters, inputs, outputs, log_ctx) \
|
||||
avfilter_graph_parse(graph, filters, inputs, outputs, log_ctx)
|
||||
#define avfilter_graph_send_command(a, b, c, d, e, f, g) -1
|
||||
#else
|
||||
#define graph_parse(graph, filters, inputs, outputs, log_ctx) \
|
||||
avfilter_graph_parse_ptr(graph, filters, &(inputs), &(outputs), log_ctx)
|
||||
#endif
|
||||
|
||||
struct priv {
|
||||
// Single filter bridge, instead of a graph.
|
||||
bool is_bridge;
|
||||
|
||||
AVFilterGraph *graph;
|
||||
AVFilterContext *in;
|
||||
AVFilterContext *out;
|
||||
|
||||
int64_t samples_in;
|
||||
|
||||
AVRational timebase_out;
|
||||
|
||||
bool eof;
|
||||
|
||||
struct mp_tags *metadata;
|
||||
|
||||
// options
|
||||
char *cfg_graph;
|
||||
char **cfg_avopts;
|
||||
char *cfg_filter_name;
|
||||
char **cfg_filter_opts;
|
||||
};
|
||||
|
||||
static void destroy_graph(struct af_instance *af)
|
||||
{
|
||||
struct priv *p = af->priv;
|
||||
avfilter_graph_free(&p->graph);
|
||||
p->in = p->out = NULL;
|
||||
p->samples_in = 0;
|
||||
p->eof = false;
|
||||
}
|
||||
|
||||
static bool recreate_graph(struct af_instance *af, struct mp_audio *config)
|
||||
{
|
||||
void *tmp = talloc_new(NULL);
|
||||
struct priv *p = af->priv;
|
||||
AVFilterContext *in = NULL, *out = NULL;
|
||||
bool ok = false;
|
||||
|
||||
if (!p->is_bridge && bstr0(p->cfg_graph).len == 0) {
|
||||
MP_FATAL(af, "lavfi: no filter graph set\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
destroy_graph(af);
|
||||
|
||||
AVFilterGraph *graph = avfilter_graph_alloc();
|
||||
if (!graph)
|
||||
goto error;
|
||||
|
||||
if (mp_set_avopts(af->log, graph, p->cfg_avopts) < 0)
|
||||
goto error;
|
||||
|
||||
AVFilterInOut *outputs = avfilter_inout_alloc();
|
||||
AVFilterInOut *inputs = avfilter_inout_alloc();
|
||||
if (!outputs || !inputs)
|
||||
goto error;
|
||||
|
||||
char *src_args = talloc_asprintf(tmp,
|
||||
"sample_rate=%d:sample_fmt=%s:time_base=%d/%d:"
|
||||
"channel_layout=0x%"PRIx64, config->rate,
|
||||
av_get_sample_fmt_name(af_to_avformat(config->format)),
|
||||
1, config->rate, mp_chmap_to_lavc(&config->channels));
|
||||
|
||||
if (avfilter_graph_create_filter(&in, avfilter_get_by_name("abuffer"),
|
||||
"src", src_args, NULL, graph) < 0)
|
||||
goto error;
|
||||
|
||||
if (avfilter_graph_create_filter(&out, avfilter_get_by_name("abuffersink"),
|
||||
"out", NULL, NULL, graph) < 0)
|
||||
goto error;
|
||||
|
||||
if (p->is_bridge) {
|
||||
AVFilterContext *filter = avfilter_graph_alloc_filter(graph,
|
||||
avfilter_get_by_name(p->cfg_filter_name), "filter");
|
||||
if (!filter)
|
||||
goto error;
|
||||
|
||||
if (mp_set_avopts(af->log, filter->priv, p->cfg_filter_opts) < 0)
|
||||
goto error;
|
||||
|
||||
if (avfilter_init_str(filter, NULL) < 0)
|
||||
goto error;
|
||||
|
||||
// Yep, we have to manually link those filters.
|
||||
if (filter->nb_inputs != 1 ||
|
||||
avfilter_pad_get_type(filter->input_pads, 0) != AVMEDIA_TYPE_AUDIO ||
|
||||
filter->nb_outputs != 1 ||
|
||||
avfilter_pad_get_type(filter->output_pads, 0) != AVMEDIA_TYPE_AUDIO)
|
||||
{
|
||||
MP_ERR(af, "The filter is required to have 1 audio input pad and "
|
||||
"1 audio output pad.\n");
|
||||
goto error;
|
||||
}
|
||||
if (avfilter_link(in, 0, filter, 0) < 0 ||
|
||||
avfilter_link(filter, 0, out, 0) < 0)
|
||||
{
|
||||
MP_ERR(af, "Failed to link filter.\n");
|
||||
goto error;
|
||||
}
|
||||
} else {
|
||||
MP_VERBOSE(af, "lavfi: create graph: '%s'\n", p->cfg_graph);
|
||||
|
||||
outputs->name = av_strdup("in");
|
||||
outputs->filter_ctx = in;
|
||||
|
||||
inputs->name = av_strdup("out");
|
||||
inputs->filter_ctx = out;
|
||||
|
||||
if (graph_parse(graph, p->cfg_graph, inputs, outputs, NULL) < 0)
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (avfilter_graph_config(graph, NULL) < 0)
|
||||
goto error;
|
||||
|
||||
p->in = in;
|
||||
p->out = out;
|
||||
p->graph = graph;
|
||||
|
||||
assert(out->nb_inputs == 1);
|
||||
assert(in->nb_outputs == 1);
|
||||
|
||||
ok = true;
|
||||
error:
|
||||
|
||||
if (!ok) {
|
||||
MP_FATAL(af, "Can't configure libavfilter graph.\n");
|
||||
avfilter_graph_free(&graph);
|
||||
}
|
||||
avfilter_inout_free(&inputs);
|
||||
avfilter_inout_free(&outputs);
|
||||
talloc_free(tmp);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static void reset(struct af_instance *af)
|
||||
{
|
||||
if (!recreate_graph(af, &af->fmt_in))
|
||||
MP_FATAL(af, "Can't recreate libavfilter filter after a seek reset.\n");
|
||||
}
|
||||
|
||||
static int control(struct af_instance *af, int cmd, void *arg)
|
||||
{
|
||||
struct priv *p = af->priv;
|
||||
|
||||
switch (cmd) {
|
||||
case AF_CONTROL_REINIT: {
|
||||
struct mp_audio *in = arg;
|
||||
struct mp_audio orig_in = *in;
|
||||
struct mp_audio *out = af->data;
|
||||
|
||||
if (af_to_avformat(in->format) == AV_SAMPLE_FMT_NONE)
|
||||
mp_audio_set_format(in, AF_FORMAT_FLOAT);
|
||||
|
||||
// Removing this requires fixing AVFrame.data vs. AVFrame.extended_data
|
||||
if (in->channels.num > AV_NUM_DATA_POINTERS)
|
||||
return AF_ERROR;
|
||||
|
||||
if (!mp_chmap_is_lavc(&in->channels))
|
||||
mp_chmap_reorder_to_lavc(&in->channels); // will always work
|
||||
|
||||
if (!recreate_graph(af, in))
|
||||
return AF_ERROR;
|
||||
|
||||
AVFilterLink *l_out = p->out->inputs[0];
|
||||
|
||||
out->rate = l_out->sample_rate;
|
||||
|
||||
mp_audio_set_format(out, af_from_avformat(l_out->format));
|
||||
|
||||
struct mp_chmap out_cm;
|
||||
mp_chmap_from_lavc(&out_cm, l_out->channel_layout);
|
||||
mp_audio_set_channels(out, &out_cm);
|
||||
|
||||
if (!mp_audio_config_valid(out) || out->channels.num > AV_NUM_DATA_POINTERS)
|
||||
return AF_ERROR;
|
||||
|
||||
p->timebase_out = l_out->time_base;
|
||||
|
||||
return mp_audio_config_equals(in, &orig_in) ? AF_OK : AF_FALSE;
|
||||
}
|
||||
case AF_CONTROL_COMMAND: {
|
||||
if (!p->graph)
|
||||
break;
|
||||
char **args = arg;
|
||||
return avfilter_graph_send_command(p->graph, "all",
|
||||
args[0], args[1], &(char){0}, 0, 0)
|
||||
>= 0 ? CONTROL_OK : CONTROL_ERROR;
|
||||
}
|
||||
case AF_CONTROL_GET_METADATA:
|
||||
if (p->metadata) {
|
||||
*(struct mp_tags *)arg = *p->metadata;
|
||||
return CONTROL_OK;
|
||||
}
|
||||
return CONTROL_NA;
|
||||
case AF_CONTROL_RESET:
|
||||
reset(af);
|
||||
return AF_OK;
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static void get_metadata_from_av_frame(struct af_instance *af, AVFrame *frame)
|
||||
{
|
||||
#if LIBAVUTIL_VERSION_MICRO >= 100
|
||||
struct priv *p = af->priv;
|
||||
if (!p->metadata)
|
||||
p->metadata = talloc_zero(p, struct mp_tags);
|
||||
|
||||
mp_tags_copy_from_av_dictionary(p->metadata, frame->metadata);
|
||||
#endif
|
||||
}
|
||||
|
||||
static int filter_frame(struct af_instance *af, struct mp_audio *data)
|
||||
{
|
||||
struct priv *p = af->priv;
|
||||
AVFrame *frame = NULL;
|
||||
|
||||
if (p->eof && data)
|
||||
reset(af);
|
||||
|
||||
if (!p->graph)
|
||||
goto error;
|
||||
|
||||
if (!data) {
|
||||
if (p->eof)
|
||||
return 0;
|
||||
p->eof = true;
|
||||
}
|
||||
|
||||
if (data) {
|
||||
frame = mp_audio_to_avframe_and_unref(data);
|
||||
data = NULL;
|
||||
if (!frame)
|
||||
goto error;
|
||||
|
||||
// Timebase is 1/sample_rate
|
||||
frame->pts = p->samples_in;
|
||||
p->samples_in += frame->nb_samples;
|
||||
}
|
||||
|
||||
if (av_buffersrc_add_frame(p->in, frame) < 0)
|
||||
goto error;
|
||||
|
||||
av_frame_free(&frame);
|
||||
talloc_free(data);
|
||||
return 0;
|
||||
error:
|
||||
av_frame_free(&frame);
|
||||
talloc_free(data);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int filter_out(struct af_instance *af)
|
||||
{
|
||||
struct priv *p = af->priv;
|
||||
|
||||
if (!p->graph)
|
||||
goto error;
|
||||
|
||||
AVFrame *frame = av_frame_alloc();
|
||||
if (!frame)
|
||||
goto error;
|
||||
|
||||
int err = av_buffersink_get_frame(p->out, frame);
|
||||
if (err == AVERROR(EAGAIN) || err == AVERROR_EOF) {
|
||||
// Not an error situation - no more output buffers in queue.
|
||||
// AVERROR_EOF means we shouldn't even give the filter more
|
||||
// input, but we don't handle that completely correctly.
|
||||
av_frame_free(&frame);
|
||||
p->eof |= err == AVERROR_EOF;
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct mp_audio *out = mp_audio_from_avframe(frame);
|
||||
if (!out)
|
||||
goto error;
|
||||
|
||||
mp_audio_copy_config(out, af->data);
|
||||
|
||||
if (frame->pts != AV_NOPTS_VALUE) {
|
||||
double in_time = p->samples_in / (double)af->fmt_in.rate;
|
||||
double out_time = frame->pts * av_q2d(p->timebase_out);
|
||||
// Need pts past the last output sample.
|
||||
out_time += out->samples / (double)out->rate;
|
||||
|
||||
af->delay = in_time - out_time;
|
||||
}
|
||||
|
||||
get_metadata_from_av_frame(af, frame);
|
||||
af_add_output_frame(af, out);
|
||||
av_frame_free(&frame);
|
||||
return 0;
|
||||
error:
|
||||
av_frame_free(&frame);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void uninit(struct af_instance *af)
|
||||
{
|
||||
destroy_graph(af);
|
||||
}
|
||||
|
||||
static int af_open(struct af_instance *af)
|
||||
{
|
||||
struct priv *p = af->priv;
|
||||
|
||||
af->control = control;
|
||||
af->uninit = uninit;
|
||||
af->filter_frame = filter_frame;
|
||||
af->filter_out = filter_out;
|
||||
|
||||
if (p->is_bridge) {
|
||||
if (!p->cfg_filter_name) {
|
||||
MP_ERR(af, "Filter name not set!\n");
|
||||
return 0;
|
||||
}
|
||||
if (!avfilter_get_by_name(p->cfg_filter_name)) {
|
||||
MP_ERR(af, "libavfilter filter '%s' not found!\n", p->cfg_filter_name);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
const struct af_info af_info_lavfi = {
|
||||
.info = "libavfilter bridge",
|
||||
.name = "lavfi",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_STRING("graph", cfg_graph, 0),
|
||||
OPT_KEYVALUELIST("o", cfg_avopts, 0),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
|
||||
const struct af_info af_info_lavfi_bridge = {
|
||||
.info = "libavfilter bridge (explicit options)",
|
||||
.name = "lavfi-bridge",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.priv_defaults = &(const struct priv){
|
||||
.is_bridge = true,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_STRING("name", cfg_filter_name, M_OPT_MIN, .min = 1),
|
||||
OPT_KEYVALUELIST("opts", cfg_filter_opts, 0),
|
||||
OPT_STRING("graph", cfg_graph, 0),
|
||||
OPT_KEYVALUELIST("o", cfg_avopts, 0),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Copyright (c) 2004 Michael Niedermayer <michaelni@gmx.at>
|
||||
* Copyright (c) 2013 Stefano Pigozzi <stefano.pigozzi@gmail.com>
|
||||
*
|
||||
* Based on Michael Niedermayer's lavcresample.
|
||||
*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
#include <math.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "config.h"
|
||||
|
||||
#include "common/av_common.h"
|
||||
#include "common/msg.h"
|
||||
#include "options/m_option.h"
|
||||
#include "audio/filter/af.h"
|
||||
#include "audio/fmt-conversion.h"
|
||||
#include "osdep/endian.h"
|
||||
#include "audio/aconverter.h"
|
||||
|
||||
struct af_resample {
|
||||
int allow_detach;
|
||||
double playback_speed;
|
||||
struct mp_resample_opts opts;
|
||||
struct mp_aconverter *converter;
|
||||
};
|
||||
|
||||
static int control(struct af_instance *af, int cmd, void *arg)
|
||||
{
|
||||
struct af_resample *s = af->priv;
|
||||
|
||||
switch (cmd) {
|
||||
case AF_CONTROL_REINIT: {
|
||||
struct mp_audio *in = arg;
|
||||
struct mp_audio *out = af->data;
|
||||
struct mp_audio orig_in = *in;
|
||||
|
||||
if (((out->rate == in->rate) || (out->rate == 0)) &&
|
||||
(out->format == in->format) &&
|
||||
(mp_chmap_equals(&out->channels, &in->channels) || out->nch == 0) &&
|
||||
s->allow_detach && s->playback_speed == 1.0)
|
||||
return AF_DETACH;
|
||||
|
||||
if (out->rate == 0)
|
||||
out->rate = in->rate;
|
||||
|
||||
if (mp_chmap_is_empty(&out->channels))
|
||||
mp_audio_set_channels(out, &in->channels);
|
||||
|
||||
if (af_to_avformat(in->format) == AV_SAMPLE_FMT_NONE)
|
||||
mp_audio_set_format(in, AF_FORMAT_FLOAT);
|
||||
if (af_to_avformat(out->format) == AV_SAMPLE_FMT_NONE)
|
||||
mp_audio_set_format(out, in->format);
|
||||
|
||||
int r = ((in->format == orig_in.format) &&
|
||||
mp_chmap_equals(&in->channels, &orig_in.channels))
|
||||
? AF_OK : AF_FALSE;
|
||||
|
||||
if (r == AF_OK) {
|
||||
if (!mp_aconverter_reconfig(s->converter,
|
||||
in->rate, in->format, in->channels,
|
||||
out->rate, out->format, out->channels))
|
||||
r = AF_ERROR;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
case AF_CONTROL_SET_PLAYBACK_SPEED_RESAMPLE: {
|
||||
s->playback_speed = *(double *)arg;
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_RESET:
|
||||
mp_aconverter_flush(s->converter);
|
||||
return AF_OK;
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static void uninit(struct af_instance *af)
|
||||
{
|
||||
struct af_resample *s = af->priv;
|
||||
|
||||
talloc_free(s->converter);
|
||||
}
|
||||
|
||||
static int filter(struct af_instance *af, struct mp_audio *in)
|
||||
{
|
||||
struct af_resample *s = af->priv;
|
||||
|
||||
mp_aconverter_set_speed(s->converter, s->playback_speed);
|
||||
|
||||
af->filter_out(af);
|
||||
|
||||
struct mp_aframe *aframe = mp_audio_to_aframe(in);
|
||||
if (!aframe && in)
|
||||
return -1;
|
||||
talloc_free(in);
|
||||
bool ok = mp_aconverter_write_input(s->converter, aframe);
|
||||
if (!ok)
|
||||
talloc_free(aframe);
|
||||
|
||||
return ok ? 0 : -1;
|
||||
}
|
||||
|
||||
static int filter_out(struct af_instance *af)
|
||||
{
|
||||
struct af_resample *s = af->priv;
|
||||
bool eof;
|
||||
struct mp_aframe *out = mp_aconverter_read_output(s->converter, &eof);
|
||||
if (out)
|
||||
af_add_output_frame(af, mp_audio_from_aframe(out));
|
||||
talloc_free(out);
|
||||
af->delay = mp_aconverter_get_latency(s->converter);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int af_open(struct af_instance *af)
|
||||
{
|
||||
struct af_resample *s = af->priv;
|
||||
|
||||
af->control = control;
|
||||
af->uninit = uninit;
|
||||
af->filter_frame = filter;
|
||||
af->filter_out = filter_out;
|
||||
|
||||
s->converter = mp_aconverter_create(af->global, af->log, &s->opts);
|
||||
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct af_resample
|
||||
|
||||
const struct af_info af_info_lavrresample = {
|
||||
.info = "Sample frequency conversion using libavresample",
|
||||
.name = "lavrresample",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(struct af_resample),
|
||||
.priv_defaults = &(const struct af_resample) {
|
||||
.opts = MP_RESAMPLE_OPTS_DEF,
|
||||
.playback_speed = 1.0,
|
||||
.allow_detach = 1,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_INTRANGE("filter-size", opts.filter_size, 0, 0, 32),
|
||||
OPT_INTRANGE("phase-shift", opts.phase_shift, 0, 0, 30),
|
||||
OPT_FLAG("linear", opts.linear, 0),
|
||||
OPT_DOUBLE("cutoff", opts.cutoff, M_OPT_RANGE, .min = 0, .max = 1),
|
||||
OPT_FLAG("detach", allow_detach, 0),
|
||||
OPT_CHOICE("normalize", opts.normalize, 0,
|
||||
({"no", 0}, {"yes", 1}, {"auto", -1})),
|
||||
OPT_KEYVALUELIST("o", opts.avopts, 0),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
+169
-287
@@ -20,350 +20,232 @@
|
||||
|
||||
#include <rubberband/rubberband-c.h>
|
||||
|
||||
#include "audio/aframe.h"
|
||||
#include "audio/format.h"
|
||||
#include "common/common.h"
|
||||
#include "filters/f_autoconvert.h"
|
||||
#include "filters/filter_internal.h"
|
||||
#include "filters/user_filters.h"
|
||||
#include "options/m_option.h"
|
||||
|
||||
// command line options
|
||||
struct f_opts {
|
||||
int transients, detector, phase, window,
|
||||
smoothing, formant, pitch, channels;
|
||||
double scale;
|
||||
};
|
||||
#include "af.h"
|
||||
|
||||
struct priv {
|
||||
struct f_opts *opts;
|
||||
|
||||
struct mp_pin *in_pin;
|
||||
struct mp_aframe *cur_format;
|
||||
struct mp_aframe_pool *out_pool;
|
||||
bool sent_final;
|
||||
RubberBandState rubber;
|
||||
double speed;
|
||||
double pitch;
|
||||
struct mp_aframe *pending;
|
||||
struct mp_audio *pending;
|
||||
bool needs_reset;
|
||||
// Estimate how much librubberband has buffered internally.
|
||||
// I could not find a way to do this with the librubberband API.
|
||||
double rubber_delay;
|
||||
// command line options
|
||||
int opt_transients, opt_detector, opt_phase, opt_window,
|
||||
opt_smoothing, opt_formant, opt_pitch, opt_channels;
|
||||
};
|
||||
|
||||
static void update_speed(struct priv *p, double new_speed)
|
||||
static void update_speed(struct af_instance *af, double new_speed)
|
||||
{
|
||||
struct priv *p = af->priv;
|
||||
|
||||
p->speed = new_speed;
|
||||
if (p->rubber)
|
||||
rubberband_set_time_ratio(p->rubber, 1.0 / p->speed);
|
||||
rubberband_set_time_ratio(p->rubber, 1.0 / p->speed);
|
||||
}
|
||||
|
||||
static bool update_pitch(struct priv *p, double new_pitch)
|
||||
static void update_pitch(struct af_instance *af, double new_pitch)
|
||||
{
|
||||
if (new_pitch < 0.01 || new_pitch > 100.0)
|
||||
return false;
|
||||
struct priv *p = af->priv;
|
||||
|
||||
p->pitch = new_pitch;
|
||||
if (p->rubber)
|
||||
rubberband_set_pitch_scale(p->rubber, p->pitch);
|
||||
return true;
|
||||
rubberband_set_pitch_scale(p->rubber, p->pitch);
|
||||
}
|
||||
|
||||
static bool init_rubberband(struct mp_filter *f)
|
||||
static int control(struct af_instance *af, int cmd, void *arg)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
struct priv *p = af->priv;
|
||||
|
||||
assert(!p->rubber);
|
||||
assert(p->pending);
|
||||
switch (cmd) {
|
||||
case AF_CONTROL_REINIT: {
|
||||
struct mp_audio *in = arg;
|
||||
struct mp_audio orig_in = *in;
|
||||
struct mp_audio *out = af->data;
|
||||
|
||||
int opts = p->opts->transients | p->opts->detector | p->opts->phase |
|
||||
p->opts->window | p->opts->smoothing | p->opts->formant |
|
||||
p->opts->pitch | p-> opts->channels |
|
||||
RubberBandOptionProcessRealTime;
|
||||
in->format = AF_FORMAT_FLOATP;
|
||||
mp_audio_copy_config(out, in);
|
||||
|
||||
int rate = mp_aframe_get_rate(p->pending);
|
||||
int channels = mp_aframe_get_channels(p->pending);
|
||||
if (mp_aframe_get_format(p->pending) != AF_FORMAT_FLOATP)
|
||||
return false;
|
||||
if (p->rubber)
|
||||
rubberband_delete(p->rubber);
|
||||
|
||||
p->rubber = rubberband_new(rate, channels, opts, 1.0, 1.0);
|
||||
if (!p->rubber) {
|
||||
MP_FATAL(f, "librubberband initialization failed.\n");
|
||||
return false;
|
||||
int opts = p->opt_transients | p->opt_detector | p->opt_phase |
|
||||
p->opt_window | p->opt_smoothing | p->opt_formant |
|
||||
p->opt_pitch | p-> opt_channels |
|
||||
RubberBandOptionProcessRealTime;
|
||||
|
||||
p->rubber = rubberband_new(in->rate, in->channels.num, opts, 1.0, 1.0);
|
||||
if (!p->rubber) {
|
||||
MP_FATAL(af, "librubberband initialization failed.\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
update_speed(af, p->speed);
|
||||
update_pitch(af, p->pitch);
|
||||
control(af, AF_CONTROL_RESET, NULL);
|
||||
|
||||
return mp_audio_config_equals(in, &orig_in) ? AF_OK : AF_FALSE;
|
||||
}
|
||||
|
||||
mp_aframe_config_copy(p->cur_format, p->pending);
|
||||
|
||||
update_speed(p, p->speed);
|
||||
update_pitch(p, p->pitch);
|
||||
|
||||
return true;
|
||||
case AF_CONTROL_SET_PLAYBACK_SPEED: {
|
||||
update_speed(af, *(double *)arg);
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_RESET:
|
||||
if (p->rubber)
|
||||
rubberband_reset(p->rubber);
|
||||
talloc_free(p->pending);
|
||||
p->pending = NULL;
|
||||
p->rubber_delay = 0;
|
||||
return AF_OK;
|
||||
case AF_CONTROL_COMMAND: {
|
||||
char **args = arg;
|
||||
if (!strcmp(args[0], "set-pitch")) {
|
||||
char *endptr;
|
||||
double pitch = strtod(args[1], &endptr);
|
||||
if (*endptr || pitch < 0.01 || pitch > 100.0)
|
||||
return CONTROL_ERROR;
|
||||
update_pitch(af, pitch);
|
||||
return CONTROL_OK;
|
||||
} else {
|
||||
return CONTROL_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static void process(struct mp_filter *f)
|
||||
static int filter_frame(struct af_instance *af, struct mp_audio *data)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
struct priv *p = af->priv;
|
||||
|
||||
if (!mp_pin_in_needs_data(f->ppins[1]))
|
||||
return;
|
||||
talloc_free(p->pending);
|
||||
p->pending = data;
|
||||
|
||||
while (!p->rubber || !p->pending || rubberband_available(p->rubber) <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int filter_out(struct af_instance *af)
|
||||
{
|
||||
struct priv *p = af->priv;
|
||||
|
||||
while (rubberband_available(p->rubber) <= 0) {
|
||||
const float *dummy[MP_NUM_CHANNELS] = {0};
|
||||
const float **in_data = dummy;
|
||||
size_t in_samples = 0;
|
||||
if (p->pending) {
|
||||
if (!p->pending->samples)
|
||||
break;
|
||||
|
||||
bool eof = false;
|
||||
if (!p->pending || !mp_aframe_get_size(p->pending)) {
|
||||
struct mp_frame frame = mp_pin_out_read(p->in_pin);
|
||||
if (frame.type == MP_FRAME_AUDIO) {
|
||||
TA_FREEP(&p->pending);
|
||||
p->pending = frame.data;
|
||||
} else if (frame.type == MP_FRAME_EOF) {
|
||||
eof = true;
|
||||
} else if (frame.type) {
|
||||
MP_ERR(f, "unexpected frame type\n");
|
||||
goto error;
|
||||
} else {
|
||||
return; // no new data yet
|
||||
}
|
||||
}
|
||||
assert(p->pending || eof);
|
||||
|
||||
if (!p->rubber) {
|
||||
if (!p->pending) {
|
||||
mp_pin_in_write(f->ppins[1], MP_EOF_FRAME);
|
||||
return;
|
||||
}
|
||||
if (!init_rubberband(f))
|
||||
goto error;
|
||||
}
|
||||
|
||||
bool format_change =
|
||||
p->pending && !mp_aframe_config_equals(p->pending, p->cur_format);
|
||||
|
||||
if (p->pending && !format_change) {
|
||||
size_t needs = rubberband_get_samples_required(p->rubber);
|
||||
uint8_t **planes = mp_aframe_get_data_ro(p->pending);
|
||||
int num_planes = mp_aframe_get_planes(p->pending);
|
||||
for (int n = 0; n < num_planes; n++)
|
||||
in_data[n] = (void *)planes[n];
|
||||
in_samples = MPMIN(mp_aframe_get_size(p->pending), needs);
|
||||
}
|
||||
|
||||
bool final = format_change || eof;
|
||||
if (!p->sent_final)
|
||||
rubberband_process(p->rubber, in_data, in_samples, final);
|
||||
p->sent_final |= final;
|
||||
|
||||
p->rubber_delay += in_samples;
|
||||
|
||||
if (p->pending && !format_change)
|
||||
mp_aframe_skip_samples(p->pending, in_samples);
|
||||
|
||||
if (rubberband_available(p->rubber) > 0) {
|
||||
if (eof)
|
||||
mp_pin_out_repeat_eof(p->in_pin); // drain more next time
|
||||
} else {
|
||||
if (eof) {
|
||||
mp_pin_in_write(f->ppins[1], MP_EOF_FRAME);
|
||||
// recover from previous EOF
|
||||
if (p->needs_reset) {
|
||||
rubberband_reset(p->rubber);
|
||||
p->rubber_delay = 0;
|
||||
TA_FREEP(&p->pending);
|
||||
p->sent_final = false;
|
||||
return;
|
||||
} else if (format_change) {
|
||||
// go on with proper reinit on the next iteration
|
||||
rubberband_delete(p->rubber);
|
||||
p->sent_final = false;
|
||||
p->rubber = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
p->needs_reset = false;
|
||||
|
||||
assert(p->pending);
|
||||
size_t needs = rubberband_get_samples_required(p->rubber);
|
||||
in_data = (void *)&p->pending->planes;
|
||||
in_samples = MPMIN(p->pending->samples, needs);
|
||||
}
|
||||
|
||||
if (p->needs_reset)
|
||||
break; // previous EOF
|
||||
p->needs_reset = !p->pending; // EOF
|
||||
|
||||
rubberband_process(p->rubber, in_data, in_samples, p->needs_reset);
|
||||
p->rubber_delay += in_samples;
|
||||
|
||||
if (!p->pending)
|
||||
break;
|
||||
mp_audio_skip_samples(p->pending, in_samples);
|
||||
}
|
||||
|
||||
int out_samples = rubberband_available(p->rubber);
|
||||
if (out_samples > 0) {
|
||||
struct mp_aframe *out = mp_aframe_new_ref(p->cur_format);
|
||||
if (mp_aframe_pool_allocate(p->out_pool, out, out_samples) < 0) {
|
||||
talloc_free(out);
|
||||
goto error;
|
||||
}
|
||||
struct mp_audio *out =
|
||||
mp_audio_pool_get(af->out_pool, af->data, out_samples);
|
||||
if (!out)
|
||||
return -1;
|
||||
if (p->pending)
|
||||
mp_audio_copy_config(out, p->pending);
|
||||
|
||||
mp_aframe_copy_attributes(out, p->pending);
|
||||
float **out_data = (void *)&out->planes;
|
||||
out->samples = rubberband_retrieve(p->rubber, out_data, out->samples);
|
||||
p->rubber_delay -= out->samples * p->speed;
|
||||
|
||||
float *out_data[MP_NUM_CHANNELS] = {0};
|
||||
uint8_t **planes = mp_aframe_get_data_rw(out);
|
||||
assert(planes);
|
||||
int num_planes = mp_aframe_get_planes(out);
|
||||
for (int n = 0; n < num_planes; n++)
|
||||
out_data[n] = (void *)planes[n];
|
||||
|
||||
out_samples = rubberband_retrieve(p->rubber, out_data, out_samples);
|
||||
|
||||
if (!out_samples) {
|
||||
mp_filter_internal_mark_progress(f); // unexpected, just try again
|
||||
talloc_free(out);
|
||||
return;
|
||||
}
|
||||
|
||||
mp_aframe_set_size(out, out_samples);
|
||||
|
||||
p->rubber_delay -= out_samples * p->speed;
|
||||
|
||||
double pts = mp_aframe_get_pts(p->pending);
|
||||
if (pts != MP_NOPTS_VALUE) {
|
||||
// Note: rubberband_get_latency() does not do what you'd expect.
|
||||
double delay = p->rubber_delay / mp_aframe_get_effective_rate(out);
|
||||
mp_aframe_set_pts(out, pts - delay);
|
||||
}
|
||||
|
||||
mp_aframe_mul_speed(out, p->speed);
|
||||
|
||||
mp_pin_in_write(f->ppins[1], MAKE_FRAME(MP_FRAME_AUDIO, out));
|
||||
af_add_output_frame(af, out);
|
||||
}
|
||||
|
||||
return;
|
||||
error:
|
||||
mp_filter_internal_mark_failed(f);
|
||||
int delay_samples = p->rubber_delay;
|
||||
if (p->pending)
|
||||
delay_samples += p->pending->samples;
|
||||
af->delay = delay_samples / (af->data->rate * p->speed);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool command(struct mp_filter *f, struct mp_filter_command *cmd)
|
||||
static void uninit(struct af_instance *af)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
|
||||
switch (cmd->type) {
|
||||
case MP_FILTER_COMMAND_TEXT: {
|
||||
char *endptr = NULL;
|
||||
double pitch = p->pitch;
|
||||
if (!strcmp(cmd->cmd, "set-pitch")) {
|
||||
pitch = strtod(cmd->arg, &endptr);
|
||||
if (*endptr)
|
||||
return false;
|
||||
return update_pitch(p, pitch);
|
||||
} else if (!strcmp(cmd->cmd, "multiply-pitch")) {
|
||||
double mult = strtod(cmd->arg, &endptr);
|
||||
if (*endptr || mult <= 0)
|
||||
return false;
|
||||
pitch *= mult;
|
||||
return update_pitch(p, pitch);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case MP_FILTER_COMMAND_SET_SPEED:
|
||||
update_speed(p, cmd->speed);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void reset(struct mp_filter *f)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
|
||||
if (p->rubber)
|
||||
rubberband_reset(p->rubber);
|
||||
p->rubber_delay = 0;
|
||||
p->sent_final = false;
|
||||
TA_FREEP(&p->pending);
|
||||
}
|
||||
|
||||
static void destroy(struct mp_filter *f)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
struct priv *p = af->priv;
|
||||
|
||||
if (p->rubber)
|
||||
rubberband_delete(p->rubber);
|
||||
talloc_free(p->pending);
|
||||
}
|
||||
|
||||
static const struct mp_filter_info af_rubberband_filter = {
|
||||
.name = "rubberband",
|
||||
.priv_size = sizeof(struct priv),
|
||||
.process = process,
|
||||
.command = command,
|
||||
.reset = reset,
|
||||
.destroy = destroy,
|
||||
};
|
||||
|
||||
static struct mp_filter *af_rubberband_create(struct mp_filter *parent,
|
||||
void *options)
|
||||
static int af_open(struct af_instance *af)
|
||||
{
|
||||
struct mp_filter *f = mp_filter_create(parent, &af_rubberband_filter);
|
||||
if (!f) {
|
||||
talloc_free(options);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
mp_filter_add_pin(f, MP_PIN_IN, "in");
|
||||
mp_filter_add_pin(f, MP_PIN_OUT, "out");
|
||||
|
||||
struct priv *p = f->priv;
|
||||
p->opts = talloc_steal(p, options);
|
||||
p->speed = 1.0;
|
||||
p->pitch = p->opts->scale;
|
||||
p->cur_format = talloc_steal(p, mp_aframe_create());
|
||||
p->out_pool = mp_aframe_pool_create(p);
|
||||
|
||||
struct mp_autoconvert *conv = mp_autoconvert_create(f);
|
||||
if (!conv)
|
||||
abort();
|
||||
|
||||
mp_autoconvert_add_afmt(conv, AF_FORMAT_FLOATP);
|
||||
|
||||
mp_pin_connect(conv->f->pins[0], f->ppins[0]);
|
||||
p->in_pin = conv->f->pins[1];
|
||||
|
||||
return f;
|
||||
af->control = control;
|
||||
af->filter_frame = filter_frame;
|
||||
af->filter_out = filter_out;
|
||||
af->uninit = uninit;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct f_opts
|
||||
|
||||
const struct mp_user_filter_entry af_rubberband = {
|
||||
.desc = {
|
||||
.description = "Pitch conversion with librubberband",
|
||||
.name = "rubberband",
|
||||
.priv_size = sizeof(OPT_BASE_STRUCT),
|
||||
.priv_defaults = &(const OPT_BASE_STRUCT) {
|
||||
.scale = 1.0,
|
||||
.pitch = RubberBandOptionPitchHighConsistency,
|
||||
.transients = RubberBandOptionTransientsMixed,
|
||||
.formant = RubberBandOptionFormantPreserved,
|
||||
.channels = RubberBandOptionChannelsTogether,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_CHOICE("transients", transients, 0,
|
||||
({"crisp", RubberBandOptionTransientsCrisp},
|
||||
{"mixed", RubberBandOptionTransientsMixed},
|
||||
{"smooth", RubberBandOptionTransientsSmooth})),
|
||||
OPT_CHOICE("detector", detector, 0,
|
||||
({"compound", RubberBandOptionDetectorCompound},
|
||||
{"percussive", RubberBandOptionDetectorPercussive},
|
||||
{"soft", RubberBandOptionDetectorSoft})),
|
||||
OPT_CHOICE("phase", phase, 0,
|
||||
({"laminar", RubberBandOptionPhaseLaminar},
|
||||
{"independent", RubberBandOptionPhaseIndependent})),
|
||||
OPT_CHOICE("window", window, 0,
|
||||
({"standard", RubberBandOptionWindowStandard},
|
||||
{"short", RubberBandOptionWindowShort},
|
||||
{"long", RubberBandOptionWindowLong})),
|
||||
OPT_CHOICE("smoothing", smoothing, 0,
|
||||
({"off", RubberBandOptionSmoothingOff},
|
||||
{"on", RubberBandOptionSmoothingOn})),
|
||||
OPT_CHOICE("formant", formant, 0,
|
||||
({"shifted", RubberBandOptionFormantShifted},
|
||||
{"preserved", RubberBandOptionFormantPreserved})),
|
||||
OPT_CHOICE("pitch", pitch, 0,
|
||||
({"quality", RubberBandOptionPitchHighQuality},
|
||||
{"speed", RubberBandOptionPitchHighSpeed},
|
||||
{"consistency", RubberBandOptionPitchHighConsistency})),
|
||||
OPT_CHOICE("channels", channels, 0,
|
||||
({"apart", RubberBandOptionChannelsApart},
|
||||
{"together", RubberBandOptionChannelsTogether})),
|
||||
OPT_DOUBLE("pitch-scale", scale, M_OPT_RANGE, .min = 0.01, .max = 100),
|
||||
{0}
|
||||
},
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
const struct af_info af_info_rubberband = {
|
||||
.info = "Pitch conversion with librubberband",
|
||||
.name = "rubberband",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.priv_defaults = &(const struct priv) {
|
||||
.speed = 1.0,
|
||||
.pitch = 1.0,
|
||||
.opt_pitch = RubberBandOptionPitchHighConsistency,
|
||||
.opt_transients = RubberBandOptionTransientsMixed,
|
||||
.opt_formant = RubberBandOptionFormantPreserved,
|
||||
.opt_channels = RubberBandOptionChannelsTogether,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_CHOICE("transients", opt_transients, 0,
|
||||
({"crisp", RubberBandOptionTransientsCrisp},
|
||||
{"mixed", RubberBandOptionTransientsMixed},
|
||||
{"smooth", RubberBandOptionTransientsSmooth})),
|
||||
OPT_CHOICE("detector", opt_detector, 0,
|
||||
({"compound", RubberBandOptionDetectorCompound},
|
||||
{"percussive", RubberBandOptionDetectorPercussive},
|
||||
{"soft", RubberBandOptionDetectorSoft})),
|
||||
OPT_CHOICE("phase", opt_phase, 0,
|
||||
({"laminar", RubberBandOptionPhaseLaminar},
|
||||
{"independent", RubberBandOptionPhaseIndependent})),
|
||||
OPT_CHOICE("window", opt_window, 0,
|
||||
({"standard", RubberBandOptionWindowStandard},
|
||||
{"short", RubberBandOptionWindowShort},
|
||||
{"long", RubberBandOptionWindowLong})),
|
||||
OPT_CHOICE("smoothing", opt_smoothing, 0,
|
||||
({"off", RubberBandOptionSmoothingOff},
|
||||
{"on", RubberBandOptionSmoothingOn})),
|
||||
OPT_CHOICE("formant", opt_formant, 0,
|
||||
({"shifted", RubberBandOptionFormantShifted},
|
||||
{"preserved", RubberBandOptionFormantPreserved})),
|
||||
OPT_CHOICE("pitch", opt_pitch, 0,
|
||||
({"quality", RubberBandOptionPitchHighQuality},
|
||||
{"speed", RubberBandOptionPitchHighSpeed},
|
||||
{"consistency", RubberBandOptionPitchHighConsistency})),
|
||||
OPT_CHOICE("channels", opt_channels, 0,
|
||||
({"apart", RubberBandOptionChannelsApart},
|
||||
{"together", RubberBandOptionChannelsTogether})),
|
||||
OPT_DOUBLE("pitch-scale", pitch, M_OPT_RANGE, .min = 0.01, .max = 100),
|
||||
{0}
|
||||
},
|
||||
.create = af_rubberband_create,
|
||||
};
|
||||
+248
-378
@@ -35,33 +35,14 @@
|
||||
#include <limits.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "audio/aframe.h"
|
||||
#include "audio/format.h"
|
||||
#include "common/common.h"
|
||||
#include "filters/f_autoconvert.h"
|
||||
#include "filters/filter_internal.h"
|
||||
#include "filters/user_filters.h"
|
||||
|
||||
#include "af.h"
|
||||
#include "options/m_option.h"
|
||||
|
||||
struct f_opts {
|
||||
float scale_nominal;
|
||||
float ms_stride;
|
||||
float ms_search;
|
||||
float percent_overlap;
|
||||
#define SCALE_TEMPO 1
|
||||
#define SCALE_PITCH 2
|
||||
int speed_opt;
|
||||
};
|
||||
|
||||
struct priv {
|
||||
struct f_opts *opts;
|
||||
|
||||
struct mp_pin *in_pin;
|
||||
struct mp_aframe *cur_format;
|
||||
struct mp_aframe_pool *out_pool;
|
||||
double current_pts;
|
||||
struct mp_aframe *in;
|
||||
|
||||
// Data for specific instances of this filter
|
||||
typedef struct af_scaletempo_s
|
||||
{
|
||||
// stride
|
||||
float scale;
|
||||
float speed;
|
||||
@@ -81,23 +62,29 @@ struct priv {
|
||||
int bytes_standing;
|
||||
void *buf_overlap;
|
||||
void *table_blend;
|
||||
void (*output_overlap)(struct priv *s, void *out_buf,
|
||||
void (*output_overlap)(struct af_scaletempo_s *s, void *out_buf,
|
||||
int bytes_off);
|
||||
// best overlap
|
||||
int frames_search;
|
||||
int num_channels;
|
||||
void *buf_pre_corr;
|
||||
void *table_window;
|
||||
int (*best_overlap_offset)(struct priv *s);
|
||||
};
|
||||
int (*best_overlap_offset)(struct af_scaletempo_s *s);
|
||||
// command line
|
||||
float scale_nominal;
|
||||
float ms_stride;
|
||||
float percent_overlap;
|
||||
float ms_search;
|
||||
#define SCALE_TEMPO 1
|
||||
#define SCALE_PITCH 2
|
||||
int speed_opt;
|
||||
} af_scaletempo_t;
|
||||
|
||||
static bool reinit(struct mp_filter *f);
|
||||
|
||||
// Return whether it got enough data for filtering.
|
||||
static bool fill_queue(struct priv *s)
|
||||
static int fill_queue(struct af_instance *af, struct mp_audio *data, int offset)
|
||||
{
|
||||
int bytes_in = s->in ? mp_aframe_get_size(s->in) * s->bytes_per_frame : 0;
|
||||
int offset = 0;
|
||||
af_scaletempo_t *s = af->priv;
|
||||
int bytes_in = (data ? mp_audio_psize(data) : 0) - offset;
|
||||
int offset_unchanged = offset;
|
||||
|
||||
if (s->bytes_to_slide > 0) {
|
||||
if (s->bytes_to_slide < s->bytes_queued) {
|
||||
@@ -116,27 +103,21 @@ static bool fill_queue(struct priv *s)
|
||||
}
|
||||
}
|
||||
|
||||
int bytes_needed = s->bytes_queue - s->bytes_queued;
|
||||
assert(bytes_needed >= 0);
|
||||
|
||||
int bytes_copy = MPMIN(bytes_needed, bytes_in);
|
||||
if (bytes_copy > 0) {
|
||||
uint8_t **planes = mp_aframe_get_data_ro(s->in);
|
||||
memcpy(s->buf_queue + s->bytes_queued, planes[0] + offset, bytes_copy);
|
||||
if (bytes_in > 0) {
|
||||
int bytes_copy = MPMIN(s->bytes_queue - s->bytes_queued, bytes_in);
|
||||
assert(bytes_copy >= 0);
|
||||
memcpy(s->buf_queue + s->bytes_queued,
|
||||
(int8_t *)data->planes[0] + offset, bytes_copy);
|
||||
s->bytes_queued += bytes_copy;
|
||||
offset += bytes_copy;
|
||||
bytes_needed -= bytes_copy;
|
||||
}
|
||||
|
||||
if (s->in)
|
||||
mp_aframe_skip_samples(s->in, offset / s->bytes_per_frame);
|
||||
|
||||
return bytes_needed == 0;
|
||||
return offset - offset_unchanged;
|
||||
}
|
||||
|
||||
#define UNROLL_PADDING (4 * 4)
|
||||
|
||||
static int best_overlap_offset_float(struct priv *s)
|
||||
static int best_overlap_offset_float(af_scaletempo_t *s)
|
||||
{
|
||||
float best_corr = INT_MIN;
|
||||
int best_off = 0;
|
||||
@@ -165,7 +146,7 @@ static int best_overlap_offset_float(struct priv *s)
|
||||
return best_off * 4 * s->num_channels;
|
||||
}
|
||||
|
||||
static int best_overlap_offset_s16(struct priv *s)
|
||||
static int best_overlap_offset_s16(af_scaletempo_t *s)
|
||||
{
|
||||
int64_t best_corr = INT64_MIN;
|
||||
int best_off = 0;
|
||||
@@ -202,7 +183,7 @@ static int best_overlap_offset_s16(struct priv *s)
|
||||
return best_off * 2 * s->num_channels;
|
||||
}
|
||||
|
||||
static void output_overlap_float(struct priv *s, void *buf_out,
|
||||
static void output_overlap_float(af_scaletempo_t *s, void *buf_out,
|
||||
int bytes_off)
|
||||
{
|
||||
float *pout = buf_out;
|
||||
@@ -215,7 +196,7 @@ static void output_overlap_float(struct priv *s, void *buf_out,
|
||||
}
|
||||
}
|
||||
|
||||
static void output_overlap_s16(struct priv *s, void *buf_out,
|
||||
static void output_overlap_s16(af_scaletempo_t *s, void *buf_out,
|
||||
int bytes_off)
|
||||
{
|
||||
int16_t *pout = buf_out;
|
||||
@@ -228,77 +209,29 @@ static void output_overlap_s16(struct priv *s, void *buf_out,
|
||||
}
|
||||
}
|
||||
|
||||
static void process(struct mp_filter *f)
|
||||
static int filter(struct af_instance *af, struct mp_audio *data)
|
||||
{
|
||||
struct priv *s = f->priv;
|
||||
af_scaletempo_t *s = af->priv;
|
||||
|
||||
if (!mp_pin_in_needs_data(f->ppins[1]))
|
||||
return;
|
||||
|
||||
struct mp_aframe *out = NULL;
|
||||
|
||||
bool drain = false;
|
||||
bool is_eof = false;
|
||||
if (!s->in) {
|
||||
struct mp_frame frame = mp_pin_out_read(s->in_pin);
|
||||
if (!frame.type)
|
||||
return; // no input yet
|
||||
if (frame.type != MP_FRAME_AUDIO && frame.type != MP_FRAME_EOF) {
|
||||
MP_ERR(f, "unexpected frame type\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
s->in = frame.type == MP_FRAME_AUDIO ? frame.data : NULL;
|
||||
is_eof = drain = !s->in;
|
||||
|
||||
// EOF before it was even initialized once.
|
||||
if (is_eof && !mp_aframe_config_is_valid(s->cur_format)) {
|
||||
mp_pin_in_write(f->ppins[1], MP_EOF_FRAME);
|
||||
return;
|
||||
}
|
||||
|
||||
if (s->in && !mp_aframe_config_equals(s->in, s->cur_format)) {
|
||||
if (s->bytes_queued) {
|
||||
// Drain remaining data before executing the format change.
|
||||
MP_VERBOSE(f, "draining\n");
|
||||
mp_pin_out_unread(s->in_pin, frame);
|
||||
s->in = NULL;
|
||||
drain = true;
|
||||
} else {
|
||||
if (!reinit(f)) {
|
||||
MP_ERR(f, "initialization failed\n");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (s->in)
|
||||
s->current_pts = mp_aframe_end_pts(s->in);
|
||||
if (s->scale == 1.0) {
|
||||
af->delay = 0;
|
||||
af_add_output_frame(af, data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!fill_queue(s) && !drain) {
|
||||
TA_FREEP(&s->in);
|
||||
mp_pin_out_request_data_next(s->in_pin);
|
||||
return;
|
||||
int in_samples = data ? data->samples : 0;
|
||||
struct mp_audio *out = mp_audio_pool_get(af->out_pool, af->data,
|
||||
((int)(in_samples / s->frames_stride_scaled) + 1) * s->frames_stride);
|
||||
if (!out) {
|
||||
talloc_free(data);
|
||||
return -1;
|
||||
}
|
||||
if (data)
|
||||
mp_audio_copy_attributes(out, data);
|
||||
|
||||
int max_out_samples = s->bytes_stride / s->bytes_per_frame;
|
||||
if (drain)
|
||||
max_out_samples += s->bytes_queued;
|
||||
|
||||
out = mp_aframe_new_ref(s->cur_format);
|
||||
if (mp_aframe_pool_allocate(s->out_pool, out, max_out_samples) < 0)
|
||||
goto error;
|
||||
|
||||
if (s->in)
|
||||
mp_aframe_copy_attributes(out, s->in);
|
||||
|
||||
uint8_t **out_planes = mp_aframe_get_data_rw(out);
|
||||
if (!out_planes)
|
||||
goto error;
|
||||
int8_t *pout = out_planes[0];
|
||||
int out_offset = 0;
|
||||
if (s->bytes_queued >= s->bytes_queue) {
|
||||
int offset_in = fill_queue(af, data, 0);
|
||||
int8_t *pout = out->planes[0];
|
||||
while (s->bytes_queued >= s->bytes_queue) {
|
||||
int ti;
|
||||
float tf;
|
||||
int bytes_off = 0;
|
||||
@@ -307,12 +240,12 @@ static void process(struct mp_filter *f)
|
||||
if (s->output_overlap) {
|
||||
if (s->best_overlap_offset)
|
||||
bytes_off = s->best_overlap_offset(s);
|
||||
s->output_overlap(s, pout + out_offset, bytes_off);
|
||||
s->output_overlap(s, pout, bytes_off);
|
||||
}
|
||||
memcpy(pout + out_offset + s->bytes_overlap,
|
||||
memcpy(pout + s->bytes_overlap,
|
||||
s->buf_queue + bytes_off + s->bytes_overlap,
|
||||
s->bytes_standing);
|
||||
out_offset += s->bytes_stride;
|
||||
pout += s->bytes_stride;
|
||||
|
||||
// input stride
|
||||
memcpy(s->buf_overlap,
|
||||
@@ -322,303 +255,240 @@ static void process(struct mp_filter *f)
|
||||
ti = (int)tf;
|
||||
s->frames_stride_error = tf - ti;
|
||||
s->bytes_to_slide = ti * s->bytes_per_frame;
|
||||
|
||||
offset_in += fill_queue(af, data, offset_in);
|
||||
}
|
||||
// Drain remaining buffered data.
|
||||
if (drain && s->bytes_queued) {
|
||||
memcpy(pout + out_offset, s->buf_queue, s->bytes_queued);
|
||||
out_offset += s->bytes_queued;
|
||||
s->bytes_queued = 0;
|
||||
}
|
||||
mp_aframe_set_size(out, out_offset / s->bytes_per_frame);
|
||||
|
||||
// This filter can have a negative delay when scale > 1:
|
||||
// output corresponding to some length of input can be decided and written
|
||||
// after receiving only a part of that input.
|
||||
float delay = (out_offset * s->speed + s->bytes_queued - s->bytes_to_slide) /
|
||||
s->bytes_per_frame / mp_aframe_get_effective_rate(out)
|
||||
+ (s->in ? mp_aframe_duration(s->in) : 0);
|
||||
af->delay = (s->bytes_queued - s->bytes_to_slide) / s->scale
|
||||
/ out->sstride / out->rate;
|
||||
|
||||
if (s->current_pts != MP_NOPTS_VALUE)
|
||||
mp_aframe_set_pts(out, s->current_pts - delay);
|
||||
|
||||
mp_aframe_mul_speed(out, s->speed);
|
||||
|
||||
if (!mp_aframe_get_size(out))
|
||||
TA_FREEP(&out);
|
||||
|
||||
if (is_eof && out) {
|
||||
mp_pin_out_repeat_eof(s->in_pin);
|
||||
} else if (is_eof && !out) {
|
||||
mp_pin_in_write(f->ppins[1], MP_EOF_FRAME);
|
||||
} else if (!is_eof && !out) {
|
||||
mp_pin_out_request_data_next(s->in_pin);
|
||||
out->samples = (pout - (int8_t *)out->planes[0]) / out->sstride;
|
||||
talloc_free(data);
|
||||
if (out->samples) {
|
||||
af_add_output_frame(af, out);
|
||||
} else {
|
||||
talloc_free(out);
|
||||
}
|
||||
|
||||
if (out)
|
||||
mp_pin_in_write(f->ppins[1], MAKE_FRAME(MP_FRAME_AUDIO, out));
|
||||
|
||||
return;
|
||||
|
||||
error:
|
||||
TA_FREEP(&s->in);
|
||||
talloc_free(out);
|
||||
mp_filter_internal_mark_failed(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void update_speed(struct priv *s, float speed)
|
||||
static void update_speed(struct af_instance *af, float speed)
|
||||
{
|
||||
af_scaletempo_t *s = af->priv;
|
||||
|
||||
s->speed = speed;
|
||||
|
||||
double factor = (s->opts->speed_opt & SCALE_PITCH) ? 1.0 / s->speed : s->speed;
|
||||
s->scale = factor * s->opts->scale_nominal;
|
||||
double factor = (s->speed_opt & SCALE_PITCH) ? 1.0 / s->speed : s->speed;
|
||||
s->scale = factor * s->scale_nominal;
|
||||
|
||||
s->frames_stride_scaled = s->scale * s->frames_stride;
|
||||
s->frames_stride_error = MPMIN(s->frames_stride_error, s->frames_stride_scaled);
|
||||
}
|
||||
|
||||
static bool reinit(struct mp_filter *f)
|
||||
// Initialization and runtime control
|
||||
static int control(struct af_instance *af, int cmd, void *arg)
|
||||
{
|
||||
struct priv *s = f->priv;
|
||||
af_scaletempo_t *s = af->priv;
|
||||
switch (cmd) {
|
||||
case AF_CONTROL_REINIT: {
|
||||
struct mp_audio *data = (struct mp_audio *)arg;
|
||||
float srate = data->rate / 1000.0;
|
||||
int nch = data->nch;
|
||||
int use_int = 0;
|
||||
|
||||
mp_aframe_reset(s->cur_format);
|
||||
mp_audio_force_interleaved_format(data);
|
||||
mp_audio_copy_config(af->data, data);
|
||||
|
||||
float srate = mp_aframe_get_rate(s->in) / 1000.0;
|
||||
int nch = mp_aframe_get_channels(s->in);
|
||||
int format = mp_aframe_get_format(s->in);
|
||||
|
||||
int use_int = 0;
|
||||
if (format == AF_FORMAT_S16) {
|
||||
use_int = 1;
|
||||
} else if (format != AF_FORMAT_FLOAT) {
|
||||
return false;
|
||||
}
|
||||
int bps = use_int ? 2 : 4;
|
||||
|
||||
s->frames_stride = srate * s->opts->ms_stride;
|
||||
s->bytes_stride = s->frames_stride * bps * nch;
|
||||
|
||||
update_speed(s, s->speed);
|
||||
|
||||
int frames_overlap = s->frames_stride * s->opts->percent_overlap;
|
||||
if (frames_overlap <= 0) {
|
||||
s->bytes_standing = s->bytes_stride;
|
||||
s->samples_standing = s->bytes_standing / bps;
|
||||
s->output_overlap = NULL;
|
||||
s->bytes_overlap = 0;
|
||||
} else {
|
||||
s->samples_overlap = frames_overlap * nch;
|
||||
s->bytes_overlap = frames_overlap * nch * bps;
|
||||
s->bytes_standing = s->bytes_stride - s->bytes_overlap;
|
||||
s->samples_standing = s->bytes_standing / bps;
|
||||
s->buf_overlap = realloc(s->buf_overlap, s->bytes_overlap);
|
||||
s->table_blend = realloc(s->table_blend, s->bytes_overlap * 4);
|
||||
if (!s->buf_overlap || !s->table_blend) {
|
||||
MP_FATAL(f, "Out of memory\n");
|
||||
return false;
|
||||
if (data->format == AF_FORMAT_S16) {
|
||||
use_int = 1;
|
||||
} else {
|
||||
mp_audio_set_format(af->data, AF_FORMAT_FLOAT);
|
||||
}
|
||||
int bps = af->data->bps;
|
||||
|
||||
s->frames_stride = srate * s->ms_stride;
|
||||
s->bytes_stride = s->frames_stride * bps * nch;
|
||||
af->delay = 0;
|
||||
|
||||
update_speed(af, s->speed);
|
||||
|
||||
int frames_overlap = s->frames_stride * s->percent_overlap;
|
||||
if (frames_overlap <= 0) {
|
||||
s->bytes_standing = s->bytes_stride;
|
||||
s->samples_standing = s->bytes_standing / bps;
|
||||
s->output_overlap = NULL;
|
||||
s->bytes_overlap = 0;
|
||||
} else {
|
||||
s->samples_overlap = frames_overlap * nch;
|
||||
s->bytes_overlap = frames_overlap * nch * bps;
|
||||
s->bytes_standing = s->bytes_stride - s->bytes_overlap;
|
||||
s->samples_standing = s->bytes_standing / bps;
|
||||
s->buf_overlap = realloc(s->buf_overlap, s->bytes_overlap);
|
||||
s->table_blend = realloc(s->table_blend, s->bytes_overlap * 4);
|
||||
if (!s->buf_overlap || !s->table_blend) {
|
||||
MP_FATAL(af, "Out of memory\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
memset(s->buf_overlap, 0, s->bytes_overlap);
|
||||
if (use_int) {
|
||||
int32_t *pb = s->table_blend;
|
||||
int64_t blend = 0;
|
||||
for (int i = 0; i < frames_overlap; i++) {
|
||||
int32_t v = blend / frames_overlap;
|
||||
for (int j = 0; j < nch; j++)
|
||||
*pb++ = v;
|
||||
blend += 65536; // 2^16
|
||||
}
|
||||
s->output_overlap = output_overlap_s16;
|
||||
} else {
|
||||
float *pb = s->table_blend;
|
||||
for (int i = 0; i < frames_overlap; i++) {
|
||||
float v = i / (float)frames_overlap;
|
||||
for (int j = 0; j < nch; j++)
|
||||
*pb++ = v;
|
||||
}
|
||||
s->output_overlap = output_overlap_float;
|
||||
}
|
||||
}
|
||||
|
||||
s->frames_search = (frames_overlap > 1) ? srate * s->ms_search : 0;
|
||||
if (s->frames_search <= 0)
|
||||
s->best_overlap_offset = NULL;
|
||||
else {
|
||||
if (use_int) {
|
||||
int64_t t = frames_overlap;
|
||||
int32_t n = 8589934588LL / (t * t); // 4 * (2^31 - 1) / t^2
|
||||
s->buf_pre_corr = realloc(s->buf_pre_corr,
|
||||
s->bytes_overlap * 2 + UNROLL_PADDING);
|
||||
s->table_window = realloc(s->table_window,
|
||||
s->bytes_overlap * 2 - nch * bps * 2);
|
||||
if (!s->buf_pre_corr || !s->table_window) {
|
||||
MP_FATAL(af, "Out of memory\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
memset((char *)s->buf_pre_corr + s->bytes_overlap * 2, 0,
|
||||
UNROLL_PADDING);
|
||||
int32_t *pw = s->table_window;
|
||||
for (int i = 1; i < frames_overlap; i++) {
|
||||
int32_t v = (i * (t - i) * n) >> 15;
|
||||
for (int j = 0; j < nch; j++)
|
||||
*pw++ = v;
|
||||
}
|
||||
s->best_overlap_offset = best_overlap_offset_s16;
|
||||
} else {
|
||||
s->buf_pre_corr = realloc(s->buf_pre_corr, s->bytes_overlap);
|
||||
s->table_window = realloc(s->table_window,
|
||||
s->bytes_overlap - nch * bps);
|
||||
if (!s->buf_pre_corr || !s->table_window) {
|
||||
MP_FATAL(af, "Out of memory\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
float *pw = s->table_window;
|
||||
for (int i = 1; i < frames_overlap; i++) {
|
||||
float v = i * (frames_overlap - i);
|
||||
for (int j = 0; j < nch; j++)
|
||||
*pw++ = v;
|
||||
}
|
||||
s->best_overlap_offset = best_overlap_offset_float;
|
||||
}
|
||||
}
|
||||
|
||||
s->bytes_per_frame = bps * nch;
|
||||
s->num_channels = nch;
|
||||
|
||||
s->bytes_queue = (s->frames_search + s->frames_stride + frames_overlap)
|
||||
* bps * nch;
|
||||
s->buf_queue = realloc(s->buf_queue, s->bytes_queue + UNROLL_PADDING);
|
||||
if (!s->buf_queue) {
|
||||
MP_FATAL(af, "Out of memory\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
s->bytes_queued = 0;
|
||||
s->bytes_to_slide = 0;
|
||||
|
||||
MP_DBG(af, ""
|
||||
"%.2f stride_in, %i stride_out, %i standing, "
|
||||
"%i overlap, %i search, %i queue, %s mode\n",
|
||||
s->frames_stride_scaled,
|
||||
(int)(s->bytes_stride / nch / bps),
|
||||
(int)(s->bytes_standing / nch / bps),
|
||||
(int)(s->bytes_overlap / nch / bps),
|
||||
s->frames_search,
|
||||
(int)(s->bytes_queue / nch / bps),
|
||||
(use_int ? "s16" : "float"));
|
||||
|
||||
return af_test_output(af, (struct mp_audio *)arg);
|
||||
}
|
||||
case AF_CONTROL_SET_PLAYBACK_SPEED: {
|
||||
double speed = *(double *)arg;
|
||||
if (s->speed_opt & SCALE_TEMPO) {
|
||||
if (s->speed_opt & SCALE_PITCH)
|
||||
break;
|
||||
update_speed(af, speed);
|
||||
} else if (s->speed_opt & SCALE_PITCH) {
|
||||
update_speed(af, speed);
|
||||
break; // do not signal OK
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_RESET:
|
||||
s->bytes_queued = 0;
|
||||
s->bytes_to_slide = 0;
|
||||
s->frames_stride_error = 0;
|
||||
memset(s->buf_overlap, 0, s->bytes_overlap);
|
||||
if (use_int) {
|
||||
int32_t *pb = s->table_blend;
|
||||
int64_t blend = 0;
|
||||
for (int i = 0; i < frames_overlap; i++) {
|
||||
int32_t v = blend / frames_overlap;
|
||||
for (int j = 0; j < nch; j++)
|
||||
*pb++ = v;
|
||||
blend += 65536; // 2^16
|
||||
}
|
||||
s->output_overlap = output_overlap_s16;
|
||||
} else {
|
||||
float *pb = s->table_blend;
|
||||
for (int i = 0; i < frames_overlap; i++) {
|
||||
float v = i / (float)frames_overlap;
|
||||
for (int j = 0; j < nch; j++)
|
||||
*pb++ = v;
|
||||
}
|
||||
s->output_overlap = output_overlap_float;
|
||||
}
|
||||
}
|
||||
|
||||
s->frames_search = (frames_overlap > 1) ? srate * s->opts->ms_search : 0;
|
||||
if (s->frames_search <= 0)
|
||||
s->best_overlap_offset = NULL;
|
||||
else {
|
||||
if (use_int) {
|
||||
int64_t t = frames_overlap;
|
||||
int32_t n = 8589934588LL / (t * t); // 4 * (2^31 - 1) / t^2
|
||||
s->buf_pre_corr = realloc(s->buf_pre_corr,
|
||||
s->bytes_overlap * 2 + UNROLL_PADDING);
|
||||
s->table_window = realloc(s->table_window,
|
||||
s->bytes_overlap * 2 - nch * bps * 2);
|
||||
if (!s->buf_pre_corr || !s->table_window) {
|
||||
MP_FATAL(f, "Out of memory\n");
|
||||
return false;
|
||||
}
|
||||
memset((char *)s->buf_pre_corr + s->bytes_overlap * 2, 0,
|
||||
UNROLL_PADDING);
|
||||
int32_t *pw = s->table_window;
|
||||
for (int i = 1; i < frames_overlap; i++) {
|
||||
int32_t v = (i * (t - i) * n) >> 15;
|
||||
for (int j = 0; j < nch; j++)
|
||||
*pw++ = v;
|
||||
}
|
||||
s->best_overlap_offset = best_overlap_offset_s16;
|
||||
} else {
|
||||
s->buf_pre_corr = realloc(s->buf_pre_corr, s->bytes_overlap);
|
||||
s->table_window = realloc(s->table_window,
|
||||
s->bytes_overlap - nch * bps);
|
||||
if (!s->buf_pre_corr || !s->table_window) {
|
||||
MP_FATAL(f, "Out of memory\n");
|
||||
return false;
|
||||
}
|
||||
float *pw = s->table_window;
|
||||
for (int i = 1; i < frames_overlap; i++) {
|
||||
float v = i * (frames_overlap - i);
|
||||
for (int j = 0; j < nch; j++)
|
||||
*pw++ = v;
|
||||
}
|
||||
s->best_overlap_offset = best_overlap_offset_float;
|
||||
}
|
||||
}
|
||||
|
||||
s->bytes_per_frame = bps * nch;
|
||||
s->num_channels = nch;
|
||||
|
||||
s->bytes_queue = (s->frames_search + s->frames_stride + frames_overlap)
|
||||
* bps * nch;
|
||||
s->buf_queue = realloc(s->buf_queue, s->bytes_queue + UNROLL_PADDING);
|
||||
if (!s->buf_queue) {
|
||||
MP_FATAL(f, "Out of memory\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
s->bytes_queued = 0;
|
||||
s->bytes_to_slide = 0;
|
||||
|
||||
MP_DBG(f, ""
|
||||
"%.2f stride_in, %i stride_out, %i standing, "
|
||||
"%i overlap, %i search, %i queue, %s mode\n",
|
||||
s->frames_stride_scaled,
|
||||
(int)(s->bytes_stride / nch / bps),
|
||||
(int)(s->bytes_standing / nch / bps),
|
||||
(int)(s->bytes_overlap / nch / bps),
|
||||
s->frames_search,
|
||||
(int)(s->bytes_queue / nch / bps),
|
||||
(use_int ? "s16" : "float"));
|
||||
|
||||
mp_aframe_config_copy(s->cur_format, s->in);
|
||||
|
||||
return true;
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static bool command(struct mp_filter *f, struct mp_filter_command *cmd)
|
||||
// Deallocate memory
|
||||
static void uninit(struct af_instance *af)
|
||||
{
|
||||
struct priv *s = f->priv;
|
||||
|
||||
if (cmd->type == MP_FILTER_COMMAND_SET_SPEED) {
|
||||
if (s->opts->speed_opt & SCALE_TEMPO) {
|
||||
if (s->opts->speed_opt & SCALE_PITCH)
|
||||
return false;
|
||||
update_speed(s, cmd->speed);
|
||||
return true;
|
||||
} else if (s->opts->speed_opt & SCALE_PITCH) {
|
||||
update_speed(s, cmd->speed);
|
||||
return false; // do not signal OK
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void reset(struct mp_filter *f)
|
||||
{
|
||||
struct priv *s = f->priv;
|
||||
|
||||
s->current_pts = MP_NOPTS_VALUE;
|
||||
s->bytes_queued = 0;
|
||||
s->bytes_to_slide = 0;
|
||||
s->frames_stride_error = 0;
|
||||
memset(s->buf_overlap, 0, s->bytes_overlap);
|
||||
TA_FREEP(&s->in);
|
||||
}
|
||||
|
||||
static void destroy(struct mp_filter *f)
|
||||
{
|
||||
struct priv *s = f->priv;
|
||||
af_scaletempo_t *s = af->priv;
|
||||
free(s->buf_queue);
|
||||
free(s->buf_overlap);
|
||||
free(s->buf_pre_corr);
|
||||
free(s->table_blend);
|
||||
free(s->table_window);
|
||||
TA_FREEP(&s->in);
|
||||
mp_filter_free_children(f);
|
||||
}
|
||||
|
||||
static const struct mp_filter_info af_scaletempo_filter = {
|
||||
.name = "scaletempo",
|
||||
.priv_size = sizeof(struct priv),
|
||||
.process = process,
|
||||
.command = command,
|
||||
.reset = reset,
|
||||
.destroy = destroy,
|
||||
};
|
||||
|
||||
static struct mp_filter *af_scaletempo_create(struct mp_filter *parent,
|
||||
void *options)
|
||||
// Allocate memory and set function pointers
|
||||
static int af_open(struct af_instance *af)
|
||||
{
|
||||
struct mp_filter *f = mp_filter_create(parent, &af_scaletempo_filter);
|
||||
if (!f) {
|
||||
talloc_free(options);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
mp_filter_add_pin(f, MP_PIN_IN, "in");
|
||||
mp_filter_add_pin(f, MP_PIN_OUT, "out");
|
||||
|
||||
struct priv *s = f->priv;
|
||||
s->opts = talloc_steal(s, options);
|
||||
s->speed = 1.0;
|
||||
s->cur_format = talloc_steal(s, mp_aframe_create());
|
||||
s->out_pool = mp_aframe_pool_create(s);
|
||||
|
||||
struct mp_autoconvert *conv = mp_autoconvert_create(f);
|
||||
if (!conv)
|
||||
abort();
|
||||
|
||||
mp_autoconvert_add_afmt(conv, AF_FORMAT_S16);
|
||||
mp_autoconvert_add_afmt(conv, AF_FORMAT_FLOAT);
|
||||
|
||||
mp_pin_connect(conv->f->pins[0], f->ppins[0]);
|
||||
s->in_pin = conv->f->pins[1];
|
||||
|
||||
return f;
|
||||
af->control = control;
|
||||
af->uninit = uninit;
|
||||
af->filter_frame = filter;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct f_opts
|
||||
#define OPT_BASE_STRUCT af_scaletempo_t
|
||||
|
||||
const struct mp_user_filter_entry af_scaletempo = {
|
||||
.desc = {
|
||||
.description = "Scale audio tempo while maintaining pitch",
|
||||
.name = "scaletempo",
|
||||
.priv_size = sizeof(OPT_BASE_STRUCT),
|
||||
.priv_defaults = &(const OPT_BASE_STRUCT) {
|
||||
.ms_stride = 60,
|
||||
.percent_overlap = .20,
|
||||
.ms_search = 14,
|
||||
.speed_opt = SCALE_TEMPO,
|
||||
.scale_nominal = 1.0,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_FLOAT("scale", scale_nominal, M_OPT_MIN, .min = 0.01),
|
||||
OPT_FLOAT("stride", ms_stride, M_OPT_MIN, .min = 0.01),
|
||||
OPT_FLOAT("overlap", percent_overlap, M_OPT_RANGE, .min = 0, .max = 1),
|
||||
OPT_FLOAT("search", ms_search, M_OPT_MIN, .min = 0),
|
||||
OPT_CHOICE("speed", speed_opt, 0,
|
||||
({"pitch", SCALE_PITCH},
|
||||
{"tempo", SCALE_TEMPO},
|
||||
{"none", 0},
|
||||
{"both", SCALE_TEMPO | SCALE_PITCH})),
|
||||
{0}
|
||||
},
|
||||
const struct af_info af_info_scaletempo = {
|
||||
.info = "Scale audio tempo while maintaining pitch",
|
||||
.name = "scaletempo",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(af_scaletempo_t),
|
||||
.priv_defaults = &(const af_scaletempo_t) {
|
||||
.ms_stride = 60,
|
||||
.percent_overlap = .20,
|
||||
.ms_search = 14,
|
||||
.speed_opt = SCALE_TEMPO,
|
||||
.speed = 1.0,
|
||||
.scale_nominal = 1.0,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_FLOAT("scale", scale_nominal, M_OPT_MIN, .min = 0.01),
|
||||
OPT_FLOAT("stride", ms_stride, M_OPT_MIN, .min = 0.01),
|
||||
OPT_FLOAT("overlap", percent_overlap, M_OPT_RANGE, .min = 0, .max = 1),
|
||||
OPT_FLOAT("search", ms_search, M_OPT_MIN, .min = 0),
|
||||
OPT_CHOICE("speed", speed_opt, 0,
|
||||
({"pitch", SCALE_PITCH},
|
||||
{"tempo", SCALE_TEMPO},
|
||||
{"none", 0},
|
||||
{"both", SCALE_TEMPO | SCALE_PITCH})),
|
||||
{0}
|
||||
},
|
||||
.create = af_scaletempo_create,
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2002 Anders Johansson ajh@atri.curtin.edu.au
|
||||
*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MPLAYER_EQUALIZER_H
|
||||
#define MPLAYER_EQUALIZER_H
|
||||
|
||||
/* Equalizer plugin header file defines struct used for setting or
|
||||
getting the gain of a specific channel and frequency */
|
||||
|
||||
typedef struct equalizer_s
|
||||
{
|
||||
float gain; // Gain in dB -15 - 15
|
||||
int channel; // Channel number 0 - 5
|
||||
int band; // Frequency band 0 - 9
|
||||
}equalizer_t;
|
||||
|
||||
/* The different frequency bands are:
|
||||
nr. center frequency
|
||||
0 31.25 Hz
|
||||
1 62.50 Hz
|
||||
2 125.0 Hz
|
||||
3 250.0 Hz
|
||||
4 500.0 Hz
|
||||
5 1.000 kHz
|
||||
6 2.000 kHz
|
||||
7 4.000 kHz
|
||||
8 8.000 kHz
|
||||
9 16.00 kHz
|
||||
*/
|
||||
|
||||
#endif /* MPLAYER_EQUALIZER_H */
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "af.h"
|
||||
|
||||
/* Convert from ms to sample time */
|
||||
int af_from_ms(int n, float* in, int* out, int rate, float mi, float ma)
|
||||
{
|
||||
int i = 0;
|
||||
// Sanity check
|
||||
if(!in || !out)
|
||||
return AF_ERROR;
|
||||
|
||||
for(i=0;i<n;i++)
|
||||
out[i]=(int)((float)rate * MPCLAMP(in[i],mi,ma)/1000.0);
|
||||
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* test if output format matches
|
||||
* af: audio filter
|
||||
* out: needed format, will be overwritten by available
|
||||
* format if they do not match
|
||||
* returns: AF_FALSE if formats do not match, AF_OK if they match
|
||||
*
|
||||
* compares the format, rate and nch values of af->data with out
|
||||
* Note: logically, *out=*af->data always happens, because out contains the
|
||||
* format only, no actual audio data or memory allocations. *out always
|
||||
* contains the parameters from af->data after the function returns.
|
||||
*/
|
||||
int af_test_output(struct af_instance* af, struct mp_audio* out)
|
||||
{
|
||||
if((af->data->format != out->format) ||
|
||||
(af->data->bps != out->bps) ||
|
||||
(af->data->rate != out->rate) ||
|
||||
!mp_chmap_equals(&af->data->channels, &out->channels)){
|
||||
*out = *af->data;
|
||||
return AF_FALSE;
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
/* Soft clipping, the sound of a dream, thanks to Jon Wattes
|
||||
post to Musicdsp.org */
|
||||
float af_softclip(float a)
|
||||
{
|
||||
if (a >= M_PI/2)
|
||||
return 1.0;
|
||||
else if (a <= -M_PI/2)
|
||||
return -1.0;
|
||||
else
|
||||
return sin(a);
|
||||
}
|
||||
@@ -27,14 +27,12 @@ static const struct {
|
||||
{AV_SAMPLE_FMT_U8, AF_FORMAT_U8},
|
||||
{AV_SAMPLE_FMT_S16, AF_FORMAT_S16},
|
||||
{AV_SAMPLE_FMT_S32, AF_FORMAT_S32},
|
||||
{AV_SAMPLE_FMT_S64, AF_FORMAT_S64},
|
||||
{AV_SAMPLE_FMT_FLT, AF_FORMAT_FLOAT},
|
||||
{AV_SAMPLE_FMT_DBL, AF_FORMAT_DOUBLE},
|
||||
|
||||
{AV_SAMPLE_FMT_U8P, AF_FORMAT_U8P},
|
||||
{AV_SAMPLE_FMT_S16P, AF_FORMAT_S16P},
|
||||
{AV_SAMPLE_FMT_S32P, AF_FORMAT_S32P},
|
||||
{AV_SAMPLE_FMT_S64P, AF_FORMAT_S64P},
|
||||
{AV_SAMPLE_FMT_FLTP, AF_FORMAT_FLOATP},
|
||||
{AV_SAMPLE_FMT_DBLP, AF_FORMAT_DOUBLEP},
|
||||
|
||||
|
||||
+27
-22
@@ -29,7 +29,6 @@ int af_fmt_to_bytes(int format)
|
||||
case AF_FORMAT_U8: return 1;
|
||||
case AF_FORMAT_S16: return 2;
|
||||
case AF_FORMAT_S32: return 4;
|
||||
case AF_FORMAT_S64: return 8;
|
||||
case AF_FORMAT_FLOAT: return 4;
|
||||
case AF_FORMAT_DOUBLE: return 8;
|
||||
}
|
||||
@@ -56,6 +55,12 @@ bool af_fmt_is_int(int format)
|
||||
return format && !af_fmt_is_spdif(format) && !af_fmt_is_float(format);
|
||||
}
|
||||
|
||||
// false for interleaved and AF_FORMAT_UNKNOWN
|
||||
bool af_fmt_is_planar(int format)
|
||||
{
|
||||
return format && af_fmt_to_planar(format) == format;
|
||||
}
|
||||
|
||||
bool af_fmt_is_spdif(int format)
|
||||
{
|
||||
return af_format_sample_alignment(format) > 1;
|
||||
@@ -70,35 +75,27 @@ static const int planar_formats[][2] = {
|
||||
{AF_FORMAT_U8P, AF_FORMAT_U8},
|
||||
{AF_FORMAT_S16P, AF_FORMAT_S16},
|
||||
{AF_FORMAT_S32P, AF_FORMAT_S32},
|
||||
{AF_FORMAT_S64P, AF_FORMAT_S64},
|
||||
{AF_FORMAT_FLOATP, AF_FORMAT_FLOAT},
|
||||
{AF_FORMAT_DOUBLEP, AF_FORMAT_DOUBLE},
|
||||
};
|
||||
|
||||
bool af_fmt_is_planar(int format)
|
||||
{
|
||||
for (int n = 0; n < MP_ARRAY_SIZE(planar_formats); n++) {
|
||||
if (planar_formats[n][0] == format)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Return the planar format corresponding to the given format.
|
||||
// If the format is already planar or if there's no equivalent,
|
||||
// return it.
|
||||
// If the format is already planar, return it.
|
||||
// Return 0 if there's no equivalent.
|
||||
int af_fmt_to_planar(int format)
|
||||
{
|
||||
for (int n = 0; n < MP_ARRAY_SIZE(planar_formats); n++) {
|
||||
if (planar_formats[n][1] == format)
|
||||
return planar_formats[n][0];
|
||||
if (planar_formats[n][0] == format)
|
||||
return format;
|
||||
}
|
||||
return format;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Return the interleaved format corresponding to the given format.
|
||||
// If the format is already interleaved or if there's no equivalent,
|
||||
// return it.
|
||||
// If the format is already interleaved, return it.
|
||||
// Always succeeds if format is actually planar; otherwise return 0.
|
||||
int af_fmt_from_planar(int format)
|
||||
{
|
||||
for (int n = 0; n < MP_ARRAY_SIZE(planar_formats); n++) {
|
||||
@@ -119,13 +116,11 @@ const char *af_fmt_to_str(int format)
|
||||
case AF_FORMAT_U8: return "u8";
|
||||
case AF_FORMAT_S16: return "s16";
|
||||
case AF_FORMAT_S32: return "s32";
|
||||
case AF_FORMAT_S64: return "s64";
|
||||
case AF_FORMAT_FLOAT: return "float";
|
||||
case AF_FORMAT_DOUBLE: return "double";
|
||||
case AF_FORMAT_U8P: return "u8p";
|
||||
case AF_FORMAT_S16P: return "s16p";
|
||||
case AF_FORMAT_S32P: return "s32p";
|
||||
case AF_FORMAT_S64P: return "s64p";
|
||||
case AF_FORMAT_FLOATP: return "floatp";
|
||||
case AF_FORMAT_DOUBLEP: return "doublep";
|
||||
case AF_FORMAT_S_AAC: return "spdif-aac";
|
||||
@@ -139,6 +134,17 @@ const char *af_fmt_to_str(int format)
|
||||
return "??";
|
||||
}
|
||||
|
||||
int af_fmt_seconds_to_bytes(int format, float seconds, int channels, int samplerate)
|
||||
{
|
||||
assert(!af_fmt_is_planar(format));
|
||||
int bps = af_fmt_to_bytes(format);
|
||||
int framelen = channels * bps;
|
||||
int bytes = seconds * bps * samplerate;
|
||||
if (bytes % framelen)
|
||||
bytes += framelen - (bytes % framelen);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
void af_fill_silence(void *dst, size_t bytes, int format)
|
||||
{
|
||||
memset(dst, af_fmt_is_unsigned(format) ? 0x80 : 0, bytes);
|
||||
@@ -148,7 +154,7 @@ void af_fill_silence(void *dst, size_t bytes, int format)
|
||||
// If the formats are equal, 1024 is returned. If they are gravely incompatible
|
||||
// (like s16<->ac3), INT_MIN is returned. If there is implied loss of precision
|
||||
// (like s16->s8), a value <0 is returned.
|
||||
int af_format_conversion_score(int dst_format, int src_format)
|
||||
static int af_format_conversion_score(int dst_format, int src_format)
|
||||
{
|
||||
if (dst_format == AF_FORMAT_UNKNOWN || src_format == AF_FORMAT_UNKNOWN)
|
||||
return INT_MIN;
|
||||
@@ -203,11 +209,10 @@ static int cmp_entry(const void *a, const void *b)
|
||||
// and the list is terminated with 0 (AF_FORMAT_UNKNOWN).
|
||||
// Keep in mind that this also returns formats with flipped interleaving
|
||||
// (e.g. for s16, it returns [s16, s16p, ...]).
|
||||
// out_formats must be an int[AF_FORMAT_COUNT + 1] array.
|
||||
void af_get_best_sample_formats(int src_format, int *out_formats)
|
||||
void af_get_best_sample_formats(int src_format, int out_formats[AF_FORMAT_COUNT])
|
||||
{
|
||||
int num = 0;
|
||||
struct entry e[AF_FORMAT_COUNT + 1];
|
||||
struct entry e[AF_FORMAT_COUNT];
|
||||
for (int fmt = 1; fmt < AF_FORMAT_COUNT; fmt++) {
|
||||
int score = af_format_conversion_score(fmt, src_format);
|
||||
if (score > INT_MIN)
|
||||
|
||||
+4
-4
@@ -27,7 +27,6 @@ enum af_format {
|
||||
AF_FORMAT_U8,
|
||||
AF_FORMAT_S16,
|
||||
AF_FORMAT_S32,
|
||||
AF_FORMAT_S64,
|
||||
AF_FORMAT_FLOAT,
|
||||
AF_FORMAT_DOUBLE,
|
||||
|
||||
@@ -35,7 +34,6 @@ enum af_format {
|
||||
AF_FORMAT_U8P,
|
||||
AF_FORMAT_S16P,
|
||||
AF_FORMAT_S32P,
|
||||
AF_FORMAT_S64P,
|
||||
AF_FORMAT_FLOATP,
|
||||
AF_FORMAT_DOUBLEP,
|
||||
|
||||
@@ -66,10 +64,12 @@ bool af_fmt_is_pcm(int format);
|
||||
int af_fmt_to_planar(int format);
|
||||
int af_fmt_from_planar(int format);
|
||||
|
||||
// Amount of bytes that contain audio of the given duration, aligned to frames.
|
||||
int af_fmt_seconds_to_bytes(int format, float seconds, int channels, int samplerate);
|
||||
|
||||
void af_fill_silence(void *dst, size_t bytes, int format);
|
||||
|
||||
void af_get_best_sample_formats(int src_format, int *out_formats);
|
||||
int af_format_conversion_score(int dst_format, int src_format);
|
||||
void af_get_best_sample_formats(int src_format, int out_formats[AF_FORMAT_COUNT]);
|
||||
int af_select_best_samplerate(int src_sampelrate, const int *available);
|
||||
|
||||
int af_format_sample_alignment(int format);
|
||||
|
||||
+7
-43
@@ -36,7 +36,6 @@
|
||||
#include "common/global.h"
|
||||
|
||||
extern const struct ao_driver audio_out_oss;
|
||||
extern const struct ao_driver audio_out_audiotrack;
|
||||
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;
|
||||
@@ -55,9 +54,6 @@ extern const struct ao_driver audio_out_sdl;
|
||||
|
||||
static const struct ao_driver * const audio_out_drivers[] = {
|
||||
// native:
|
||||
#if HAVE_ANDROID
|
||||
&audio_out_audiotrack,
|
||||
#endif
|
||||
#if HAVE_AUDIOUNIT
|
||||
&audio_out_audiounit,
|
||||
#endif
|
||||
@@ -86,7 +82,7 @@ static const struct ao_driver * const audio_out_drivers[] = {
|
||||
#if HAVE_OPENSLES
|
||||
&audio_out_opensles,
|
||||
#endif
|
||||
#if HAVE_SDL2_AUDIO
|
||||
#if HAVE_SDL1 || HAVE_SDL2
|
||||
&audio_out_sdl,
|
||||
#endif
|
||||
#if HAVE_SNDIO
|
||||
@@ -97,7 +93,9 @@ static const struct ao_driver * const audio_out_drivers[] = {
|
||||
&audio_out_coreaudio_exclusive,
|
||||
#endif
|
||||
&audio_out_pcm,
|
||||
#if HAVE_ENCODING
|
||||
&audio_out_lavc,
|
||||
#endif
|
||||
#if HAVE_RSOUND
|
||||
&audio_out_rsound,
|
||||
#endif
|
||||
@@ -124,7 +122,7 @@ static bool get_desc(struct m_obj_desc *dst, int index)
|
||||
}
|
||||
|
||||
// For the ao option
|
||||
static const struct m_obj_list ao_obj_list = {
|
||||
const struct m_obj_list ao_obj_list = {
|
||||
.get_desc = get_desc,
|
||||
.description = "audio outputs",
|
||||
.allow_unknown_entries = true,
|
||||
@@ -133,30 +131,13 @@ static const struct m_obj_list ao_obj_list = {
|
||||
.use_global_options = true,
|
||||
};
|
||||
|
||||
#define OPT_BASE_STRUCT struct ao_opts
|
||||
const struct m_sub_options ao_conf = {
|
||||
.opts = (const struct m_option[]) {
|
||||
OPT_SETTINGSLIST("ao", audio_driver_list, UPDATE_AUDIO, &ao_obj_list, ),
|
||||
OPT_STRING("audio-device", audio_device, UPDATE_AUDIO),
|
||||
OPT_STRING("audio-client-name", audio_client_name, UPDATE_AUDIO),
|
||||
OPT_DOUBLE("audio-buffer", audio_buffer,
|
||||
UPDATE_AUDIO | M_OPT_MIN | M_OPT_MAX, .min = 0, .max = 10),
|
||||
{0}
|
||||
},
|
||||
.size = sizeof(OPT_BASE_STRUCT),
|
||||
.defaults = &(const OPT_BASE_STRUCT){
|
||||
.audio_buffer = 0.2,
|
||||
.audio_device = "auto",
|
||||
.audio_client_name = "mpv",
|
||||
},
|
||||
};
|
||||
|
||||
static struct ao *ao_alloc(bool probing, struct mpv_global *global,
|
||||
void (*wakeup_cb)(void *ctx), void *wakeup_ctx,
|
||||
char *name)
|
||||
{
|
||||
assert(wakeup_cb);
|
||||
|
||||
struct MPOpts *opts = global->opts;
|
||||
struct mp_log *log = mp_log_new(NULL, global->log, "ao");
|
||||
struct m_obj_desc desc;
|
||||
if (!m_obj_list_find(&desc, &ao_obj_list, bstr0(name))) {
|
||||
@@ -164,7 +145,6 @@ static struct ao *ao_alloc(bool probing, struct mpv_global *global,
|
||||
talloc_free(log);
|
||||
return NULL;
|
||||
};
|
||||
struct ao_opts *opts = mp_get_config_group(NULL, global, &ao_conf);
|
||||
struct ao *ao = talloc_ptrtype(NULL, ao);
|
||||
talloc_steal(ao, log);
|
||||
*ao = (struct ao) {
|
||||
@@ -177,7 +157,6 @@ static struct ao *ao_alloc(bool probing, struct mpv_global *global,
|
||||
.def_buffer = opts->audio_buffer,
|
||||
.client_name = talloc_strdup(ao, opts->audio_client_name),
|
||||
};
|
||||
talloc_free(opts);
|
||||
ao->priv = m_config_group_from_desc(ao, ao->log, global, &desc, name);
|
||||
if (!ao->priv)
|
||||
goto error;
|
||||
@@ -253,7 +232,6 @@ static struct ao *ao_init(bool probing, struct mpv_global *global,
|
||||
if (ao->device_buffer)
|
||||
MP_VERBOSE(ao, "device buffer: %d samples.\n", ao->device_buffer);
|
||||
ao->buffer = MPMAX(ao->device_buffer, ao->def_buffer * ao->samplerate);
|
||||
ao->buffer = MPMAX(ao->buffer, 1);
|
||||
|
||||
int align = af_format_sample_alignment(ao->format);
|
||||
ao->buffer = (ao->buffer + align - 1) / align * align;
|
||||
@@ -290,8 +268,8 @@ struct ao *ao_init_best(struct mpv_global *global,
|
||||
struct encode_lavc_context *encode_lavc_ctx,
|
||||
int samplerate, int format, struct mp_chmap channels)
|
||||
{
|
||||
struct MPOpts *opts = global->opts;
|
||||
void *tmp = talloc_new(NULL);
|
||||
struct ao_opts *opts = mp_get_config_group(tmp, global, &ao_conf);
|
||||
struct mp_log *log = mp_log_new(tmp, global->log, "ao");
|
||||
struct ao *ao = NULL;
|
||||
struct m_obj_settings *ao_list = NULL;
|
||||
@@ -406,7 +384,6 @@ void ao_reset(struct ao *ao)
|
||||
{
|
||||
if (ao->api->reset)
|
||||
ao->api->reset(ao);
|
||||
atomic_fetch_and(&ao->events_, ~(unsigned int)AO_EVENT_UNDERRUN);
|
||||
}
|
||||
|
||||
// Pause playback. Keep the current buffer. ao_get_delay() must return the
|
||||
@@ -444,7 +421,7 @@ int ao_query_and_reset_events(struct ao *ao, int events)
|
||||
return atomic_fetch_and(&ao->events_, ~(unsigned)events) & events;
|
||||
}
|
||||
|
||||
void ao_add_events(struct ao *ao, int events)
|
||||
static void ao_add_events(struct ao *ao, int events)
|
||||
{
|
||||
atomic_fetch_or(&ao->events_, events);
|
||||
ao->wakeup_cb(ao->wakeup_ctx);
|
||||
@@ -462,14 +439,6 @@ void ao_hotplug_event(struct ao *ao)
|
||||
ao_add_events(ao, AO_EVENT_HOTPLUG);
|
||||
}
|
||||
|
||||
void ao_underrun_event(struct ao *ao)
|
||||
{
|
||||
// Racy check, but it's just for the message.
|
||||
if (!(atomic_load(&ao->events_) & AO_EVENT_UNDERRUN))
|
||||
MP_WARN(ao, "Device underrun detected.\n");
|
||||
ao_add_events(ao, AO_EVENT_UNDERRUN);
|
||||
}
|
||||
|
||||
bool ao_chmap_sel_adjust(struct ao *ao, const struct mp_chmap_sel *s,
|
||||
struct mp_chmap *map)
|
||||
{
|
||||
@@ -530,11 +499,6 @@ const char *ao_get_description(struct ao *ao)
|
||||
return ao->driver->description;
|
||||
}
|
||||
|
||||
bool ao_get_reports_underruns(struct ao *ao)
|
||||
{
|
||||
return ao->driver->reports_underruns;
|
||||
}
|
||||
|
||||
bool ao_untimed(struct ao *ao)
|
||||
{
|
||||
return ao->untimed;
|
||||
|
||||
+4
-12
@@ -35,6 +35,10 @@ enum aocontrol {
|
||||
AOCONTROL_SET_MUTE,
|
||||
// Has char* as argument, which contains the desired stream title.
|
||||
AOCONTROL_UPDATE_STREAM_TITLE,
|
||||
// the AO does the equivalent of af_volume (return CONTROL_TRUE if yes)
|
||||
AOCONTROL_HAS_SOFT_VOLUME,
|
||||
// like above, but volume persists (per app), mpv won't restore volume
|
||||
AOCONTROL_HAS_PER_APP_VOLUME,
|
||||
};
|
||||
|
||||
// If set, then the queued audio data is the last. Note that after a while, new
|
||||
@@ -44,8 +48,6 @@ enum aocontrol {
|
||||
enum {
|
||||
AO_EVENT_RELOAD = 1,
|
||||
AO_EVENT_HOTPLUG = 2,
|
||||
AO_EVENT_INITIAL_UNBLOCK = 4,
|
||||
AO_EVENT_UNDERRUN = 8,
|
||||
};
|
||||
|
||||
enum {
|
||||
@@ -80,13 +82,6 @@ struct mpv_global;
|
||||
struct input_ctx;
|
||||
struct encode_lavc_context;
|
||||
|
||||
struct ao_opts {
|
||||
struct m_obj_settings *audio_driver_list;
|
||||
char *audio_device;
|
||||
char *audio_client_name;
|
||||
double audio_buffer;
|
||||
};
|
||||
|
||||
struct ao *ao_init_best(struct mpv_global *global,
|
||||
int init_flags,
|
||||
void (*wakeup_cb)(void *ctx), void *wakeup_ctx,
|
||||
@@ -97,7 +92,6 @@ void ao_get_format(struct ao *ao,
|
||||
int *samplerate, int *format, struct mp_chmap *channels);
|
||||
const char *ao_get_name(struct ao *ao);
|
||||
const char *ao_get_description(struct ao *ao);
|
||||
bool ao_get_reports_underruns(struct ao *ao);
|
||||
bool ao_untimed(struct ao *ao);
|
||||
int ao_play(struct ao *ao, void **data, int samples, int flags);
|
||||
int ao_control(struct ao *ao, enum aocontrol cmd, void *arg);
|
||||
@@ -110,8 +104,6 @@ void ao_resume(struct ao *ao);
|
||||
void ao_drain(struct ao *ao);
|
||||
bool ao_eof_reached(struct ao *ao);
|
||||
int ao_query_and_reset_events(struct ao *ao, int events);
|
||||
void ao_add_events(struct ao *ao, int events);
|
||||
void ao_unblock(struct ao *ao);
|
||||
void ao_request_reload(struct ao *ao);
|
||||
void ao_hotplug_event(struct ao *ao);
|
||||
|
||||
|
||||
+39
-80
@@ -30,7 +30,6 @@
|
||||
#include <sys/time.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <limits.h>
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -43,11 +42,8 @@
|
||||
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
#if defined(SND_CHMAP_API_VERSION) && SND_CHMAP_API_VERSION >= (1 << 16)
|
||||
#define HAVE_CHMAP_API 1
|
||||
#else
|
||||
#define HAVE_CHMAP_API 0
|
||||
#endif
|
||||
#define HAVE_CHMAP_API \
|
||||
(defined(SND_CHMAP_API_VERSION) && SND_CHMAP_API_VERSION >= (1 << 16))
|
||||
|
||||
#include "ao.h"
|
||||
#include "internal.h"
|
||||
@@ -60,8 +56,6 @@ struct ao_alsa_opts {
|
||||
int resample;
|
||||
int ni;
|
||||
int ignore_chmap;
|
||||
int buffer_time;
|
||||
int frags;
|
||||
};
|
||||
|
||||
#define OPT_BASE_STRUCT struct ao_alsa_opts
|
||||
@@ -73,8 +67,6 @@ static const struct m_sub_options ao_alsa_conf = {
|
||||
OPT_INTRANGE("alsa-mixer-index", mixer_index, 0, 0, 99),
|
||||
OPT_FLAG("alsa-non-interleaved", ni, 0),
|
||||
OPT_FLAG("alsa-ignore-chmap", ignore_chmap, 0),
|
||||
OPT_INTRANGE("alsa-buffer-time", buffer_time, 0, 0, INT_MAX),
|
||||
OPT_INTRANGE("alsa-periods", frags, 0, 0, INT_MAX),
|
||||
{0}
|
||||
},
|
||||
.defaults = &(const struct ao_alsa_opts) {
|
||||
@@ -82,8 +74,6 @@ static const struct m_sub_options ao_alsa_conf = {
|
||||
.mixer_name = "Master",
|
||||
.mixer_index = 0,
|
||||
.ni = 0,
|
||||
.buffer_time = 100000,
|
||||
.frags = 4,
|
||||
},
|
||||
.size = sizeof(struct ao_alsa_opts),
|
||||
};
|
||||
@@ -94,7 +84,6 @@ struct priv {
|
||||
snd_pcm_format_t alsa_fmt;
|
||||
bool can_pause;
|
||||
bool paused;
|
||||
bool final_chunk_written;
|
||||
snd_pcm_sframes_t prepause_frames;
|
||||
double delay_before_pause;
|
||||
snd_pcm_uframes_t buffersize;
|
||||
@@ -107,6 +96,9 @@ struct priv {
|
||||
struct ao_alsa_opts *opts;
|
||||
};
|
||||
|
||||
#define BUFFER_TIME 250000 // 250ms
|
||||
#define FRAGCOUNT 16
|
||||
|
||||
#define CHECK_ALSA_ERROR(message) \
|
||||
do { \
|
||||
if (err < 0) { \
|
||||
@@ -136,19 +128,6 @@ static bool check_device_present(struct ao *ao, int alsa_err)
|
||||
return false;
|
||||
}
|
||||
|
||||
static void handle_underrun(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
if (!p->final_chunk_written) {
|
||||
ao_underrun_event(ao);
|
||||
|
||||
int err = snd_pcm_prepare(p->alsa);
|
||||
CHECK_ALSA_ERROR("pcm prepare error");
|
||||
alsa_error: ;
|
||||
}
|
||||
}
|
||||
|
||||
static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
@@ -539,7 +518,7 @@ static int set_chmap(struct ao *ao, struct mp_chmap *dev_chmap, int num_channels
|
||||
|
||||
#endif /* else HAVE_CHMAP_API */
|
||||
|
||||
static void dump_hw_params(struct ao *ao, const char *msg,
|
||||
static void dump_hw_params(struct ao *ao, int msglevel, const char *msg,
|
||||
snd_pcm_hw_params_t *hw_params)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
@@ -551,7 +530,7 @@ static void dump_hw_params(struct ao *ao, const char *msg,
|
||||
char *tmp = NULL;
|
||||
size_t tmp_s = snd_output_buffer_string(p->output, &tmp);
|
||||
if (tmp)
|
||||
mp_msg(ao->log, MSGL_DEBUG, "%s---\n%.*s---\n", msg, (int)tmp_s, tmp);
|
||||
mp_msg(ao->log, msglevel, "%s---\n%.*s---\n", msg, (int)tmp_s, tmp);
|
||||
snd_output_flush(p->output);
|
||||
}
|
||||
|
||||
@@ -666,7 +645,6 @@ alsa_error: ;
|
||||
static int init_device(struct ao *ao, int mode)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
struct ao_alsa_opts *opts = p->opts;
|
||||
int ret = INIT_DEVICE_ERR_GENERIC;
|
||||
char *tmp;
|
||||
size_t tmp_s;
|
||||
@@ -700,7 +678,7 @@ static int init_device(struct ao *ao, int mode)
|
||||
err = snd_pcm_hw_params_any(p->alsa, alsa_hwparams);
|
||||
CHECK_ALSA_ERROR("Unable to get initial parameters");
|
||||
|
||||
dump_hw_params(ao, "Start HW params:\n", alsa_hwparams);
|
||||
dump_hw_params(ao, MSGL_DEBUG, "Start HW params:\n", alsa_hwparams);
|
||||
|
||||
// Some ALSA drivers have broken delay reporting, so disable the ALSA
|
||||
// resampling plugin by default.
|
||||
@@ -708,7 +686,7 @@ static int init_device(struct ao *ao, int mode)
|
||||
err = snd_pcm_hw_params_set_rate_resample(p->alsa, alsa_hwparams, 0);
|
||||
CHECK_ALSA_ERROR("Unable to disable resampling");
|
||||
}
|
||||
dump_hw_params(ao, "HW params after rate:\n", alsa_hwparams);
|
||||
dump_hw_params(ao, MSGL_DEBUG, "HW params after rate:\n", alsa_hwparams);
|
||||
|
||||
snd_pcm_access_t access = af_fmt_is_planar(ao->format)
|
||||
? SND_PCM_ACCESS_RW_NONINTERLEAVED
|
||||
@@ -720,10 +698,10 @@ static int init_device(struct ao *ao, int mode)
|
||||
err = snd_pcm_hw_params_set_access(p->alsa, alsa_hwparams, access);
|
||||
}
|
||||
CHECK_ALSA_ERROR("Unable to set access type");
|
||||
dump_hw_params(ao, "HW params after access:\n", alsa_hwparams);
|
||||
dump_hw_params(ao, MSGL_DEBUG, "HW params after access:\n", alsa_hwparams);
|
||||
|
||||
bool found_format = false;
|
||||
int try_formats[AF_FORMAT_COUNT + 1];
|
||||
int try_formats[AF_FORMAT_COUNT];
|
||||
af_get_best_sample_formats(ao->format, try_formats);
|
||||
for (int n = 0; try_formats[n] && !found_format; n++) {
|
||||
int mp_format = try_formats[n];
|
||||
@@ -763,7 +741,7 @@ static int init_device(struct ao *ao, int mode)
|
||||
|
||||
err = snd_pcm_hw_params_set_format(p->alsa, alsa_hwparams, p->alsa_fmt);
|
||||
CHECK_ALSA_ERROR("Unable to set format");
|
||||
dump_hw_params(ao, "HW params after format:\n", alsa_hwparams);
|
||||
dump_hw_params(ao, MSGL_DEBUG, "HW params after format:\n", alsa_hwparams);
|
||||
|
||||
// Stereo, or mono if input is 1 channel.
|
||||
struct mp_chmap reduced;
|
||||
@@ -792,7 +770,7 @@ static int init_device(struct ao *ao, int mode)
|
||||
err = snd_pcm_hw_params_set_channels_near
|
||||
(p->alsa, alsa_hwparams, &num_channels);
|
||||
CHECK_ALSA_ERROR("Unable to set channels");
|
||||
dump_hw_params(ao, "HW params after channels:\n", alsa_hwparams);
|
||||
dump_hw_params(ao, MSGL_DEBUG, "HW params after channels:\n", alsa_hwparams);
|
||||
|
||||
if (num_channels > MP_NUM_CHANNELS) {
|
||||
MP_FATAL(ao, "Too many audio channels (%d).\n", num_channels);
|
||||
@@ -802,35 +780,32 @@ static int init_device(struct ao *ao, int mode)
|
||||
err = snd_pcm_hw_params_set_rate_near
|
||||
(p->alsa, alsa_hwparams, &ao->samplerate, NULL);
|
||||
CHECK_ALSA_ERROR("Unable to set samplerate-2");
|
||||
dump_hw_params(ao, "HW params after rate-2:\n", alsa_hwparams);
|
||||
dump_hw_params(ao, MSGL_DEBUG, "HW params after rate-2:\n", alsa_hwparams);
|
||||
|
||||
snd_pcm_hw_params_t *hwparams_backup;
|
||||
snd_pcm_hw_params_alloca(&hwparams_backup);
|
||||
snd_pcm_hw_params_copy(hwparams_backup, alsa_hwparams);
|
||||
|
||||
// Cargo-culted buffer settings; might still be useful for PulseAudio.
|
||||
err = 0;
|
||||
if (opts->buffer_time) {
|
||||
err = snd_pcm_hw_params_set_buffer_time_near
|
||||
(p->alsa, alsa_hwparams, &(unsigned int){opts->buffer_time}, NULL);
|
||||
CHECK_ALSA_WARN("Unable to set buffer time near");
|
||||
}
|
||||
if (err >= 0 && opts->frags) {
|
||||
err = snd_pcm_hw_params_set_buffer_time_near
|
||||
(p->alsa, alsa_hwparams, &(unsigned int){BUFFER_TIME}, NULL);
|
||||
CHECK_ALSA_WARN("Unable to set buffer time near");
|
||||
if (err >= 0) {
|
||||
err = snd_pcm_hw_params_set_periods_near
|
||||
(p->alsa, alsa_hwparams, &(unsigned int){opts->frags}, NULL);
|
||||
(p->alsa, alsa_hwparams, &(unsigned int){FRAGCOUNT}, NULL);
|
||||
CHECK_ALSA_WARN("Unable to set periods");
|
||||
}
|
||||
if (err < 0)
|
||||
snd_pcm_hw_params_copy(alsa_hwparams, hwparams_backup);
|
||||
|
||||
dump_hw_params(ao, "Going to set final HW params:\n", alsa_hwparams);
|
||||
dump_hw_params(ao, MSGL_V, "Going to set final HW params:\n", alsa_hwparams);
|
||||
|
||||
/* finally install hardware parameters */
|
||||
err = snd_pcm_hw_params(p->alsa, alsa_hwparams);
|
||||
ret = INIT_DEVICE_ERR_HWPARAMS;
|
||||
CHECK_ALSA_ERROR("Unable to set hw-parameters");
|
||||
ret = INIT_DEVICE_ERR_GENERIC;
|
||||
dump_hw_params(ao, "Final HW params:\n", alsa_hwparams);
|
||||
dump_hw_params(ao, MSGL_DEBUG, "Final HW params:\n", alsa_hwparams);
|
||||
|
||||
if (set_chmap(ao, &dev_chmap, num_channels) < 0)
|
||||
goto alsa_error;
|
||||
@@ -870,6 +845,11 @@ static int init_device(struct ao *ao, int mode)
|
||||
(p->alsa, alsa_swparams, p->outburst);
|
||||
CHECK_ALSA_ERROR("Unable to set start threshold");
|
||||
|
||||
/* disable underrun reporting */
|
||||
err = snd_pcm_sw_params_set_stop_threshold
|
||||
(p->alsa, alsa_swparams, boundary);
|
||||
CHECK_ALSA_ERROR("Unable to set stop threshold");
|
||||
|
||||
/* play silence when there is an underrun */
|
||||
err = snd_pcm_sw_params_set_silence_size
|
||||
(p->alsa, alsa_swparams, boundary);
|
||||
@@ -953,28 +933,18 @@ static void drain(struct ao *ao)
|
||||
static int get_space(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
snd_pcm_status_t *status;
|
||||
int err;
|
||||
|
||||
// in case of pausing or the device still being configured,
|
||||
// just return our buffer size.
|
||||
if (p->paused || snd_pcm_state(p->alsa) == SND_PCM_STATE_SETUP)
|
||||
return p->buffersize;
|
||||
snd_pcm_status_alloca(&status);
|
||||
|
||||
snd_pcm_sframes_t space = snd_pcm_avail(p->alsa);
|
||||
if (space < 0 && space != -EPIPE) {
|
||||
MP_ERR(ao, "Error received from snd_pcm_avail "
|
||||
"(%ld, %s with ALSA state %s)!\n",
|
||||
space, snd_strerror(space),
|
||||
snd_pcm_state_name(snd_pcm_state(p->alsa)));
|
||||
|
||||
// request a reload of the AO if device is not present,
|
||||
// then error out.
|
||||
check_device_present(ao, space);
|
||||
err = snd_pcm_status(p->alsa, status);
|
||||
if (!check_device_present(ao, err))
|
||||
goto alsa_error;
|
||||
}
|
||||
if (space == -EPIPE)
|
||||
handle_underrun(ao);
|
||||
CHECK_ALSA_ERROR("cannot get pcm status");
|
||||
|
||||
if (space > p->buffersize || space < 0) // Buffer underrun?
|
||||
unsigned space = snd_pcm_status_get_avail(status);
|
||||
if (space > p->buffersize) // Buffer underrun?
|
||||
space = p->buffersize;
|
||||
return space / p->outburst * p->outburst;
|
||||
|
||||
@@ -991,12 +961,8 @@ static double get_delay(struct ao *ao)
|
||||
if (p->paused)
|
||||
return p->delay_before_pause;
|
||||
|
||||
int err = snd_pcm_delay(p->alsa, &delay);
|
||||
if (err < 0) {
|
||||
if (err == -EPIPE)
|
||||
handle_underrun(ao);
|
||||
if (snd_pcm_delay(p->alsa, &delay) < 0)
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (delay < 0) {
|
||||
/* underrun - move the application pointer forward to catch up */
|
||||
@@ -1102,7 +1068,6 @@ static void reset(struct ao *ao)
|
||||
p->paused = false;
|
||||
p->prepause_frames = 0;
|
||||
p->delay_before_pause = 0;
|
||||
p->final_chunk_written = false;
|
||||
|
||||
if (ao->stream_silence) {
|
||||
soft_reset(ao);
|
||||
@@ -1120,13 +1085,11 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
snd_pcm_sframes_t res = 0;
|
||||
bool final_chunk = flags & AOPLAY_FINAL_CHUNK;
|
||||
|
||||
if (!final_chunk)
|
||||
if (!(flags & AOPLAY_FINAL_CHUNK))
|
||||
samples = samples / p->outburst * p->outburst;
|
||||
|
||||
if (samples == 0)
|
||||
goto done;
|
||||
return 0;
|
||||
ao_convert_inplace(&p->convert, data, samples);
|
||||
|
||||
do {
|
||||
@@ -1143,12 +1106,11 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
} else if (res < 0) {
|
||||
if (res == -ESTRPIPE) { /* suspend */
|
||||
resume_device(ao);
|
||||
} else if (res == -EPIPE) {
|
||||
handle_underrun(ao);
|
||||
} else {
|
||||
MP_ERR(ao, "Write error: %s\n", snd_strerror(res));
|
||||
}
|
||||
int err = snd_pcm_prepare(p->alsa);
|
||||
res = snd_pcm_prepare(p->alsa);
|
||||
int err = res;
|
||||
CHECK_ALSA_ERROR("pcm prepare error");
|
||||
res = 0;
|
||||
}
|
||||
@@ -1156,8 +1118,6 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
|
||||
p->paused = false;
|
||||
|
||||
done:
|
||||
p->final_chunk_written = res == samples && final_chunk;
|
||||
return res < 0 ? -1 : res;
|
||||
|
||||
alsa_error:
|
||||
@@ -1264,7 +1224,6 @@ const struct ao_driver audio_out_alsa = {
|
||||
.wait = audio_wait,
|
||||
.wakeup = ao_wakeup_poll,
|
||||
.list_devs = list_devs,
|
||||
.reports_underruns = true,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.global_opts = &ao_alsa_conf,
|
||||
};
|
||||
@@ -1,717 +0,0 @@
|
||||
/*
|
||||
* Android AudioTrack audio output driver.
|
||||
* Copyright (C) 2018 Aman Gupta <aman@tmm1.net>
|
||||
* Copyright (C) 2012-2015 VLC authors and VideoLAN, VideoLabs
|
||||
* Authors: Thomas Guillem <thomas@gllm.fr>
|
||||
* Ming Hu <tewilove@gmail.com>
|
||||
*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ao.h"
|
||||
#include "internal.h"
|
||||
#include "common/msg.h"
|
||||
#include "audio/format.h"
|
||||
#include "options/m_option.h"
|
||||
#include "osdep/threads.h"
|
||||
#include "osdep/timer.h"
|
||||
#include "misc/jni.h"
|
||||
|
||||
struct priv {
|
||||
jobject audiotrack;
|
||||
jint samplerate;
|
||||
jint channel_config;
|
||||
jint format;
|
||||
jint size;
|
||||
|
||||
jobject timestamp;
|
||||
int64_t timestamp_fetched;
|
||||
bool timestamp_set;
|
||||
int timestamp_stable;
|
||||
|
||||
uint32_t written_frames; /* requires uint32_t rollover semantics */
|
||||
uint32_t playhead_pos;
|
||||
uint32_t playhead_offset;
|
||||
bool reset_pending;
|
||||
|
||||
void *chunk;
|
||||
int chunksize;
|
||||
jbyteArray bytearray;
|
||||
jshortArray shortarray;
|
||||
jfloatArray floatarray;
|
||||
jobject bbuf;
|
||||
|
||||
int cfg_pcm_float;
|
||||
int cfg_session_id;
|
||||
|
||||
bool needs_timestamp_offset;
|
||||
int64_t timestamp_offset;
|
||||
|
||||
bool thread_terminate;
|
||||
bool thread_created;
|
||||
pthread_t thread;
|
||||
pthread_mutex_t lock;
|
||||
pthread_cond_t wakeup;
|
||||
};
|
||||
|
||||
struct JNIByteBuffer {
|
||||
jclass clazz;
|
||||
jmethodID clear;
|
||||
struct MPJniField mapping[];
|
||||
} ByteBuffer = {.mapping = {
|
||||
#define OFFSET(member) offsetof(struct JNIByteBuffer, member)
|
||||
{"java/nio/ByteBuffer", NULL, NULL, MP_JNI_CLASS, OFFSET(clazz), 1},
|
||||
{"java/nio/ByteBuffer", "clear", "()Ljava/nio/Buffer;", MP_JNI_METHOD, OFFSET(clear), 1},
|
||||
{0},
|
||||
#undef OFFSET
|
||||
}};
|
||||
|
||||
struct JNIAudioTrack {
|
||||
jclass clazz;
|
||||
jmethodID ctor;
|
||||
jmethodID release;
|
||||
jmethodID getState;
|
||||
jmethodID getPlayState;
|
||||
jmethodID play;
|
||||
jmethodID stop;
|
||||
jmethodID flush;
|
||||
jmethodID pause;
|
||||
jmethodID write;
|
||||
jmethodID writeFloat;
|
||||
jmethodID writeV23;
|
||||
jmethodID writeShortV23;
|
||||
jmethodID writeBufferV21;
|
||||
jmethodID getPlaybackHeadPosition;
|
||||
jmethodID getTimestamp;
|
||||
jmethodID getLatency;
|
||||
jmethodID getMinBufferSize;
|
||||
jmethodID getNativeOutputSampleRate;
|
||||
jint STATE_INITIALIZED;
|
||||
jint PLAYSTATE_STOPPED;
|
||||
jint PLAYSTATE_PAUSED;
|
||||
jint PLAYSTATE_PLAYING;
|
||||
jint MODE_STREAM;
|
||||
jint ERROR;
|
||||
jint ERROR_BAD_VALUE;
|
||||
jint ERROR_INVALID_OPERATION;
|
||||
jint WRITE_BLOCKING;
|
||||
jint WRITE_NON_BLOCKING;
|
||||
struct MPJniField mapping[];
|
||||
} AudioTrack = {.mapping = {
|
||||
#define OFFSET(member) offsetof(struct JNIAudioTrack, member)
|
||||
{"android/media/AudioTrack", NULL, NULL, MP_JNI_CLASS, OFFSET(clazz), 1},
|
||||
{"android/media/AudioTrack", "<init>", "(IIIIIII)V", MP_JNI_METHOD, OFFSET(ctor), 1},
|
||||
{"android/media/AudioTrack", "release", "()V", MP_JNI_METHOD, OFFSET(release), 1},
|
||||
{"android/media/AudioTrack", "getState", "()I", MP_JNI_METHOD, OFFSET(getState), 1},
|
||||
{"android/media/AudioTrack", "getPlayState", "()I", MP_JNI_METHOD, OFFSET(getPlayState), 1},
|
||||
{"android/media/AudioTrack", "play", "()V", MP_JNI_METHOD, OFFSET(play), 1},
|
||||
{"android/media/AudioTrack", "stop", "()V", MP_JNI_METHOD, OFFSET(stop), 1},
|
||||
{"android/media/AudioTrack", "flush", "()V", MP_JNI_METHOD, OFFSET(flush), 1},
|
||||
{"android/media/AudioTrack", "pause", "()V", MP_JNI_METHOD, OFFSET(pause), 1},
|
||||
{"android/media/AudioTrack", "write", "([BII)I", MP_JNI_METHOD, OFFSET(write), 1},
|
||||
{"android/media/AudioTrack", "write", "([FIII)I", MP_JNI_METHOD, OFFSET(writeFloat), 1},
|
||||
{"android/media/AudioTrack", "write", "([BIII)I", MP_JNI_METHOD, OFFSET(writeV23), 0},
|
||||
{"android/media/AudioTrack", "write", "([SIII)I", MP_JNI_METHOD, OFFSET(writeShortV23), 0},
|
||||
{"android/media/AudioTrack", "write", "(Ljava/nio/ByteBuffer;II)I", MP_JNI_METHOD, OFFSET(writeBufferV21), 1},
|
||||
{"android/media/AudioTrack", "getTimestamp", "(Landroid/media/AudioTimestamp;)Z", MP_JNI_METHOD, OFFSET(getTimestamp), 1},
|
||||
{"android/media/AudioTrack", "getPlaybackHeadPosition", "()I", MP_JNI_METHOD, OFFSET(getPlaybackHeadPosition), 1},
|
||||
{"android/media/AudioTrack", "getLatency", "()I", MP_JNI_METHOD, OFFSET(getLatency), 1},
|
||||
{"android/media/AudioTrack", "getMinBufferSize", "(III)I", MP_JNI_STATIC_METHOD, OFFSET(getMinBufferSize), 1},
|
||||
{"android/media/AudioTrack", "getNativeOutputSampleRate", "(I)I", MP_JNI_STATIC_METHOD, OFFSET(getNativeOutputSampleRate), 1},
|
||||
{"android/media/AudioTrack", "WRITE_BLOCKING", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(WRITE_BLOCKING), 0},
|
||||
{"android/media/AudioTrack", "WRITE_NON_BLOCKING", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(WRITE_NON_BLOCKING), 0},
|
||||
{"android/media/AudioTrack", "STATE_INITIALIZED", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(STATE_INITIALIZED), 1},
|
||||
{"android/media/AudioTrack", "PLAYSTATE_STOPPED", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(PLAYSTATE_STOPPED), 1},
|
||||
{"android/media/AudioTrack", "PLAYSTATE_PAUSED", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(PLAYSTATE_PAUSED), 1},
|
||||
{"android/media/AudioTrack", "PLAYSTATE_PLAYING", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(PLAYSTATE_PLAYING), 1},
|
||||
{"android/media/AudioTrack", "MODE_STREAM", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(MODE_STREAM), 1},
|
||||
{"android/media/AudioTrack", "ERROR", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ERROR), 1},
|
||||
{"android/media/AudioTrack", "ERROR_BAD_VALUE", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ERROR_BAD_VALUE), 1},
|
||||
{"android/media/AudioTrack", "ERROR_INVALID_OPERATION", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ERROR_INVALID_OPERATION), 1},
|
||||
{0}
|
||||
#undef OFFSET
|
||||
}};
|
||||
|
||||
struct JNIAudioFormat {
|
||||
jclass clazz;
|
||||
jint ENCODING_PCM_8BIT;
|
||||
jint ENCODING_PCM_16BIT;
|
||||
jint ENCODING_PCM_FLOAT;
|
||||
jint ENCODING_IEC61937;
|
||||
jint ENCODING_AC3;
|
||||
jint CHANNEL_OUT_MONO;
|
||||
jint CHANNEL_OUT_STEREO;
|
||||
jint CHANNEL_OUT_FRONT_LEFT;
|
||||
jint CHANNEL_OUT_FRONT_RIGHT;
|
||||
jint CHANNEL_OUT_BACK_LEFT;
|
||||
jint CHANNEL_OUT_BACK_RIGHT;
|
||||
jint CHANNEL_OUT_FRONT_CENTER;
|
||||
jint CHANNEL_OUT_LOW_FREQUENCY;
|
||||
jint CHANNEL_OUT_BACK_CENTER;
|
||||
jint CHANNEL_OUT_5POINT1;
|
||||
jint CHANNEL_OUT_SIDE_LEFT;
|
||||
jint CHANNEL_OUT_SIDE_RIGHT;
|
||||
struct MPJniField mapping[];
|
||||
} AudioFormat = {.mapping = {
|
||||
#define OFFSET(member) offsetof(struct JNIAudioFormat, member)
|
||||
{"android/media/AudioFormat", NULL, NULL, MP_JNI_CLASS, OFFSET(clazz), 1},
|
||||
{"android/media/AudioFormat", "ENCODING_PCM_8BIT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ENCODING_PCM_8BIT), 1},
|
||||
{"android/media/AudioFormat", "ENCODING_PCM_16BIT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ENCODING_PCM_16BIT), 1},
|
||||
{"android/media/AudioFormat", "ENCODING_PCM_FLOAT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ENCODING_PCM_FLOAT), 1},
|
||||
{"android/media/AudioFormat", "ENCODING_AC3", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ENCODING_AC3), 0},
|
||||
{"android/media/AudioFormat", "ENCODING_IEC61937", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ENCODING_IEC61937), 0},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_MONO", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_MONO), 1},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_STEREO", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_STEREO), 1},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_5POINT1", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_5POINT1), 1},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_FRONT_LEFT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_FRONT_LEFT), 1},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_FRONT_RIGHT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_FRONT_RIGHT), 1},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_FRONT_CENTER", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_FRONT_CENTER), 1},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_LOW_FREQUENCY", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_LOW_FREQUENCY), 1},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_BACK_LEFT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_BACK_LEFT), 1},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_BACK_RIGHT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_BACK_RIGHT), 1},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_BACK_CENTER", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_BACK_CENTER), 1},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_SIDE_LEFT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_SIDE_LEFT), 0},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_SIDE_RIGHT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_SIDE_RIGHT), 0},
|
||||
{0}
|
||||
#undef OFFSET
|
||||
}};
|
||||
|
||||
struct JNIAudioManager {
|
||||
jclass clazz;
|
||||
jint ERROR_DEAD_OBJECT;
|
||||
jint STREAM_MUSIC;
|
||||
struct MPJniField mapping[];
|
||||
} AudioManager = {.mapping = {
|
||||
#define OFFSET(member) offsetof(struct JNIAudioManager, member)
|
||||
{"android/media/AudioManager", NULL, NULL, MP_JNI_CLASS, OFFSET(clazz), 1},
|
||||
{"android/media/AudioManager", "STREAM_MUSIC", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(STREAM_MUSIC), 1},
|
||||
{"android/media/AudioManager", "ERROR_DEAD_OBJECT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ERROR_DEAD_OBJECT), 0},
|
||||
{0}
|
||||
#undef OFFSET
|
||||
}};
|
||||
|
||||
struct JNIAudioTimestamp {
|
||||
jclass clazz;
|
||||
jmethodID ctor;
|
||||
jfieldID framePosition;
|
||||
jfieldID nanoTime;
|
||||
struct MPJniField mapping[];
|
||||
} AudioTimestamp = {.mapping = {
|
||||
#define OFFSET(member) offsetof(struct JNIAudioTimestamp, member)
|
||||
{"android/media/AudioTimestamp", NULL, NULL, MP_JNI_CLASS, OFFSET(clazz), 1},
|
||||
{"android/media/AudioTimestamp", "<init>", "()V", MP_JNI_METHOD, OFFSET(ctor), 1},
|
||||
{"android/media/AudioTimestamp", "framePosition", "J", MP_JNI_FIELD, OFFSET(framePosition), 1},
|
||||
{"android/media/AudioTimestamp", "nanoTime", "J", MP_JNI_FIELD, OFFSET(nanoTime), 1},
|
||||
{0}
|
||||
#undef OFFSET
|
||||
}};
|
||||
|
||||
static int AudioTrack_New(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
JNIEnv *env = MP_JNI_GET_ENV(ao);
|
||||
|
||||
jobject audiotrack = MP_JNI_NEW(
|
||||
AudioTrack.clazz,
|
||||
AudioTrack.ctor,
|
||||
AudioManager.STREAM_MUSIC,
|
||||
p->samplerate,
|
||||
p->channel_config,
|
||||
p->format,
|
||||
p->size,
|
||||
AudioTrack.MODE_STREAM,
|
||||
p->cfg_session_id
|
||||
);
|
||||
if (!audiotrack || MP_JNI_EXCEPTION_LOG(ao) < 0) {
|
||||
MP_FATAL(ao, "AudioTrack Init failed\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (MP_JNI_CALL_INT(audiotrack, AudioTrack.getState) != AudioTrack.STATE_INITIALIZED) {
|
||||
MP_JNI_CALL_VOID(audiotrack, AudioTrack.release);
|
||||
MP_JNI_EXCEPTION_LOG(ao);
|
||||
(*env)->DeleteLocalRef(env, audiotrack);
|
||||
MP_ERR(ao, "AudioTrack.getState failed\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
p->audiotrack = (*env)->NewGlobalRef(env, audiotrack);
|
||||
(*env)->DeleteLocalRef(env, audiotrack);
|
||||
if (!p->audiotrack)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int AudioTrack_Recreate(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
JNIEnv *env = MP_JNI_GET_ENV(ao);
|
||||
|
||||
MP_JNI_CALL_VOID(p->audiotrack, AudioTrack.release);
|
||||
MP_JNI_EXCEPTION_LOG(ao);
|
||||
(*env)->DeleteGlobalRef(env, p->audiotrack);
|
||||
p->audiotrack = NULL;
|
||||
return AudioTrack_New(ao);
|
||||
}
|
||||
|
||||
static uint32_t AudioTrack_getPlaybackHeadPosition(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
if (!p->audiotrack)
|
||||
return 0;
|
||||
JNIEnv *env = MP_JNI_GET_ENV(ao);
|
||||
uint32_t pos = 0;
|
||||
int64_t now = mp_raw_time_us();
|
||||
int state = MP_JNI_CALL_INT(p->audiotrack, AudioTrack.getPlayState);
|
||||
|
||||
int stable_count = 20;
|
||||
int64_t wait = p->timestamp_stable < stable_count ? 50000 : 3000000;
|
||||
|
||||
if (state == AudioTrack.PLAYSTATE_PLAYING && p->format != AudioFormat.ENCODING_IEC61937 &&
|
||||
(p->timestamp_fetched == 0 || now - p->timestamp_fetched >= wait)) {
|
||||
if (!p->timestamp_fetched)
|
||||
p->timestamp_stable = 0;
|
||||
|
||||
int64_t utime1 = MP_JNI_GET_LONG(p->timestamp, AudioTimestamp.nanoTime) / 1000;
|
||||
if (MP_JNI_CALL_BOOL(p->audiotrack, AudioTrack.getTimestamp, p->timestamp)) {
|
||||
p->timestamp_set = true;
|
||||
p->timestamp_fetched = now;
|
||||
if (p->timestamp_stable < stable_count) {
|
||||
uint32_t fpos = 0xFFFFFFFFL & MP_JNI_GET_LONG(p->timestamp, AudioTimestamp.framePosition);
|
||||
int64_t utime2 = MP_JNI_GET_LONG(p->timestamp, AudioTimestamp.nanoTime) / 1000;
|
||||
//MP_VERBOSE(ao, "getTimestamp: fpos= %u / time= %"PRId64" / now= %"PRId64" / stable= %d\n", fpos, utime2, now, p->timestamp_stable);
|
||||
if (utime1 != utime2 && utime2 != 0 && fpos != 0) {
|
||||
p->timestamp_stable++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* AudioTrack's framePosition and playbackHeadPosition return a signed integer,
|
||||
* but documentation states it should be interpreted as a 32-bit unsigned integer.
|
||||
*/
|
||||
if (p->timestamp_set) {
|
||||
pos = 0xFFFFFFFFL & MP_JNI_GET_LONG(p->timestamp, AudioTimestamp.framePosition);
|
||||
uint32_t fpos = pos;
|
||||
int64_t utime = MP_JNI_GET_LONG(p->timestamp, AudioTimestamp.nanoTime) / 1000;
|
||||
if (utime == 0)
|
||||
fpos = pos = 0;
|
||||
if (p->needs_timestamp_offset) {
|
||||
if (utime != 0 && !p->timestamp_offset)
|
||||
p->timestamp_offset = now - utime;
|
||||
utime += p->timestamp_offset;
|
||||
}
|
||||
if (fpos != 0 && utime != 0 && state == AudioTrack.PLAYSTATE_PLAYING) {
|
||||
double diff = (double)(now - utime) / 1e6;
|
||||
pos += diff * ao->samplerate;
|
||||
}
|
||||
//MP_VERBOSE(ao, "position = %u via getTimestamp (state = %d / fpos= %u / time= %"PRId64")\n", pos, state, fpos, utime);
|
||||
} else {
|
||||
pos = 0xFFFFFFFFL & MP_JNI_CALL_INT(p->audiotrack, AudioTrack.getPlaybackHeadPosition);
|
||||
//MP_VERBOSE(ao, "playbackHeadPosition = %u (reset_pending=%d)\n", pos, p->reset_pending);
|
||||
}
|
||||
|
||||
|
||||
if (p->format == AudioFormat.ENCODING_IEC61937) {
|
||||
if (p->reset_pending) {
|
||||
// after a flush(), playbackHeadPosition will not reset to 0 right away.
|
||||
// sometimes, it will never reset at all.
|
||||
// save the initial offset after the reset, to subtract it going forward.
|
||||
if (p->playhead_offset == 0)
|
||||
p->playhead_offset = pos;
|
||||
p->reset_pending = false;
|
||||
MP_VERBOSE(ao, "IEC/playbackHead offset = %d\n", pos);
|
||||
}
|
||||
|
||||
// usually shortly after a flush(), playbackHeadPosition will reset to 0.
|
||||
// clear out the position and offset to avoid regular "rollover" below
|
||||
if (pos == 0 && p->playhead_offset != 0) {
|
||||
MP_VERBOSE(ao, "IEC/playbackHeadPosition %d -> %d (flush)\n", p->playhead_pos, pos);
|
||||
p->playhead_offset = 0;
|
||||
p->playhead_pos = 0;
|
||||
}
|
||||
|
||||
// sometimes on a new AudioTrack instance, playbackHeadPosition will reset
|
||||
// to 0 shortly after playback starts for no reason.
|
||||
if (pos == 0 && p->playhead_pos != 0) {
|
||||
MP_VERBOSE(ao, "IEC/playbackHeadPosition %d -> %d (reset)\n", p->playhead_pos, pos);
|
||||
p->playhead_offset = 0;
|
||||
p->playhead_pos = 0;
|
||||
p->written_frames = 0;
|
||||
}
|
||||
}
|
||||
|
||||
p->playhead_pos = pos;
|
||||
return p->playhead_pos - p->playhead_offset;
|
||||
}
|
||||
|
||||
static double AudioTrack_getLatency(struct ao *ao)
|
||||
{
|
||||
JNIEnv *env = MP_JNI_GET_ENV(ao);
|
||||
struct priv *p = ao->priv;
|
||||
if (!p->audiotrack)
|
||||
return 0;
|
||||
|
||||
uint32_t playhead = AudioTrack_getPlaybackHeadPosition(ao);
|
||||
uint32_t diff = p->written_frames - playhead;
|
||||
double delay = diff / (double)(ao->samplerate);
|
||||
if (!p->timestamp_set &&
|
||||
p->format != AudioFormat.ENCODING_IEC61937)
|
||||
delay += (double)MP_JNI_CALL_INT(p->audiotrack, AudioTrack.getLatency)/1000.0;
|
||||
if (delay > 1.0) {
|
||||
//MP_WARN(ao, "getLatency: written=%u playhead=%u diff=%u delay=%f\n", p->written_frames, playhead, diff, delay);
|
||||
p->timestamp_fetched = 0;
|
||||
return 0;
|
||||
}
|
||||
return MPCLAMP(delay, 0.0, 1.0);
|
||||
}
|
||||
|
||||
static int AudioTrack_write(struct ao *ao, int len)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
if (!p->audiotrack)
|
||||
return -1;
|
||||
JNIEnv *env = MP_JNI_GET_ENV(ao);
|
||||
void *buf = p->chunk;
|
||||
|
||||
jint ret;
|
||||
if (p->format == AudioFormat.ENCODING_IEC61937) {
|
||||
(*env)->SetShortArrayRegion(env, p->shortarray, 0, len / 2, buf);
|
||||
if (MP_JNI_EXCEPTION_LOG(ao) < 0) return -1;
|
||||
ret = MP_JNI_CALL_INT(p->audiotrack, AudioTrack.writeShortV23, p->shortarray, 0, len / 2, AudioTrack.WRITE_BLOCKING);
|
||||
if (MP_JNI_EXCEPTION_LOG(ao) < 0) return -1;
|
||||
if (ret > 0) ret *= 2;
|
||||
|
||||
} else if (p->format == AudioFormat.ENCODING_PCM_FLOAT) {
|
||||
(*env)->SetFloatArrayRegion(env, p->floatarray, 0, len / sizeof(float), buf);
|
||||
if (MP_JNI_EXCEPTION_LOG(ao) < 0) return -1;
|
||||
ret = MP_JNI_CALL_INT(p->audiotrack, AudioTrack.writeFloat, p->floatarray, 0, len / sizeof(float), AudioTrack.WRITE_BLOCKING);
|
||||
if (MP_JNI_EXCEPTION_LOG(ao) < 0) return -1;
|
||||
if (ret > 0) ret *= sizeof(float);
|
||||
|
||||
} else if (AudioTrack.writeBufferV21) {
|
||||
jobject bbuf = MP_JNI_CALL_OBJECT(p->bbuf, ByteBuffer.clear);
|
||||
if (MP_JNI_EXCEPTION_LOG(ao) < 0) return -1;
|
||||
(*env)->DeleteLocalRef(env, bbuf);
|
||||
ret = MP_JNI_CALL_INT(p->audiotrack, AudioTrack.writeBufferV21, p->bbuf, len, AudioTrack.WRITE_BLOCKING);
|
||||
if (MP_JNI_EXCEPTION_LOG(ao) < 0) return -1;
|
||||
|
||||
} else {
|
||||
(*env)->SetByteArrayRegion(env, p->bytearray, 0, len, buf);
|
||||
if (MP_JNI_EXCEPTION_LOG(ao) < 0) return -1;
|
||||
ret = MP_JNI_CALL_INT(p->audiotrack, AudioTrack.write, p->bytearray, 0, len);
|
||||
if (MP_JNI_EXCEPTION_LOG(ao) < 0) return -1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void uninit_jni(struct ao *ao)
|
||||
{
|
||||
JNIEnv *env = MP_JNI_GET_ENV(ao);
|
||||
mp_jni_reset_jfields(env, &AudioTrack, AudioTrack.mapping, 1, ao->log);
|
||||
mp_jni_reset_jfields(env, &AudioTimestamp, AudioTimestamp.mapping, 1, ao->log);
|
||||
mp_jni_reset_jfields(env, &AudioManager, AudioManager.mapping, 1, ao->log);
|
||||
mp_jni_reset_jfields(env, &AudioFormat, AudioFormat.mapping, 1, ao->log);
|
||||
mp_jni_reset_jfields(env, &ByteBuffer, ByteBuffer.mapping, 1, ao->log);
|
||||
}
|
||||
|
||||
static int init_jni(struct ao *ao)
|
||||
{
|
||||
JNIEnv *env = MP_JNI_GET_ENV(ao);
|
||||
if (mp_jni_init_jfields(env, &AudioTrack, AudioTrack.mapping, 1, ao->log) < 0 ||
|
||||
mp_jni_init_jfields(env, &ByteBuffer, ByteBuffer.mapping, 1, ao->log) < 0 ||
|
||||
mp_jni_init_jfields(env, &AudioTimestamp, AudioTimestamp.mapping, 1, ao->log) < 0 ||
|
||||
mp_jni_init_jfields(env, &AudioManager, AudioManager.mapping, 1, ao->log) < 0 ||
|
||||
mp_jni_init_jfields(env, &AudioFormat, AudioFormat.mapping, 1, ao->log) < 0) {
|
||||
uninit_jni(ao);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void *playthread(void *arg)
|
||||
{
|
||||
struct ao *ao = arg;
|
||||
struct priv *p = ao->priv;
|
||||
JNIEnv *env = MP_JNI_GET_ENV(ao);
|
||||
mpthread_set_name("audiotrack");
|
||||
pthread_mutex_lock(&p->lock);
|
||||
while (!p->thread_terminate) {
|
||||
int state = AudioTrack.PLAYSTATE_PAUSED;
|
||||
if (p->audiotrack) {
|
||||
state = MP_JNI_CALL_INT(p->audiotrack, AudioTrack.getPlayState);
|
||||
}
|
||||
if (state == AudioTrack.PLAYSTATE_PLAYING) {
|
||||
int read_samples = p->chunksize / ao->sstride;
|
||||
int64_t ts = mp_time_us();
|
||||
ts += (read_samples / (double)(ao->samplerate)) * 1e6;
|
||||
ts += AudioTrack_getLatency(ao) * 1e6;
|
||||
int samples = ao_read_data(ao, &p->chunk, read_samples, ts);
|
||||
int write_samples = read_samples;
|
||||
int ret = AudioTrack_write(ao, write_samples * ao->sstride);
|
||||
if (ret >= 0) {
|
||||
p->written_frames += ret / ao->sstride;
|
||||
} else if (ret == AudioManager.ERROR_DEAD_OBJECT) {
|
||||
MP_WARN(ao, "AudioTrack.write failed with ERROR_DEAD_OBJECT. Recreating AudioTrack...\n");
|
||||
if (AudioTrack_Recreate(ao) < 0) {
|
||||
MP_ERR(ao, "AudioTrack_Recreate failed\n");
|
||||
}
|
||||
} else {
|
||||
MP_ERR(ao, "AudioTrack.write failed with %d\n", ret);
|
||||
}
|
||||
} else {
|
||||
struct timespec wait = mp_rel_time_to_timespec(0.300);
|
||||
pthread_cond_timedwait(&p->wakeup, &p->lock, &wait);
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&p->lock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void uninit(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
JNIEnv *env = MP_JNI_GET_ENV(ao);
|
||||
if (p->audiotrack) {
|
||||
MP_JNI_CALL_VOID(p->audiotrack, AudioTrack.stop);
|
||||
MP_JNI_EXCEPTION_LOG(ao);
|
||||
MP_JNI_CALL_VOID(p->audiotrack, AudioTrack.flush);
|
||||
MP_JNI_EXCEPTION_LOG(ao);
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&p->lock);
|
||||
p->thread_terminate = true;
|
||||
pthread_cond_signal(&p->wakeup);
|
||||
pthread_mutex_unlock(&p->lock);
|
||||
|
||||
if (p->thread_created)
|
||||
pthread_join(p->thread, NULL);
|
||||
|
||||
if (p->audiotrack) {
|
||||
MP_JNI_CALL_VOID(p->audiotrack, AudioTrack.release);
|
||||
MP_JNI_EXCEPTION_LOG(ao);
|
||||
(*env)->DeleteGlobalRef(env, p->audiotrack);
|
||||
p->audiotrack = NULL;
|
||||
}
|
||||
|
||||
if (p->bytearray) {
|
||||
(*env)->DeleteGlobalRef(env, p->bytearray);
|
||||
p->bytearray = NULL;
|
||||
}
|
||||
|
||||
if (p->shortarray) {
|
||||
(*env)->DeleteGlobalRef(env, p->shortarray);
|
||||
p->shortarray = NULL;
|
||||
}
|
||||
|
||||
if (p->floatarray) {
|
||||
(*env)->DeleteGlobalRef(env, p->floatarray);
|
||||
p->floatarray = NULL;
|
||||
}
|
||||
|
||||
if (p->bbuf) {
|
||||
(*env)->DeleteGlobalRef(env, p->bbuf);
|
||||
p->bbuf = NULL;
|
||||
}
|
||||
|
||||
if (p->timestamp) {
|
||||
(*env)->DeleteGlobalRef(env, p->timestamp);
|
||||
p->timestamp = NULL;
|
||||
}
|
||||
|
||||
if (p->chunk) {
|
||||
free(p->chunk);
|
||||
p->chunk = NULL;
|
||||
}
|
||||
|
||||
pthread_cond_destroy(&p->wakeup);
|
||||
pthread_mutex_destroy(&p->lock);
|
||||
|
||||
uninit_jni(ao);
|
||||
}
|
||||
|
||||
static int init(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
JNIEnv *env = MP_JNI_GET_ENV(ao);
|
||||
if (!env)
|
||||
return -1;
|
||||
|
||||
pthread_mutex_init(&p->lock, NULL);
|
||||
pthread_cond_init(&p->wakeup, NULL);
|
||||
|
||||
if (init_jni(ao) < 0)
|
||||
return -1;
|
||||
|
||||
if (af_fmt_is_spdif(ao->format)) {
|
||||
p->format = AudioFormat.ENCODING_IEC61937;
|
||||
} else if (ao->format == AF_FORMAT_U8) {
|
||||
p->format = AudioFormat.ENCODING_PCM_8BIT;
|
||||
} else if (p->cfg_pcm_float && (ao->format == AF_FORMAT_FLOAT || ao->format == AF_FORMAT_FLOATP)) {
|
||||
ao->format = AF_FORMAT_FLOAT;
|
||||
p->format = AudioFormat.ENCODING_PCM_FLOAT;
|
||||
} else {
|
||||
ao->format = AF_FORMAT_S16;
|
||||
p->format = AudioFormat.ENCODING_PCM_16BIT;
|
||||
}
|
||||
|
||||
if (AudioTrack.getNativeOutputSampleRate) {
|
||||
jint samplerate = MP_JNI_CALL_STATIC_INT(
|
||||
AudioTrack.clazz,
|
||||
AudioTrack.getNativeOutputSampleRate,
|
||||
AudioManager.STREAM_MUSIC
|
||||
);
|
||||
if (MP_JNI_EXCEPTION_LOG(ao) == 0) {
|
||||
ao->samplerate = samplerate;
|
||||
MP_VERBOSE(ao, "AudioTrack.nativeOutputSampleRate = %d\n", samplerate);
|
||||
}
|
||||
}
|
||||
p->samplerate = ao->samplerate;
|
||||
|
||||
if (p->format == AudioFormat.ENCODING_IEC61937) {
|
||||
p->channel_config = AudioFormat.CHANNEL_OUT_STEREO;
|
||||
} else if (ao->channels.num == 1) {
|
||||
p->channel_config = AudioFormat.CHANNEL_OUT_MONO;
|
||||
} else if (ao->channels.num == 6) {
|
||||
p->channel_config = AudioFormat.CHANNEL_OUT_5POINT1;
|
||||
ao->channels = (struct mp_chmap)MP_CHMAP6(FL, FR, FC, LFE, BL, BR);
|
||||
} else {
|
||||
p->channel_config = AudioFormat.CHANNEL_OUT_STEREO;
|
||||
ao->channels = (struct mp_chmap)MP_CHMAP_INIT_STEREO;
|
||||
}
|
||||
|
||||
jint buffer_size = MP_JNI_CALL_STATIC_INT(
|
||||
AudioTrack.clazz,
|
||||
AudioTrack.getMinBufferSize,
|
||||
p->samplerate,
|
||||
p->channel_config,
|
||||
p->format
|
||||
);
|
||||
if (buffer_size <= 0 || MP_JNI_EXCEPTION_LOG(ao) < 0) {
|
||||
MP_FATAL(ao, "AudioTrack.getMinBufferSize returned an invalid size: %d", buffer_size);
|
||||
return -1;
|
||||
}
|
||||
p->chunksize = buffer_size;
|
||||
p->chunk = malloc(buffer_size);
|
||||
|
||||
int min = 0.200 * p->samplerate * af_fmt_to_bytes(ao->format);
|
||||
int max = min * 3 / 2;
|
||||
p->size = MPCLAMP(buffer_size * 2, min, max);
|
||||
MP_VERBOSE(ao, "Setting bufferSize = %d (driver=%d, min=%d, max=%d)\n", p->size, buffer_size, min, max);
|
||||
ao->device_buffer = p->size / af_fmt_to_bytes(ao->format);
|
||||
|
||||
jobject timestamp = MP_JNI_NEW(AudioTimestamp.clazz, AudioTimestamp.ctor);
|
||||
if (!timestamp || MP_JNI_EXCEPTION_LOG(ao) < 0) {
|
||||
MP_FATAL(ao, "AudioTimestamp could not be created\n");
|
||||
return -1;
|
||||
}
|
||||
p->timestamp = (*env)->NewGlobalRef(env, timestamp);
|
||||
(*env)->DeleteLocalRef(env, timestamp);
|
||||
|
||||
if (p->format == AudioFormat.ENCODING_IEC61937) {
|
||||
jshortArray shortarray = (*env)->NewShortArray(env, p->chunksize / 2);
|
||||
p->shortarray = (*env)->NewGlobalRef(env, shortarray);
|
||||
(*env)->DeleteLocalRef(env, shortarray);
|
||||
} else if (p->format == AudioFormat.ENCODING_PCM_FLOAT) {
|
||||
jfloatArray floatarray = (*env)->NewFloatArray(env, p->chunksize / sizeof(float));
|
||||
p->floatarray = (*env)->NewGlobalRef(env, floatarray);
|
||||
(*env)->DeleteLocalRef(env, floatarray);
|
||||
} else if (AudioTrack.writeBufferV21) {
|
||||
jobject bbuf = (*env)->NewDirectByteBuffer(env, p->chunk, p->chunksize);
|
||||
p->bbuf = (*env)->NewGlobalRef(env, bbuf);
|
||||
(*env)->DeleteLocalRef(env, bbuf);
|
||||
} else {
|
||||
jbyteArray bytearray = (*env)->NewByteArray(env, p->chunksize);
|
||||
p->bytearray = (*env)->NewGlobalRef(env, bytearray);
|
||||
(*env)->DeleteLocalRef(env, bytearray);
|
||||
}
|
||||
|
||||
/* create AudioTrack object */
|
||||
if (AudioTrack_New(ao) != 0) {
|
||||
MP_FATAL(ao, "Failed to create AudioTrack\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (pthread_create(&p->thread, NULL, playthread, ao)) {
|
||||
MP_ERR(ao, "pthread creation failed\n");
|
||||
goto error;
|
||||
}
|
||||
p->thread_created = true;
|
||||
|
||||
return 1;
|
||||
|
||||
error:
|
||||
uninit(ao);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void stop(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
if (!p->audiotrack) {
|
||||
MP_ERR(ao, "AudioTrack does not exist to stop!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
JNIEnv *env = MP_JNI_GET_ENV(ao);
|
||||
MP_JNI_CALL_VOID(p->audiotrack, AudioTrack.pause);
|
||||
MP_JNI_EXCEPTION_LOG(ao);
|
||||
MP_JNI_CALL_VOID(p->audiotrack, AudioTrack.flush);
|
||||
MP_JNI_EXCEPTION_LOG(ao);
|
||||
|
||||
p->playhead_offset = 0;
|
||||
p->reset_pending = true;
|
||||
p->written_frames = 0;
|
||||
p->timestamp_fetched = 0;
|
||||
p->timestamp_set = false;
|
||||
p->timestamp_offset = 0;
|
||||
}
|
||||
|
||||
static void start(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
if (!p->audiotrack) {
|
||||
MP_ERR(ao, "AudioTrack does not exist to start!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
JNIEnv *env = MP_JNI_GET_ENV(ao);
|
||||
MP_JNI_CALL_VOID(p->audiotrack, AudioTrack.play);
|
||||
MP_JNI_EXCEPTION_LOG(ao);
|
||||
|
||||
pthread_cond_signal(&p->wakeup);
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
const struct ao_driver audio_out_audiotrack = {
|
||||
.description = "Android AudioTrack audio output",
|
||||
.name = "audiotrack",
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.reset = stop,
|
||||
.resume = start,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_FLAG("pcm-float", cfg_pcm_float, 0),
|
||||
OPT_INT("session-id", cfg_session_id, 0),
|
||||
{0}
|
||||
},
|
||||
.options_prefix = "audiotrack",
|
||||
};
|
||||
@@ -49,8 +49,8 @@ static OSStatus render_cb_lpcm(void *ctx, AudioUnitRenderActionFlags *aflags,
|
||||
planes[n] = buffer_list->mBuffers[n].mData;
|
||||
|
||||
int64_t end = mp_time_us();
|
||||
end += ca_frames_to_us(ao, frames);
|
||||
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;
|
||||
}
|
||||
@@ -155,7 +155,7 @@ static void start(struct ao *ao)
|
||||
struct priv *p = ao->priv;
|
||||
AVAudioSession *instance = AVAudioSession.sharedInstance;
|
||||
|
||||
p->device_latency = [instance outputLatency] + [instance IOBufferDuration];
|
||||
p->device_latency = [instance outputLatency];
|
||||
|
||||
OSStatus err = AudioOutputUnitStart(p->audio_unit);
|
||||
CHECK_CA_WARN("can't start audio unit");
|
||||
@@ -192,7 +192,7 @@ const struct ao_driver audio_out_audiounit = {
|
||||
.name = "audiounit",
|
||||
.uninit = uninit,
|
||||
.init = init,
|
||||
.reset = stop,
|
||||
.pause = stop,
|
||||
.resume = start,
|
||||
.priv_size = sizeof(struct priv),
|
||||
};
|
||||
@@ -117,6 +117,8 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
return get_volume(ao, arg);
|
||||
case AOCONTROL_SET_VOLUME:
|
||||
return set_volume(ao, arg);
|
||||
case AOCONTROL_HAS_SOFT_VOLUME:
|
||||
return CONTROL_TRUE;
|
||||
}
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
|
||||
@@ -31,8 +31,6 @@
|
||||
#if HAVE_COREAUDIO
|
||||
#include "audio/out/ao_coreaudio_properties.h"
|
||||
#include <CoreAudio/HostTime.h>
|
||||
#else
|
||||
#include <mach/mach_time.h>
|
||||
#endif
|
||||
|
||||
CFStringRef cfstr_from_cstr(char *str)
|
||||
@@ -312,9 +310,9 @@ int64_t ca_frames_to_us(struct ao *ao, uint32_t frames)
|
||||
return frames / (float) ao->samplerate * 1e6;
|
||||
}
|
||||
|
||||
#if HAVE_COREAUDIO
|
||||
int64_t ca_get_latency(const AudioTimeStamp *ts)
|
||||
{
|
||||
#if HAVE_COREAUDIO
|
||||
uint64_t out = AudioConvertHostTimeToNanos(ts->mHostTime);
|
||||
uint64_t now = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());
|
||||
|
||||
@@ -322,22 +320,8 @@ int64_t ca_get_latency(const AudioTimeStamp *ts)
|
||||
return 0;
|
||||
|
||||
return (out - now) * 1e-3;
|
||||
#else
|
||||
static mach_timebase_info_data_t timebase;
|
||||
if (timebase.denom == 0)
|
||||
mach_timebase_info(&timebase);
|
||||
|
||||
uint64_t out = ts->mHostTime;
|
||||
uint64_t now = mach_absolute_time();
|
||||
|
||||
if (now > out)
|
||||
return 0;
|
||||
|
||||
return (out - now) * timebase.numer / timebase.denom / 1e3;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if HAVE_COREAUDIO
|
||||
bool ca_stream_supports_compressed(struct ao *ao, AudioStreamID stream)
|
||||
{
|
||||
AudioStreamRangedDescription *formats = NULL;
|
||||
|
||||
@@ -65,7 +65,9 @@ bool ca_asbd_is_better(AudioStreamBasicDescription *req,
|
||||
AudioStreamBasicDescription *new);
|
||||
|
||||
int64_t ca_frames_to_us(struct ao *ao, uint32_t frames);
|
||||
#if HAVE_COREAUDIO
|
||||
int64_t ca_get_latency(const AudioTimeStamp *ts);
|
||||
#endif
|
||||
|
||||
#if HAVE_COREAUDIO
|
||||
bool ca_stream_supports_compressed(struct ao *ao, AudioStreamID stream);
|
||||
|
||||
+1
-2
@@ -143,8 +143,7 @@ connect_to_outports(struct ao *ao)
|
||||
if (!port_name)
|
||||
port_flags |= JackPortIsPhysical;
|
||||
|
||||
const char *port_type = JACK_DEFAULT_AUDIO_TYPE; // exclude MIDI ports
|
||||
matching_ports = jack_get_ports(p->client, port_name, port_type, port_flags);
|
||||
matching_ports = jack_get_ports(p->client, port_name, NULL, port_flags);
|
||||
|
||||
if (!matching_ports || !matching_ports[0]) {
|
||||
MP_FATAL(ao, "no ports to connect to\n");
|
||||
|
||||
+232
-74
@@ -40,8 +40,8 @@
|
||||
#include "common/encode_lavc.h"
|
||||
|
||||
struct priv {
|
||||
struct encoder_context *enc;
|
||||
|
||||
AVStream *stream;
|
||||
AVCodecContext *codec;
|
||||
int pcmhack;
|
||||
int aframesize;
|
||||
int aframecount;
|
||||
@@ -53,17 +53,16 @@ struct priv {
|
||||
double expected_next_pts;
|
||||
|
||||
AVRational worst_time_base;
|
||||
int worst_time_base_is_stream;
|
||||
|
||||
bool shutdown;
|
||||
};
|
||||
|
||||
static void encode(struct ao *ao, double apts, void **data);
|
||||
|
||||
static bool supports_format(const AVCodec *codec, int format)
|
||||
static bool supports_format(AVCodec *codec, int format)
|
||||
{
|
||||
for (const enum AVSampleFormat *sampleformat = codec->sample_fmts;
|
||||
sampleformat && *sampleformat != AV_SAMPLE_FMT_NONE;
|
||||
sampleformat++)
|
||||
++sampleformat)
|
||||
{
|
||||
if (af_from_avformat(*sampleformat) == format)
|
||||
return true;
|
||||
@@ -71,9 +70,9 @@ static bool supports_format(const AVCodec *codec, int format)
|
||||
return false;
|
||||
}
|
||||
|
||||
static void select_format(struct ao *ao, const AVCodec *codec)
|
||||
static void select_format(struct ao *ao, AVCodec *codec)
|
||||
{
|
||||
int formats[AF_FORMAT_COUNT + 1];
|
||||
int formats[AF_FORMAT_COUNT];
|
||||
af_get_best_sample_formats(ao->format, formats);
|
||||
|
||||
for (int n = 0; formats[n]; n++) {
|
||||
@@ -84,69 +83,77 @@ static void select_format(struct ao *ao, const AVCodec *codec)
|
||||
}
|
||||
}
|
||||
|
||||
static void on_ready(void *ptr)
|
||||
{
|
||||
struct ao *ao = ptr;
|
||||
|
||||
ao_add_events(ao, AO_EVENT_INITIAL_UNBLOCK);
|
||||
}
|
||||
|
||||
// open & setup audio device
|
||||
static int init(struct ao *ao)
|
||||
{
|
||||
struct priv *ac = ao->priv;
|
||||
struct priv *ac = talloc_zero(ao, struct priv);
|
||||
AVCodec *codec;
|
||||
|
||||
ac->enc = encoder_context_alloc(ao->encode_lavc_ctx, STREAM_AUDIO, ao->log);
|
||||
if (!ac->enc)
|
||||
ao->priv = ac;
|
||||
|
||||
if (!encode_lavc_available(ao->encode_lavc_ctx)) {
|
||||
MP_ERR(ao, "the option --o (output file) must be specified\n");
|
||||
return -1;
|
||||
talloc_steal(ac, ac->enc);
|
||||
}
|
||||
|
||||
AVCodecContext *encoder = ac->enc->encoder;
|
||||
const AVCodec *codec = encoder->codec;
|
||||
pthread_mutex_lock(&ao->encode_lavc_ctx->lock);
|
||||
|
||||
if (encode_lavc_alloc_stream(ao->encode_lavc_ctx,
|
||||
AVMEDIA_TYPE_AUDIO,
|
||||
&ac->stream, &ac->codec) < 0) {
|
||||
MP_ERR(ao, "could not get a new audio stream\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
codec = ao->encode_lavc_ctx->ac;
|
||||
|
||||
int samplerate = af_select_best_samplerate(ao->samplerate,
|
||||
codec->supported_samplerates);
|
||||
if (samplerate > 0)
|
||||
ao->samplerate = samplerate;
|
||||
|
||||
encoder->time_base.num = 1;
|
||||
encoder->time_base.den = ao->samplerate;
|
||||
// TODO: Remove this redundancy with encode_lavc_alloc_stream also
|
||||
// setting the time base.
|
||||
// Using codec->time_bvase is deprecated, but needed for older lavf.
|
||||
ac->stream->time_base.num = 1;
|
||||
ac->stream->time_base.den = ao->samplerate;
|
||||
ac->codec->time_base.num = 1;
|
||||
ac->codec->time_base.den = ao->samplerate;
|
||||
|
||||
encoder->sample_rate = ao->samplerate;
|
||||
ac->codec->sample_rate = ao->samplerate;
|
||||
|
||||
struct mp_chmap_sel sel = {0};
|
||||
mp_chmap_sel_add_any(&sel);
|
||||
if (!ao_chmap_sel_adjust2(ao, &sel, &ao->channels, false))
|
||||
goto fail;
|
||||
mp_chmap_reorder_to_lavc(&ao->channels);
|
||||
encoder->channels = ao->channels.num;
|
||||
encoder->channel_layout = mp_chmap_to_lavc(&ao->channels);
|
||||
ac->codec->channels = ao->channels.num;
|
||||
ac->codec->channel_layout = mp_chmap_to_lavc(&ao->channels);
|
||||
|
||||
encoder->sample_fmt = AV_SAMPLE_FMT_NONE;
|
||||
ac->codec->sample_fmt = AV_SAMPLE_FMT_NONE;
|
||||
|
||||
select_format(ao, codec);
|
||||
|
||||
ac->sample_size = af_fmt_to_bytes(ao->format);
|
||||
encoder->sample_fmt = af_to_avformat(ao->format);
|
||||
encoder->bits_per_raw_sample = ac->sample_size * 8;
|
||||
ac->codec->sample_fmt = af_to_avformat(ao->format);
|
||||
ac->codec->bits_per_raw_sample = ac->sample_size * 8;
|
||||
|
||||
if (!encoder_init_codec_and_muxer(ac->enc, on_ready, ao))
|
||||
if (encode_lavc_open_codec(ao->encode_lavc_ctx, ac->codec) < 0)
|
||||
goto fail;
|
||||
|
||||
ac->pcmhack = 0;
|
||||
if (encoder->frame_size <= 1)
|
||||
ac->pcmhack = av_get_bits_per_sample(encoder->codec_id) / 8;
|
||||
if (ac->codec->frame_size <= 1)
|
||||
ac->pcmhack = av_get_bits_per_sample(ac->codec->codec_id) / 8;
|
||||
|
||||
if (ac->pcmhack) {
|
||||
if (ac->pcmhack)
|
||||
ac->aframesize = 16384; // "enough"
|
||||
} else {
|
||||
ac->aframesize = encoder->frame_size;
|
||||
}
|
||||
else
|
||||
ac->aframesize = ac->codec->frame_size;
|
||||
|
||||
// enough frames for at least 0.25 seconds
|
||||
ac->framecount = ceil(ao->samplerate * 0.25 / ac->aframesize);
|
||||
// but at least one!
|
||||
ac->framecount = MPMAX(ac->framecount, 1);
|
||||
ac->framecount = FFMAX(ac->framecount, 1);
|
||||
|
||||
ac->savepts = AV_NOPTS_VALUE;
|
||||
ac->lastpts = AV_NOPTS_VALUE;
|
||||
@@ -158,6 +165,7 @@ static int init(struct ao *ao)
|
||||
if (ao->channels.num > AV_NUM_DATA_POINTERS)
|
||||
goto fail;
|
||||
|
||||
pthread_mutex_unlock(&ao->encode_lavc_ctx->lock);
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
@@ -167,22 +175,34 @@ fail:
|
||||
}
|
||||
|
||||
// close audio device
|
||||
static void encode(struct ao *ao, double apts, void **data);
|
||||
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;
|
||||
if (!ac || ac->shutdown)
|
||||
return;
|
||||
|
||||
pthread_mutex_lock(&ectx->lock);
|
||||
if (!ac->enc->options->rawts)
|
||||
outpts += ectx->discontinuity_pts_offset;
|
||||
pthread_mutex_lock(&ectx->lock);
|
||||
|
||||
if (!encode_lavc_start(ectx)) {
|
||||
MP_WARN(ao, "not even ready to encode audio at end -> dropped\n");
|
||||
pthread_mutex_unlock(&ectx->lock);
|
||||
return;
|
||||
}
|
||||
|
||||
outpts += encoder_get_offset(ac->enc);
|
||||
if (ac->stream) {
|
||||
double outpts = ac->expected_next_pts;
|
||||
if (!ectx->options->rawts && ectx->options->copyts)
|
||||
outpts += ectx->discontinuity_pts_offset;
|
||||
outpts += encode_lavc_getoffset(ectx, ac->codec);
|
||||
encode(ao, outpts, NULL);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&ectx->lock);
|
||||
|
||||
ac->shutdown = true;
|
||||
}
|
||||
|
||||
// return: how many samples can be played without blocking
|
||||
@@ -193,21 +213,106 @@ static int get_space(struct ao *ao)
|
||||
return ac->aframesize * ac->framecount;
|
||||
}
|
||||
|
||||
static void write_packet(struct ao *ao, AVPacket *packet)
|
||||
{
|
||||
// TODO: Can we unify this with the equivalent video code path?
|
||||
struct priv *ac = ao->priv;
|
||||
|
||||
packet->stream_index = ac->stream->index;
|
||||
if (packet->pts != AV_NOPTS_VALUE) {
|
||||
packet->pts = av_rescale_q(packet->pts,
|
||||
ac->codec->time_base,
|
||||
ac->stream->time_base);
|
||||
} else {
|
||||
// Do we need this at all? Better be safe than sorry...
|
||||
MP_WARN(ao, "encoder lost pts, why?\n");
|
||||
if (ac->savepts != MP_NOPTS_VALUE) {
|
||||
packet->pts = av_rescale_q(ac->savepts,
|
||||
ac->codec->time_base,
|
||||
ac->stream->time_base);
|
||||
}
|
||||
}
|
||||
if (packet->dts != AV_NOPTS_VALUE) {
|
||||
packet->dts = av_rescale_q(packet->dts,
|
||||
ac->codec->time_base,
|
||||
ac->stream->time_base);
|
||||
}
|
||||
if (packet->duration > 0) {
|
||||
packet->duration = av_rescale_q(packet->duration,
|
||||
ac->codec->time_base,
|
||||
ac->stream->time_base);
|
||||
}
|
||||
|
||||
ac->savepts = AV_NOPTS_VALUE;
|
||||
|
||||
if (encode_lavc_write_frame(ao->encode_lavc_ctx,
|
||||
ac->stream, packet) < 0) {
|
||||
MP_ERR(ao, "error writing at %d %d/%d\n",
|
||||
(int) packet->pts,
|
||||
ac->stream->time_base.num,
|
||||
ac->stream->time_base.den);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void encode_audio_and_write(struct ao *ao, AVFrame *frame)
|
||||
{
|
||||
// TODO: Can we unify this with the equivalent video code path?
|
||||
struct priv *ac = ao->priv;
|
||||
AVPacket packet = {0};
|
||||
|
||||
int status = avcodec_send_frame(ac->codec, frame);
|
||||
if (status < 0) {
|
||||
MP_ERR(ao, "error encoding at %d %d/%d\n",
|
||||
frame ? (int) frame->pts : -1,
|
||||
ac->codec->time_base.num,
|
||||
ac->codec->time_base.den);
|
||||
return;
|
||||
}
|
||||
for (;;) {
|
||||
av_init_packet(&packet);
|
||||
status = avcodec_receive_packet(ac->codec, &packet);
|
||||
if (status == AVERROR(EAGAIN)) { // No more packets for now.
|
||||
if (frame == NULL) {
|
||||
MP_ERR(ao, "sent flush frame, got EAGAIN");
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (status == AVERROR_EOF) { // No more packets, ever.
|
||||
if (frame != NULL) {
|
||||
MP_ERR(ao, "sent audio frame, got EOF");
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (status < 0) {
|
||||
MP_ERR(ao, "error encoding at %d %d/%d\n",
|
||||
frame ? (int) frame->pts : -1,
|
||||
ac->codec->time_base.num,
|
||||
ac->codec->time_base.den);
|
||||
break;
|
||||
}
|
||||
if (frame) {
|
||||
if (ac->savepts == AV_NOPTS_VALUE)
|
||||
ac->savepts = frame->pts;
|
||||
}
|
||||
encode_lavc_write_stats(ao->encode_lavc_ctx, ac->codec);
|
||||
write_packet(ao, &packet);
|
||||
av_packet_unref(&packet);
|
||||
}
|
||||
}
|
||||
|
||||
// must get exactly ac->aframesize amount of data
|
||||
static void encode(struct ao *ao, double apts, void **data)
|
||||
{
|
||||
struct priv *ac = ao->priv;
|
||||
struct encode_lavc_context *ectx = ao->encode_lavc_ctx;
|
||||
AVCodecContext *encoder = ac->enc->encoder;
|
||||
double realapts = ac->aframecount * (double) ac->aframesize /
|
||||
ao->samplerate;
|
||||
|
||||
ac->aframecount++;
|
||||
|
||||
pthread_mutex_lock(&ectx->lock);
|
||||
if (data)
|
||||
ectx->audio_pts_offset = realapts - apts;
|
||||
pthread_mutex_unlock(&ectx->lock);
|
||||
|
||||
if(data) {
|
||||
AVFrame *frame = av_frame_alloc();
|
||||
@@ -221,27 +326,31 @@ static void encode(struct ao *ao, double apts, void **data)
|
||||
|
||||
frame->linesize[0] = frame->nb_samples * ao->sstride;
|
||||
|
||||
frame->pts = rint(apts * av_q2d(av_inv_q(encoder->time_base)));
|
||||
if (ectx->options->rawts || ectx->options->copyts) {
|
||||
// real audio pts
|
||||
frame->pts = floor(apts * ac->codec->time_base.den / ac->codec->time_base.num + 0.5);
|
||||
} else {
|
||||
// audio playback time
|
||||
frame->pts = floor(realapts * ac->codec->time_base.den / ac->codec->time_base.num + 0.5);
|
||||
}
|
||||
|
||||
int64_t frame_pts = av_rescale_q(frame->pts, encoder->time_base,
|
||||
ac->worst_time_base);
|
||||
int64_t frame_pts = av_rescale_q(frame->pts, ac->codec->time_base, ac->worst_time_base);
|
||||
if (ac->lastpts != AV_NOPTS_VALUE && frame_pts <= ac->lastpts) {
|
||||
// this indicates broken video
|
||||
// (video pts failing to increase fast enough to match audio)
|
||||
MP_WARN(ao, "audio frame pts went backwards (%d <- %d), autofixed\n",
|
||||
(int)frame->pts, (int)ac->lastpts);
|
||||
frame_pts = ac->lastpts + 1;
|
||||
frame->pts = av_rescale_q(frame_pts, ac->worst_time_base,
|
||||
encoder->time_base);
|
||||
frame->pts = av_rescale_q(frame_pts, ac->worst_time_base, ac->codec->time_base);
|
||||
}
|
||||
ac->lastpts = frame_pts;
|
||||
|
||||
frame->quality = encoder->global_quality;
|
||||
encoder_encode(ac->enc, frame);
|
||||
frame->quality = ac->codec->global_quality;
|
||||
encode_audio_and_write(ao, frame);
|
||||
av_frame_free(&frame);
|
||||
} else {
|
||||
encoder_encode(ac->enc, NULL);
|
||||
}
|
||||
else
|
||||
encode_audio_and_write(ao, NULL);
|
||||
}
|
||||
|
||||
// this should round samples down to frame sizes
|
||||
@@ -249,15 +358,20 @@ static void encode(struct ao *ao, double apts, void **data)
|
||||
static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
{
|
||||
struct priv *ac = ao->priv;
|
||||
struct encoder_context *enc = ac->enc;
|
||||
struct encode_lavc_context *ectx = ao->encode_lavc_ctx;
|
||||
int bufpos = 0;
|
||||
double nextpts;
|
||||
double outpts;
|
||||
int orig_samples = samples;
|
||||
|
||||
// for ectx PTS fields
|
||||
pthread_mutex_lock(&ectx->lock);
|
||||
|
||||
if (!encode_lavc_start(ectx)) {
|
||||
MP_WARN(ao, "not ready yet for encoding audio\n");
|
||||
pthread_mutex_unlock(&ectx->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
double pts = ectx->last_audio_in_pts;
|
||||
pts += ectx->samples_since_last_pts / (double)ao->samplerate;
|
||||
|
||||
@@ -279,15 +393,60 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
samples = (bytelen + extralen) / ao->sstride;
|
||||
}
|
||||
|
||||
double outpts = pts;
|
||||
if (!enc->options->rawts) {
|
||||
// Fix and apply the discontinuity pts offset.
|
||||
if (pts == MP_NOPTS_VALUE) {
|
||||
MP_WARN(ao, "frame without pts, please report; synthesizing pts instead\n");
|
||||
// synthesize pts from previous expected next pts
|
||||
pts = ac->expected_next_pts;
|
||||
}
|
||||
|
||||
if (ac->worst_time_base.den == 0) {
|
||||
//if (ac->codec->time_base.num / ac->codec->time_base.den >= ac->stream->time_base.num / ac->stream->time_base.den)
|
||||
if (ac->codec->time_base.num * (double) ac->stream->time_base.den >=
|
||||
ac->stream->time_base.num * (double) ac->codec->time_base.den) {
|
||||
MP_VERBOSE(ao, "NOTE: using codec time base (%d/%d) for pts "
|
||||
"adjustment; the stream base (%d/%d) is not worse.\n",
|
||||
(int)ac->codec->time_base.num,
|
||||
(int)ac->codec->time_base.den,
|
||||
(int)ac->stream->time_base.num,
|
||||
(int)ac->stream->time_base.den);
|
||||
ac->worst_time_base = ac->codec->time_base;
|
||||
ac->worst_time_base_is_stream = 0;
|
||||
} else {
|
||||
MP_WARN(ao, "NOTE: not using codec time base (%d/%d) for pts "
|
||||
"adjustment; the stream base (%d/%d) is worse.\n",
|
||||
(int)ac->codec->time_base.num,
|
||||
(int)ac->codec->time_base.den,
|
||||
(int)ac->stream->time_base.num,
|
||||
(int)ac->stream->time_base.den);
|
||||
ac->worst_time_base = ac->stream->time_base;
|
||||
ac->worst_time_base_is_stream = 1;
|
||||
}
|
||||
|
||||
// NOTE: we use the following "axiom" of av_rescale_q:
|
||||
// if time base A is worse than time base B, then
|
||||
// av_rescale_q(av_rescale_q(x, A, B), B, A) == x
|
||||
// this can be proven as long as av_rescale_q rounds to nearest, which
|
||||
// it currently does
|
||||
|
||||
// av_rescale_q(x, A, B) * B = "round x*A to nearest multiple of B"
|
||||
// and:
|
||||
// av_rescale_q(av_rescale_q(x, A, B), B, A) * A
|
||||
// == "round av_rescale_q(x, A, B)*B to nearest multiple of A"
|
||||
// == "round 'round x*A to nearest multiple of B' to nearest multiple of A"
|
||||
//
|
||||
// assume this fails. Then there is a value of x*A, for which the
|
||||
// nearest multiple of B is outside the range [(x-0.5)*A, (x+0.5)*A[.
|
||||
// Absurd, as this range MUST contain at least one multiple of B.
|
||||
}
|
||||
|
||||
// Fix and apply the discontinuity pts offset.
|
||||
if (!ectx->options->rawts && ectx->options->copyts) {
|
||||
// fix the discontinuity pts offset
|
||||
nextpts = pts;
|
||||
if (ectx->discontinuity_pts_offset == MP_NOPTS_VALUE) {
|
||||
ectx->discontinuity_pts_offset = ectx->next_in_pts - nextpts;
|
||||
} else if (fabs(nextpts + ectx->discontinuity_pts_offset -
|
||||
ectx->next_in_pts) > 30)
|
||||
{
|
||||
}
|
||||
else if (fabs(nextpts + ectx->discontinuity_pts_offset - ectx->next_in_pts) > 30) {
|
||||
MP_WARN(ao, "detected an unexpected discontinuity (pts jumped by "
|
||||
"%f seconds)\n",
|
||||
nextpts + ectx->discontinuity_pts_offset - ectx->next_in_pts);
|
||||
@@ -296,11 +455,12 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
|
||||
outpts = pts + ectx->discontinuity_pts_offset;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&ectx->lock);
|
||||
else {
|
||||
outpts = pts;
|
||||
}
|
||||
|
||||
// Shift pts by the pts offset first.
|
||||
outpts += encoder_get_offset(enc);
|
||||
outpts += encode_lavc_getoffset(ectx, ac->codec);
|
||||
|
||||
while (samples - bufpos >= ac->aframesize) {
|
||||
void *start[MP_NUM_CHANNELS] = {0};
|
||||
@@ -313,10 +473,8 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
// Calculate expected pts of next audio frame (input side).
|
||||
ac->expected_next_pts = pts + bufpos / (double) ao->samplerate;
|
||||
|
||||
pthread_mutex_lock(&ectx->lock);
|
||||
|
||||
// Set next allowed input pts value (input side).
|
||||
if (!enc->options->rawts) {
|
||||
if (!ectx->options->rawts && ectx->options->copyts) {
|
||||
nextpts = ac->expected_next_pts + ectx->discontinuity_pts_offset;
|
||||
if (nextpts > ectx->next_in_pts)
|
||||
ectx->next_in_pts = nextpts;
|
||||
@@ -324,17 +482,19 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
|
||||
talloc_free(tempdata);
|
||||
|
||||
int taken = MPMIN(bufpos, orig_samples);
|
||||
int taken = FFMIN(bufpos, orig_samples);
|
||||
ectx->samples_since_last_pts += taken;
|
||||
|
||||
pthread_mutex_unlock(&ectx->lock);
|
||||
|
||||
if (flags & AOPLAY_FINAL_CHUNK) {
|
||||
if (bufpos < orig_samples)
|
||||
if (bufpos < orig_samples) {
|
||||
MP_ERR(ao, "did not write enough data at the end\n");
|
||||
}
|
||||
} else {
|
||||
if (bufpos > orig_samples)
|
||||
if (bufpos > orig_samples) {
|
||||
MP_ERR(ao, "audio buffer overflow (should never happen)\n");
|
||||
}
|
||||
}
|
||||
|
||||
return taken;
|
||||
@@ -349,8 +509,6 @@ const struct ao_driver audio_out_lavc = {
|
||||
.encode = true,
|
||||
.description = "audio encoding using libavcodec",
|
||||
.name = "lavc",
|
||||
.initially_blocked = true,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.get_space = get_space,
|
||||
|
||||
@@ -58,7 +58,6 @@ struct priv {
|
||||
int outburst; // samples
|
||||
|
||||
struct m_channels channel_layouts;
|
||||
int format;
|
||||
};
|
||||
|
||||
static void drain(struct ao *ao)
|
||||
@@ -89,9 +88,6 @@ static int init(struct ao *ao)
|
||||
{
|
||||
struct priv *priv = ao->priv;
|
||||
|
||||
if (priv->format)
|
||||
ao->format = priv->format;
|
||||
|
||||
ao->untimed = priv->untimed;
|
||||
|
||||
struct mp_chmap_sel sel = {.tmp = ao};
|
||||
@@ -245,7 +241,6 @@ const struct ao_driver audio_out_null = {
|
||||
OPT_FLAG("broken-eof", broken_eof, 0),
|
||||
OPT_FLAG("broken-delay", broken_delay, 0),
|
||||
OPT_CHANNELS("channel-layouts", channel_layouts, 0),
|
||||
OPT_AUDIOFORMAT("format", format, 0),
|
||||
{0}
|
||||
},
|
||||
.options_prefix = "ao-null",
|
||||
|
||||
+124
-222
@@ -24,6 +24,19 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#ifdef __APPLE__
|
||||
#ifndef AL_FORMAT_MONO_FLOAT32
|
||||
#define AL_FORMAT_MONO_FLOAT32 0x10010
|
||||
#endif
|
||||
#ifndef AL_FORMAT_STEREO_FLOAT32
|
||||
#define AL_FORMAT_STEREO_FLOAT32 0x10011
|
||||
#endif
|
||||
#ifndef AL_FORMAT_MONO_DOUBLE_EXT
|
||||
#define AL_FORMAT_MONO_DOUBLE_EXT 0x10012
|
||||
#endif
|
||||
#include <OpenAL/MacOSX_OALExtensions.h>
|
||||
#else
|
||||
#ifdef OPENAL_AL_H
|
||||
#include <OpenAL/alc.h>
|
||||
#include <OpenAL/al.h>
|
||||
@@ -33,6 +46,7 @@
|
||||
#include <AL/al.h>
|
||||
#include <AL/alext.h>
|
||||
#endif
|
||||
#endif // __APPLE__
|
||||
|
||||
#include "common/msg.h"
|
||||
|
||||
@@ -43,22 +57,19 @@
|
||||
#include "options/m_option.h"
|
||||
|
||||
#define MAX_CHANS MP_NUM_CHANNELS
|
||||
#define MAX_BUF 128
|
||||
#define MAX_SAMPLES 32768
|
||||
static ALuint buffers[MAX_BUF];
|
||||
static ALuint buffer_size[MAX_BUF];
|
||||
static ALuint source;
|
||||
#define NUM_BUF 128
|
||||
#define CHUNK_SAMPLES 256
|
||||
static ALuint buffers[MAX_CHANS][NUM_BUF];
|
||||
static ALuint sources[MAX_CHANS];
|
||||
|
||||
static int cur_buf;
|
||||
static int unqueue_buf;
|
||||
static int cur_buf[MAX_CHANS];
|
||||
static int unqueue_buf[MAX_CHANS];
|
||||
|
||||
static struct ao *ao_data;
|
||||
|
||||
struct priv {
|
||||
ALenum al_format;
|
||||
int num_buffers;
|
||||
int num_samples;
|
||||
int direct_channels;
|
||||
int chunk_size;
|
||||
};
|
||||
|
||||
static void reset(struct ao *ao);
|
||||
@@ -78,81 +89,43 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
vol->left = vol->right = volume * 100;
|
||||
return CONTROL_TRUE;
|
||||
}
|
||||
case AOCONTROL_GET_MUTE:
|
||||
case AOCONTROL_SET_MUTE: {
|
||||
bool mute = *(bool *)arg;
|
||||
|
||||
// openal has no mute control, only gain.
|
||||
// Thus reverse the muted state to get required gain
|
||||
ALfloat al_mute = (ALfloat)(!mute);
|
||||
if (cmd == AOCONTROL_SET_MUTE) {
|
||||
alSourcef(source, AL_GAIN, al_mute);
|
||||
}
|
||||
alGetSourcef(source, AL_GAIN, &al_mute);
|
||||
*(bool *)arg = !((bool)al_mute);
|
||||
case AOCONTROL_HAS_SOFT_VOLUME:
|
||||
return CONTROL_TRUE;
|
||||
}
|
||||
|
||||
}
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
|
||||
static enum af_format get_supported_format(int format)
|
||||
struct speaker {
|
||||
int id;
|
||||
float pos[3];
|
||||
};
|
||||
|
||||
static const struct speaker speaker_pos[] = {
|
||||
{MP_SPEAKER_ID_FL, {-0.500, 0, -0.866}}, // -30 deg
|
||||
{MP_SPEAKER_ID_FR, { 0.500, 0, -0.866}}, // 30 deg
|
||||
{MP_SPEAKER_ID_FC, { 0, 0, -1}}, // 0 deg
|
||||
{MP_SPEAKER_ID_LFE, { 0, -1, 0}}, // below
|
||||
{MP_SPEAKER_ID_BL, {-0.609, 0, 0.793}}, // -142.5 deg
|
||||
{MP_SPEAKER_ID_BR, { 0.609, 0, 0.793}}, // 142.5 deg
|
||||
{MP_SPEAKER_ID_BC, { 0, 0, 1}}, // 180 deg
|
||||
{MP_SPEAKER_ID_SL, {-0.985, 0, 0.174}}, // -100 deg
|
||||
{MP_SPEAKER_ID_SR, { 0.985, 0, 0.174}}, // 100 deg
|
||||
{-1},
|
||||
};
|
||||
|
||||
static ALenum get_al_format(int format)
|
||||
{
|
||||
switch (format) {
|
||||
case AF_FORMAT_U8:
|
||||
if (alGetEnumValue((ALchar*)"AL_FORMAT_MONO8"))
|
||||
return AF_FORMAT_U8;
|
||||
break;
|
||||
|
||||
case AF_FORMAT_S16:
|
||||
if (alGetEnumValue((ALchar*)"AL_FORMAT_MONO16"))
|
||||
return AF_FORMAT_S16;
|
||||
break;
|
||||
|
||||
case AF_FORMAT_S32:
|
||||
if (strstr(alGetString(AL_RENDERER), "X-Fi") != NULL)
|
||||
return AF_FORMAT_S32;
|
||||
break;
|
||||
|
||||
case AF_FORMAT_FLOAT:
|
||||
case AF_FORMAT_U8P: return AL_FORMAT_MONO8;
|
||||
case AF_FORMAT_S16P: return AL_FORMAT_MONO16;
|
||||
case AF_FORMAT_FLOATP:
|
||||
if (alIsExtensionPresent((ALchar*)"AL_EXT_float32") == AL_TRUE)
|
||||
return AF_FORMAT_FLOAT;
|
||||
return AL_FORMAT_MONO_FLOAT32;
|
||||
break;
|
||||
case AF_FORMAT_DOUBLEP:
|
||||
if (alIsExtensionPresent((ALchar*)"AL_EXT_double") == AL_TRUE)
|
||||
return AL_FORMAT_MONO_DOUBLE_EXT;
|
||||
break;
|
||||
}
|
||||
return AL_FALSE;
|
||||
}
|
||||
|
||||
static ALenum get_supported_layout(int format, int channels)
|
||||
{
|
||||
const char *channel_str[] = {
|
||||
[1] = "MONO",
|
||||
[2] = "STEREO",
|
||||
[4] = "QUAD",
|
||||
[6] = "51CHN",
|
||||
[7] = "61CHN",
|
||||
[8] = "71CHN",
|
||||
};
|
||||
const char *format_str[] = {
|
||||
[AF_FORMAT_U8] = "8",
|
||||
[AF_FORMAT_S16] = "16",
|
||||
[AF_FORMAT_S32] = "32",
|
||||
[AF_FORMAT_FLOAT] = "_FLOAT32",
|
||||
};
|
||||
if (channel_str[channels] == NULL || format_str[format] == NULL)
|
||||
return AL_FALSE;
|
||||
|
||||
char enum_name[32];
|
||||
// AF_FORMAT_FLOAT uses same enum name as AF_FORMAT_S32 for multichannel
|
||||
// playback, while it is different for mono and stereo.
|
||||
// OpenAL Soft does not support AF_FORMAT_S32 and seems to reuse the names.
|
||||
if (channels > 2 && format == AF_FORMAT_FLOAT)
|
||||
format = AF_FORMAT_S32;
|
||||
snprintf(enum_name, sizeof(enum_name), "AL_FORMAT_%s%s", channel_str[channels],
|
||||
format_str[format]);
|
||||
|
||||
if (alGetEnumValue((ALchar*)enum_name)) {
|
||||
return alGetEnumValue((ALchar*)enum_name);
|
||||
}
|
||||
return AL_FALSE;
|
||||
}
|
||||
@@ -160,15 +133,9 @@ static ALenum get_supported_layout(int format, int channels)
|
||||
// close audio device
|
||||
static void uninit(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
alSourceStop(source);
|
||||
alSourcei(source, AL_BUFFER, 0);
|
||||
|
||||
alDeleteBuffers(p->num_buffers, buffers);
|
||||
alDeleteSources(1, &source);
|
||||
|
||||
ALCcontext *ctx = alcGetCurrentContext();
|
||||
ALCdevice *dev = alcGetContextsDevice(ctx);
|
||||
reset(ao);
|
||||
alcMakeContextCurrent(NULL);
|
||||
alcDestroyContext(ctx);
|
||||
alcCloseDevice(dev);
|
||||
@@ -183,12 +150,30 @@ static int init(struct ao *ao)
|
||||
ALCcontext *ctx = NULL;
|
||||
ALCint freq = 0;
|
||||
ALCint attribs[] = {ALC_FREQUENCY, ao->samplerate, 0, 0};
|
||||
int i;
|
||||
struct priv *p = ao->priv;
|
||||
if (ao_data) {
|
||||
MP_FATAL(ao, "Not reentrant!\n");
|
||||
return -1;
|
||||
}
|
||||
ao_data = ao;
|
||||
struct mp_chmap_sel sel = {0};
|
||||
for (i = 0; speaker_pos[i].id != -1; i++)
|
||||
mp_chmap_sel_add_speaker(&sel, speaker_pos[i].id);
|
||||
if (!ao_chmap_sel_adjust(ao, &sel, &ao->channels))
|
||||
goto err_out;
|
||||
struct speaker speakers[MAX_CHANS];
|
||||
for (i = 0; i < ao->channels.num; i++) {
|
||||
speakers[i].id = -1;
|
||||
for (int n = 0; speaker_pos[n].id >= 0; n++) {
|
||||
if (speaker_pos[n].id == ao->channels.speaker[i])
|
||||
speakers[i] = speaker_pos[n];
|
||||
}
|
||||
if (speakers[i].id < 0) {
|
||||
MP_FATAL(ao, "Unknown channel layout\n");
|
||||
goto err_out;
|
||||
}
|
||||
}
|
||||
char *dev_name = ao->device;
|
||||
dev = alcOpenDevice(dev_name && dev_name[0] ? dev_name : NULL);
|
||||
if (!dev) {
|
||||
@@ -199,74 +184,37 @@ static int init(struct ao *ao)
|
||||
alcMakeContextCurrent(ctx);
|
||||
alListenerfv(AL_POSITION, position);
|
||||
alListenerfv(AL_ORIENTATION, direction);
|
||||
|
||||
alGenSources(1, &source);
|
||||
if (p->direct_channels && alGetEnumValue((ALchar*)"AL_DIRECT_CHANNELS_SOFT")) {
|
||||
alSourcei(source, alGetEnumValue((ALchar*)"AL_DIRECT_CHANNELS_SOFT"), AL_TRUE);
|
||||
alGenSources(ao->channels.num, sources);
|
||||
for (i = 0; i < ao->channels.num; i++) {
|
||||
cur_buf[i] = 0;
|
||||
unqueue_buf[i] = 0;
|
||||
alGenBuffers(NUM_BUF, buffers[i]);
|
||||
alSourcefv(sources[i], AL_POSITION, speakers[i].pos);
|
||||
alSource3f(sources[i], AL_VELOCITY, 0, 0, 0);
|
||||
}
|
||||
|
||||
cur_buf = 0;
|
||||
unqueue_buf = 0;
|
||||
for (int i = 0; i < p->num_buffers; ++i) {
|
||||
buffer_size[i] = 0;
|
||||
}
|
||||
|
||||
alGenBuffers(p->num_buffers, buffers);
|
||||
|
||||
alcGetIntegerv(dev, ALC_FREQUENCY, 1, &freq);
|
||||
if (alcGetError(dev) == ALC_NO_ERROR && freq)
|
||||
ao->samplerate = freq;
|
||||
|
||||
// Check sample format
|
||||
int try_formats[AF_FORMAT_COUNT + 1];
|
||||
enum af_format sample_format = 0;
|
||||
p->al_format = AL_FALSE;
|
||||
int try_formats[AF_FORMAT_COUNT];
|
||||
af_get_best_sample_formats(ao->format, try_formats);
|
||||
for (int n = 0; try_formats[n]; n++) {
|
||||
sample_format = get_supported_format(try_formats[n]);
|
||||
if (sample_format != AF_FORMAT_UNKNOWN) {
|
||||
p->al_format = get_al_format(try_formats[n]);
|
||||
if (p->al_format != AL_FALSE) {
|
||||
ao->format = try_formats[n];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (sample_format == AF_FORMAT_UNKNOWN) {
|
||||
if (p->al_format == AL_FALSE) {
|
||||
MP_FATAL(ao, "Can't find appropriate sample format.\n");
|
||||
uninit(ao);
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
// Check if OpenAL driver supports the desired number of channels.
|
||||
int num_channels = ao->channels.num;
|
||||
do {
|
||||
p->al_format = get_supported_layout(sample_format, num_channels);
|
||||
if (p->al_format == AL_FALSE) {
|
||||
num_channels = num_channels - 1;
|
||||
}
|
||||
} while (p->al_format == AL_FALSE && num_channels > 1);
|
||||
|
||||
// Request number of speakers for output from ao.
|
||||
const struct mp_chmap possible_layouts[] = {
|
||||
{0}, // empty
|
||||
MP_CHMAP_INIT_MONO, // mono
|
||||
MP_CHMAP_INIT_STEREO, // stereo
|
||||
{0}, // 2.1
|
||||
MP_CHMAP4(FL, FR, BL, BR), // 4.0
|
||||
{0}, // 5.0
|
||||
MP_CHMAP6(FL, FR, FC, LFE, BL, BR), // 5.1
|
||||
MP_CHMAP7(FL, FR, FC, LFE, SL, SR, BC), // 6.1
|
||||
MP_CHMAP8(FL, FR, FC, LFE, BL, BR, SL, SR), // 7.1
|
||||
};
|
||||
ao->channels = possible_layouts[num_channels];
|
||||
if (!ao->channels.num)
|
||||
mp_chmap_set_unknown(&ao->channels, num_channels);
|
||||
|
||||
if (p->al_format == AL_FALSE || !mp_chmap_is_valid(&ao->channels)) {
|
||||
MP_FATAL(ao, "Can't find appropriate channel layout.\n");
|
||||
uninit(ao);
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
ao->period_size = p->num_samples;
|
||||
p->chunk_size = CHUNK_SAMPLES * af_fmt_to_bytes(ao->format);
|
||||
ao->period_size = CHUNK_SAMPLES;
|
||||
return 0;
|
||||
|
||||
err_out:
|
||||
@@ -277,27 +225,30 @@ err_out:
|
||||
static void drain(struct ao *ao)
|
||||
{
|
||||
ALint state;
|
||||
alGetSourcei(source, AL_SOURCE_STATE, &state);
|
||||
alGetSourcei(sources[0], AL_SOURCE_STATE, &state);
|
||||
while (state == AL_PLAYING) {
|
||||
mp_sleep_us(10000);
|
||||
alGetSourcei(source, AL_SOURCE_STATE, &state);
|
||||
alGetSourcei(sources[0], AL_SOURCE_STATE, &state);
|
||||
}
|
||||
}
|
||||
|
||||
static void unqueue_buffers(struct ao *ao)
|
||||
static void unqueue_buffers(void)
|
||||
{
|
||||
struct priv *q = ao->priv;
|
||||
ALint p;
|
||||
int till_wrap = q->num_buffers - unqueue_buf;
|
||||
alGetSourcei(source, AL_BUFFERS_PROCESSED, &p);
|
||||
if (p >= till_wrap) {
|
||||
alSourceUnqueueBuffers(source, till_wrap, &buffers[unqueue_buf]);
|
||||
unqueue_buf = 0;
|
||||
p -= till_wrap;
|
||||
}
|
||||
if (p) {
|
||||
alSourceUnqueueBuffers(source, p, &buffers[unqueue_buf]);
|
||||
unqueue_buf += p;
|
||||
int s;
|
||||
for (s = 0; s < ao_data->channels.num; s++) {
|
||||
int till_wrap = NUM_BUF - unqueue_buf[s];
|
||||
alGetSourcei(sources[s], AL_BUFFERS_PROCESSED, &p);
|
||||
if (p >= till_wrap) {
|
||||
alSourceUnqueueBuffers(sources[s], till_wrap,
|
||||
&buffers[s][unqueue_buf[s]]);
|
||||
unqueue_buf[s] = 0;
|
||||
p -= till_wrap;
|
||||
}
|
||||
if (p) {
|
||||
alSourceUnqueueBuffers(sources[s], p, &buffers[s][unqueue_buf[s]]);
|
||||
unqueue_buf[s] += p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,8 +257,8 @@ static void unqueue_buffers(struct ao *ao)
|
||||
*/
|
||||
static void reset(struct ao *ao)
|
||||
{
|
||||
alSourceStop(source);
|
||||
unqueue_buffers(ao);
|
||||
alSourceStopv(ao->channels.num, sources);
|
||||
unqueue_buffers();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -315,7 +266,7 @@ static void reset(struct ao *ao)
|
||||
*/
|
||||
static void audio_pause(struct ao *ao)
|
||||
{
|
||||
alSourcePause(source);
|
||||
alSourcePausev(ao->channels.num, sources);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -323,19 +274,18 @@ static void audio_pause(struct ao *ao)
|
||||
*/
|
||||
static void audio_resume(struct ao *ao)
|
||||
{
|
||||
alSourcePlay(source);
|
||||
alSourcePlayv(ao->channels.num, sources);
|
||||
}
|
||||
|
||||
static int get_space(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
ALint queued;
|
||||
unqueue_buffers(ao);
|
||||
alGetSourcei(source, AL_BUFFERS_QUEUED, &queued);
|
||||
queued = p->num_buffers - queued;
|
||||
unqueue_buffers();
|
||||
alGetSourcei(sources[0], AL_BUFFERS_QUEUED, &queued);
|
||||
queued = NUM_BUF - queued - 3;
|
||||
if (queued < 0)
|
||||
return 0;
|
||||
return p->num_samples * queued;
|
||||
return queued * CHUNK_SAMPLES;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -344,66 +294,30 @@ static int get_space(struct ao *ao)
|
||||
static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
int buffered_samples = 0;
|
||||
int num = 0;
|
||||
if (flags & AOPLAY_FINAL_CHUNK) {
|
||||
num = 1;
|
||||
buffered_samples = samples;
|
||||
} else {
|
||||
num = samples / p->num_samples;
|
||||
buffered_samples = num * p->num_samples;
|
||||
}
|
||||
|
||||
for (int i = 0; i < num; i++) {
|
||||
char *d = *data;
|
||||
if (flags & AOPLAY_FINAL_CHUNK) {
|
||||
buffer_size[cur_buf] = samples;
|
||||
} else {
|
||||
buffer_size[cur_buf] = p->num_samples;
|
||||
}
|
||||
d += i * buffer_size[cur_buf] * ao->sstride;
|
||||
alBufferData(buffers[cur_buf], p->al_format, d,
|
||||
buffer_size[cur_buf] * ao->sstride, ao->samplerate);
|
||||
alSourceQueueBuffers(source, 1, &buffers[cur_buf]);
|
||||
cur_buf = (cur_buf + 1) % p->num_buffers;
|
||||
}
|
||||
|
||||
ALint state;
|
||||
alGetSourcei(source, AL_SOURCE_STATE, &state);
|
||||
int num = samples / CHUNK_SAMPLES;
|
||||
for (int i = 0; i < num; i++) {
|
||||
for (int ch = 0; ch < ao->channels.num; ch++) {
|
||||
char *d = data[ch];
|
||||
d += i * p->chunk_size;
|
||||
alBufferData(buffers[ch][cur_buf[ch]], p->al_format, d,
|
||||
p->chunk_size, ao->samplerate);
|
||||
alSourceQueueBuffers(sources[ch], 1, &buffers[ch][cur_buf[ch]]);
|
||||
cur_buf[ch] = (cur_buf[ch] + 1) % NUM_BUF;
|
||||
}
|
||||
}
|
||||
alGetSourcei(sources[0], AL_SOURCE_STATE, &state);
|
||||
if (state != AL_PLAYING) // checked here in case of an underrun
|
||||
alSourcePlay(source);
|
||||
|
||||
return buffered_samples;
|
||||
alSourcePlayv(ao->channels.num, sources);
|
||||
return num * CHUNK_SAMPLES;
|
||||
}
|
||||
|
||||
static double get_delay(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
ALint queued;
|
||||
unqueue_buffers(ao);
|
||||
alGetSourcei(source, AL_BUFFERS_QUEUED, &queued);
|
||||
|
||||
double soft_source_latency = 0;
|
||||
if(alIsExtensionPresent("AL_SOFT_source_latency")) {
|
||||
ALdouble offsets[2];
|
||||
LPALGETSOURCEDVSOFT alGetSourcedvSOFT = alGetProcAddress("alGetSourcedvSOFT");
|
||||
alGetSourcedvSOFT(source, AL_SEC_OFFSET_LATENCY_SOFT, offsets);
|
||||
// Additional latency to the play buffer, the remaining seconds to be
|
||||
// played minus the offset (seconds already played)
|
||||
soft_source_latency = offsets[1] - offsets[0];
|
||||
} else {
|
||||
float offset = 0;
|
||||
alGetSourcef(source, AL_SEC_OFFSET, &offset);
|
||||
soft_source_latency = -offset;
|
||||
}
|
||||
|
||||
int queued_samples = 0;
|
||||
for (int i = 0, index = cur_buf; i < queued; ++i) {
|
||||
queued_samples += buffer_size[index];
|
||||
index = (index + 1) % p->num_buffers;
|
||||
}
|
||||
return (queued_samples / (double)ao->samplerate) + soft_source_latency;
|
||||
unqueue_buffers();
|
||||
alGetSourcei(sources[0], AL_BUFFERS_QUEUED, &queued);
|
||||
return queued * CHUNK_SAMPLES / (double)ao->samplerate;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
@@ -422,16 +336,4 @@ const struct ao_driver audio_out_openal = {
|
||||
.reset = reset,
|
||||
.drain = drain,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.priv_defaults = &(const struct priv) {
|
||||
.num_buffers = 4,
|
||||
.num_samples = 8192,
|
||||
.direct_channels = 0,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_INTRANGE("num-buffers", num_buffers, 0, 2, MAX_BUF),
|
||||
OPT_INTRANGE("num-samples", num_samples, 0, 256, MAX_SAMPLES),
|
||||
OPT_FLAG("direct-channels", direct_channels, 0),
|
||||
{0}
|
||||
},
|
||||
.options_prefix = "openal",
|
||||
};
|
||||
+73
-85
@@ -35,13 +35,18 @@ struct priv {
|
||||
SLBufferQueueItf buffer_queue;
|
||||
SLEngineItf engine;
|
||||
SLPlayItf play;
|
||||
void *buf;
|
||||
int bytes_per_enqueue;
|
||||
char *curbuf, *buf1, *buf2;
|
||||
size_t buffer_size;
|
||||
pthread_mutex_t buffer_lock;
|
||||
double audio_latency;
|
||||
|
||||
int frames_per_enqueue;
|
||||
int buffer_size_in_ms;
|
||||
int cfg_frames_per_buffer;
|
||||
int cfg_sample_rate;
|
||||
};
|
||||
|
||||
static const int fmtmap[][2] = {
|
||||
{ AF_FORMAT_U8, SL_PCMSAMPLEFORMAT_FIXED_8 },
|
||||
{ AF_FORMAT_S16, SL_PCMSAMPLEFORMAT_FIXED_16 },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
#define DESTROY(thing) \
|
||||
@@ -64,8 +69,10 @@ static void uninit(struct ao *ao)
|
||||
|
||||
pthread_mutex_destroy(&p->buffer_lock);
|
||||
|
||||
free(p->buf);
|
||||
p->buf = NULL;
|
||||
free(p->buf1);
|
||||
free(p->buf2);
|
||||
p->curbuf = p->buf1 = p->buf2 = NULL;
|
||||
p->buffer_size = 0;
|
||||
}
|
||||
|
||||
#undef DESTROY
|
||||
@@ -75,22 +82,27 @@ static void buffer_callback(SLBufferQueueItf buffer_queue, void *context)
|
||||
struct ao *ao = context;
|
||||
struct priv *p = ao->priv;
|
||||
SLresult res;
|
||||
void *data[1];
|
||||
double delay;
|
||||
|
||||
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,
|
||||
data[0] = p->curbuf;
|
||||
delay = 2 * p->buffer_size / (double)ao->bps;
|
||||
ao_read_data(ao, data, p->buffer_size / ao->sstride,
|
||||
mp_time_us() + 1000000LL * delay);
|
||||
|
||||
res = (*buffer_queue)->Enqueue(buffer_queue, p->buf, p->bytes_per_enqueue);
|
||||
res = (*buffer_queue)->Enqueue(buffer_queue, p->curbuf, p->buffer_size);
|
||||
if (res != SL_RESULT_SUCCESS)
|
||||
MP_ERR(ao, "Failed to Enqueue: %d\n", res);
|
||||
else
|
||||
p->curbuf = (p->curbuf == p->buf1) ? p->buf2 : p->buf1;
|
||||
|
||||
pthread_mutex_unlock(&p->buffer_lock);
|
||||
}
|
||||
|
||||
#define DEFAULT_BUFFER_SIZE_MS 50
|
||||
|
||||
#define CHK(stmt) \
|
||||
{ \
|
||||
SLresult res = stmt; \
|
||||
@@ -105,7 +117,7 @@ static int init(struct ao *ao)
|
||||
struct priv *p = ao->priv;
|
||||
SLDataLocator_BufferQueue locator_buffer_queue;
|
||||
SLDataLocator_OutputMix locator_output_mix;
|
||||
SLAndroidDataFormat_PCM_EX pcm;
|
||||
SLDataFormat_PCM pcm;
|
||||
SLDataSource audio_source;
|
||||
SLDataSink audio_sink;
|
||||
|
||||
@@ -119,55 +131,48 @@ static int init(struct ao *ao)
|
||||
CHK((*p->output_mix)->Realize(p->output_mix, SL_BOOLEAN_FALSE));
|
||||
|
||||
locator_buffer_queue.locatorType = SL_DATALOCATOR_BUFFERQUEUE;
|
||||
locator_buffer_queue.numBuffers = 8;
|
||||
locator_buffer_queue.numBuffers = 2;
|
||||
|
||||
if (af_fmt_is_int(ao->format)) {
|
||||
// Be future-proof
|
||||
if (af_fmt_to_bytes(ao->format) > 2)
|
||||
ao->format = AF_FORMAT_S32;
|
||||
else
|
||||
ao->format = af_fmt_from_planar(ao->format);
|
||||
pcm.formatType = SL_DATAFORMAT_PCM;
|
||||
} else {
|
||||
ao->format = AF_FORMAT_FLOAT;
|
||||
pcm.formatType = SL_ANDROID_DATAFORMAT_PCM_EX;
|
||||
pcm.representation = SL_ANDROID_PCM_REPRESENTATION_FLOAT;
|
||||
pcm.formatType = SL_DATAFORMAT_PCM;
|
||||
pcm.numChannels = 2;
|
||||
|
||||
int compatible_formats[AF_FORMAT_COUNT];
|
||||
af_get_best_sample_formats(ao->format, compatible_formats);
|
||||
pcm.bitsPerSample = 0;
|
||||
for (int i = 0; compatible_formats[i] && !pcm.bitsPerSample; ++i)
|
||||
for (int j = 0; fmtmap[j][0]; ++j)
|
||||
if (compatible_formats[i] == fmtmap[j][0]) {
|
||||
ao->format = fmtmap[j][0];
|
||||
pcm.bitsPerSample = fmtmap[j][1];
|
||||
break;
|
||||
}
|
||||
if (!pcm.bitsPerSample) {
|
||||
MP_ERR(ao, "Cannot find compatible audio format\n");
|
||||
goto error;
|
||||
}
|
||||
pcm.numChannels = ao->channels.num;
|
||||
pcm.containerSize = pcm.bitsPerSample = 8 * af_fmt_to_bytes(ao->format);
|
||||
pcm.containerSize = 8 * af_fmt_to_bytes(ao->format);
|
||||
pcm.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
|
||||
pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
|
||||
pcm.sampleRate = ao->samplerate * 1000;
|
||||
|
||||
if (p->buffer_size_in_ms) {
|
||||
ao->device_buffer = ao->samplerate * p->buffer_size_in_ms / 1000;
|
||||
// As the purpose of buffer_size_in_ms is to request a specific
|
||||
// soft buffer size:
|
||||
ao->def_buffer = 0;
|
||||
}
|
||||
if (p->cfg_sample_rate)
|
||||
ao->samplerate = p->cfg_sample_rate;
|
||||
|
||||
// But it does not make sense if it is smaller than the enqueue size:
|
||||
if (p->frames_per_enqueue) {
|
||||
ao->device_buffer = MPMAX(ao->device_buffer, p->frames_per_enqueue);
|
||||
} else {
|
||||
if (ao->device_buffer) {
|
||||
p->frames_per_enqueue = ao->device_buffer;
|
||||
} else if (ao->def_buffer) {
|
||||
p->frames_per_enqueue = ao->def_buffer * ao->samplerate;
|
||||
} else {
|
||||
MP_ERR(ao, "Enqueue size is not set and can neither be derived\n");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
// samplesPerSec is misnamed, actually it's samples per ms
|
||||
pcm.samplesPerSec = ao->samplerate * 1000;
|
||||
|
||||
p->bytes_per_enqueue = p->frames_per_enqueue * ao->channels.num *
|
||||
if (p->cfg_frames_per_buffer)
|
||||
ao->device_buffer = p->cfg_frames_per_buffer;
|
||||
else
|
||||
ao->device_buffer = ao->samplerate * DEFAULT_BUFFER_SIZE_MS / 1000;
|
||||
p->buffer_size = ao->device_buffer * ao->channels.num *
|
||||
af_fmt_to_bytes(ao->format);
|
||||
p->buf = calloc(1, p->bytes_per_enqueue);
|
||||
if (!p->buf) {
|
||||
p->buf1 = calloc(1, p->buffer_size);
|
||||
p->buf2 = calloc(1, p->buffer_size);
|
||||
p->curbuf = p->buf1;
|
||||
if (!p->buf1 || !p->buf2) {
|
||||
MP_ERR(ao, "Failed to allocate device buffer\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
int r = pthread_mutex_init(&p->buffer_lock, NULL);
|
||||
if (r) {
|
||||
MP_ERR(ao, "Failed to initialize the mutex: %d\n", r);
|
||||
@@ -183,41 +188,16 @@ static int init(struct ao *ao)
|
||||
audio_sink.pLocator = (void*)&locator_output_mix;
|
||||
audio_sink.pFormat = NULL;
|
||||
|
||||
SLInterfaceID iid_array[] = { SL_IID_BUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION };
|
||||
SLboolean required[] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE };
|
||||
SLboolean required[] = { SL_BOOLEAN_TRUE };
|
||||
SLInterfaceID iid_array[] = { SL_IID_BUFFERQUEUE };
|
||||
CHK((*p->engine)->CreateAudioPlayer(p->engine, &p->player, &audio_source,
|
||||
&audio_sink, 2, iid_array, required));
|
||||
|
||||
&audio_sink, 1, iid_array, required));
|
||||
CHK((*p->player)->Realize(p->player, SL_BOOLEAN_FALSE));
|
||||
CHK((*p->player)->GetInterface(p->player, SL_IID_PLAY, (void*)&p->play));
|
||||
CHK((*p->player)->GetInterface(p->player, SL_IID_BUFFERQUEUE,
|
||||
(void*)&p->buffer_queue));
|
||||
CHK((*p->buffer_queue)->RegisterCallback(p->buffer_queue,
|
||||
buffer_callback, ao));
|
||||
CHK((*p->play)->SetPlayState(p->play, SL_PLAYSTATE_PLAYING));
|
||||
|
||||
SLAndroidConfigurationItf android_config;
|
||||
SLuint32 audio_latency = 0, value_size = sizeof(SLuint32);
|
||||
|
||||
SLint32 get_interface_result = (*p->player)->GetInterface(
|
||||
p->player,
|
||||
SL_IID_ANDROIDCONFIGURATION,
|
||||
&android_config
|
||||
);
|
||||
|
||||
if (get_interface_result == SL_RESULT_SUCCESS) {
|
||||
SLint32 get_configuration_result = (*android_config)->GetConfiguration(
|
||||
android_config,
|
||||
(const SLchar *)"androidGetAudioLatency",
|
||||
&value_size,
|
||||
&audio_latency
|
||||
);
|
||||
|
||||
if (get_configuration_result == SL_RESULT_SUCCESS) {
|
||||
p->audio_latency = (double)audio_latency / 1000.0;
|
||||
MP_INFO(ao, "Device latency is %f\n", p->audio_latency);
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
error:
|
||||
@@ -227,15 +207,26 @@ error:
|
||||
|
||||
#undef CHK
|
||||
|
||||
static void reset(struct ao *ao)
|
||||
static void set_play_state(struct ao *ao, SLuint32 state)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
(*p->buffer_queue)->Clear(p->buffer_queue);
|
||||
SLresult res = (*p->play)->SetPlayState(p->play, state);
|
||||
if (res != SL_RESULT_SUCCESS)
|
||||
MP_ERR(ao, "Failed to SetPlayState(%d): %d\n", state, res);
|
||||
}
|
||||
|
||||
static void reset(struct ao *ao)
|
||||
{
|
||||
set_play_state(ao, SL_PLAYSTATE_STOPPED);
|
||||
}
|
||||
|
||||
static void resume(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
set_play_state(ao, SL_PLAYSTATE_PLAYING);
|
||||
|
||||
// enqueue two buffers
|
||||
buffer_callback(p->buffer_queue, ao);
|
||||
buffer_callback(p->buffer_queue, ao);
|
||||
}
|
||||
|
||||
@@ -250,12 +241,9 @@ const struct ao_driver audio_out_opensles = {
|
||||
.resume = resume,
|
||||
|
||||
.priv_size = sizeof(struct priv),
|
||||
.priv_defaults = &(const struct priv) {
|
||||
.buffer_size_in_ms = 250,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_INTRANGE("frames-per-enqueue", frames_per_enqueue, 0, 1, 96000),
|
||||
OPT_INTRANGE("buffer-size-in-ms", buffer_size_in_ms, 0, 0, 500),
|
||||
OPT_INTRANGE("frames-per-buffer", cfg_frames_per_buffer, 0, 1, 10000),
|
||||
OPT_INTRANGE("sample-rate", cfg_sample_rate, 0, 1000, 100000),
|
||||
{0}
|
||||
},
|
||||
.options_prefix = "opensles",
|
||||
|
||||
+13
-17
@@ -56,11 +56,7 @@
|
||||
// Define to 0 if the device must be reopened to reset it (stop all playback,
|
||||
// clear the buffer), and the device should be closed when unused.
|
||||
// Define to 1 if SNDCTL_DSP_RESET should be used to reset without close.
|
||||
#if defined(SNDCTL_DSP_RESET) && !defined(__NetBSD__)
|
||||
#define KEEP_DEVICE 1
|
||||
#else
|
||||
#define KEEP_DEVICE 0
|
||||
#endif
|
||||
#define KEEP_DEVICE (defined(SNDCTL_DSP_RESET) && !defined(__NetBSD__))
|
||||
|
||||
#define PATH_DEV_DSP "/dev/dsp"
|
||||
#define PATH_DEV_MIXER "/dev/mixer"
|
||||
@@ -204,6 +200,10 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
}
|
||||
return CONTROL_ERROR;
|
||||
}
|
||||
#ifdef SNDCTL_DSP_GETPLAYVOL
|
||||
case AOCONTROL_HAS_SOFT_VOLUME:
|
||||
return CONTROL_TRUE;
|
||||
#endif
|
||||
}
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
@@ -311,7 +311,7 @@ static int reopen_device(struct ao *ao, bool allow_format_changes)
|
||||
}
|
||||
}
|
||||
|
||||
int try_formats[AF_FORMAT_COUNT + 1];
|
||||
int try_formats[AF_FORMAT_COUNT];
|
||||
af_get_best_sample_formats(format, try_formats);
|
||||
for (int n = 0; try_formats[n]; n++) {
|
||||
format = try_formats[n];
|
||||
@@ -332,23 +332,19 @@ static int reopen_device(struct ao *ao, bool allow_format_changes)
|
||||
mp_chmap_sel_add_map(&sel, &oss_layouts[n]);
|
||||
if (!ao_chmap_sel_adjust(ao, &sel, &channels))
|
||||
goto fail;
|
||||
int c, nchannels, reqchannels;
|
||||
nchannels = reqchannels = channels.num;
|
||||
int reqchannels = channels.num;
|
||||
// We only use SNDCTL_DSP_CHANNELS for >2 channels, in case some drivers don't have it
|
||||
if (reqchannels > 2) {
|
||||
if (ioctl(p->audio_fd, SNDCTL_DSP_CHANNELS, &nchannels) == -1) {
|
||||
int nchannels = reqchannels;
|
||||
if (ioctl(p->audio_fd, SNDCTL_DSP_CHANNELS, &nchannels) == -1 ||
|
||||
nchannels != reqchannels)
|
||||
{
|
||||
MP_ERR(ao, "Failed to set audio device to %d channels.\n",
|
||||
reqchannels);
|
||||
goto fail;
|
||||
}
|
||||
if (nchannels != reqchannels) {
|
||||
// Fallback to stereo
|
||||
nchannels = 2;
|
||||
goto stereo;
|
||||
}
|
||||
} else {
|
||||
stereo:
|
||||
c = nchannels - 1;
|
||||
int c = reqchannels - 1;
|
||||
if (ioctl(p->audio_fd, SNDCTL_DSP_STEREO, &c) == -1) {
|
||||
MP_ERR(ao, "Failed to set audio device to %d channels.\n",
|
||||
reqchannels);
|
||||
@@ -649,7 +645,7 @@ const struct ao_driver audio_out_oss = {
|
||||
.oss_mixer_device = PATH_DEV_MIXER,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_STRING("mixer-device", oss_mixer_device, M_OPT_FILE),
|
||||
OPT_STRING("mixer-device", oss_mixer_device, 0),
|
||||
OPT_STRING("mixer-channel", cfg_oss_mixer_channel, 0),
|
||||
{0}
|
||||
},
|
||||
|
||||
+14
-18
@@ -61,7 +61,6 @@ struct priv {
|
||||
char *cfg_host;
|
||||
int cfg_buffer;
|
||||
int cfg_latency_hacks;
|
||||
int cfg_allow_suspended;
|
||||
};
|
||||
|
||||
#define GENERIC_ERR_MSG(str) \
|
||||
@@ -202,18 +201,13 @@ static const struct format_map {
|
||||
static pa_encoding_t map_digital_format(int format)
|
||||
{
|
||||
switch (format) {
|
||||
case AF_FORMAT_S_AC3: return PA_ENCODING_AC3_IEC61937;
|
||||
case AF_FORMAT_S_EAC3: return PA_ENCODING_EAC3_IEC61937;
|
||||
case AF_FORMAT_S_MP3: return PA_ENCODING_MPEG_IEC61937;
|
||||
case AF_FORMAT_S_DTS: return PA_ENCODING_DTS_IEC61937;
|
||||
#ifdef PA_ENCODING_DTSHD_IEC61937
|
||||
case AF_FORMAT_S_DTSHD: return PA_ENCODING_DTSHD_IEC61937;
|
||||
#endif
|
||||
case AF_FORMAT_S_AC3: return PA_ENCODING_AC3_IEC61937;
|
||||
case AF_FORMAT_S_EAC3: return PA_ENCODING_EAC3_IEC61937;
|
||||
case AF_FORMAT_S_MP3: return PA_ENCODING_MPEG_IEC61937;
|
||||
case AF_FORMAT_S_DTS:
|
||||
case AF_FORMAT_S_DTSHD: return PA_ENCODING_DTS_IEC61937;
|
||||
#ifdef PA_ENCODING_MPEG2_AAC_IEC61937
|
||||
case AF_FORMAT_S_AAC: return PA_ENCODING_MPEG2_AAC_IEC61937;
|
||||
#endif
|
||||
#ifdef PA_ENCODING_TRUEHD_IEC61937
|
||||
case AF_FORMAT_S_TRUEHD: return PA_ENCODING_TRUEHD_IEC61937;
|
||||
case AF_FORMAT_S_AAC: return PA_ENCODING_MPEG2_AAC_IEC61937;
|
||||
#endif
|
||||
default:
|
||||
if (af_fmt_is_spdif(format))
|
||||
@@ -460,11 +454,11 @@ static int init(struct ao *ao)
|
||||
pa_stream_set_write_callback(priv->stream, stream_request_cb, ao);
|
||||
pa_stream_set_latency_update_callback(priv->stream,
|
||||
stream_latency_update_cb, ao);
|
||||
uint32_t buf_size = ao->samplerate * (priv->cfg_buffer / 1000.0) *
|
||||
af_fmt_to_bytes(ao->format) * ao->channels.num;
|
||||
int buf_size = af_fmt_seconds_to_bytes(ao->format, priv->cfg_buffer / 1000.0,
|
||||
ao->channels.num, ao->samplerate);
|
||||
pa_buffer_attr bufattr = {
|
||||
.maxlength = -1,
|
||||
.tlength = buf_size > 0 ? buf_size : -1,
|
||||
.tlength = buf_size > 0 ? buf_size : (uint32_t)-1,
|
||||
.prebuf = -1,
|
||||
.minreq = -1,
|
||||
.fragsize = -1,
|
||||
@@ -488,7 +482,7 @@ static int init(struct ao *ao)
|
||||
pa_threaded_mainloop_wait(priv->mainloop);
|
||||
}
|
||||
|
||||
if (pa_stream_is_suspended(priv->stream) && !priv->cfg_allow_suspended) {
|
||||
if (pa_stream_is_suspended(priv->stream)) {
|
||||
MP_ERR(ao, "The stream is suspended. Bailing out.\n");
|
||||
goto unlock_and_fail;
|
||||
}
|
||||
@@ -749,6 +743,9 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
return CONTROL_OK;
|
||||
}
|
||||
|
||||
case AOCONTROL_HAS_PER_APP_VOLUME:
|
||||
return CONTROL_TRUE;
|
||||
|
||||
case AOCONTROL_UPDATE_STREAM_TITLE: {
|
||||
char *title = (char *)arg;
|
||||
pa_threaded_mainloop_lock(priv->mainloop);
|
||||
@@ -834,13 +831,12 @@ const struct ao_driver audio_out_pulse = {
|
||||
.list_devs = list_devs,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.priv_defaults = &(const struct priv) {
|
||||
.cfg_buffer = 100,
|
||||
.cfg_buffer = 250,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_STRING("host", cfg_host, 0),
|
||||
OPT_CHOICE_OR_INT("buffer", cfg_buffer, 0, 1, 2000, ({"native", 0})),
|
||||
OPT_FLAG("latency-hacks", cfg_latency_hacks, 0),
|
||||
OPT_FLAG("allow-suspended", cfg_allow_suspended, 0),
|
||||
{0}
|
||||
},
|
||||
.options_prefix = "pulse",
|
||||
|
||||
+5
-7
@@ -113,7 +113,8 @@ static int init(struct ao *ao)
|
||||
|
||||
ao->format = af_fmt_from_planar(ao->format);
|
||||
|
||||
SDL_AudioSpec desired = {0};
|
||||
SDL_AudioSpec desired, obtained;
|
||||
|
||||
desired.format = AUDIO_S16SYS;
|
||||
for (int n = 0; fmtmap[n][0]; n++) {
|
||||
if (ao->format == fmtmap[n][0]) {
|
||||
@@ -123,10 +124,8 @@ static int init(struct ao *ao)
|
||||
}
|
||||
desired.freq = ao->samplerate;
|
||||
desired.channels = ao->channels.num;
|
||||
if (priv->buflen) {
|
||||
desired.samples = MPMIN(32768, ceil_power_of_two(ao->samplerate *
|
||||
priv->buflen));
|
||||
}
|
||||
desired.samples = MPMIN(32768, ceil_power_of_two(ao->samplerate *
|
||||
priv->buflen));
|
||||
desired.callback = audio_callback;
|
||||
desired.userdata = ao;
|
||||
|
||||
@@ -135,7 +134,7 @@ static int init(struct ao *ao)
|
||||
(int) desired.freq, (int) desired.channels,
|
||||
(int) desired.format, (int) desired.samples);
|
||||
|
||||
SDL_AudioSpec obtained = desired;
|
||||
obtained = desired;
|
||||
if (SDL_OpenAudio(&desired, &obtained)) {
|
||||
if (!ao->probing)
|
||||
MP_ERR(ao, "could not open audio: %s\n", SDL_GetError());
|
||||
@@ -205,7 +204,6 @@ const struct ao_driver audio_out_sdl = {
|
||||
.uninit = uninit,
|
||||
.reset = reset,
|
||||
.resume = resume,
|
||||
.reports_underruns = true,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.priv_defaults = &(const struct priv) {
|
||||
.buflen = 0, // use SDL default
|
||||
|
||||
@@ -61,6 +61,8 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
return CONTROL_FALSE;
|
||||
sio_setvol(p->hdl, vol->left * SIO_MAXVOL / 100);
|
||||
break;
|
||||
case AOCONTROL_HAS_SOFT_VOLUME:
|
||||
return CONTROL_TRUE;
|
||||
default:
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
|
||||
@@ -340,6 +340,8 @@ static int thread_control_exclusive(struct ao *ao, enum aocontrol cmd, void *arg
|
||||
if (!(state->vol_hw_support & ENDPOINT_HARDWARE_SUPPORT_MUTE))
|
||||
return CONTROL_FALSE;
|
||||
break;
|
||||
case AOCONTROL_HAS_PER_APP_VOLUME:
|
||||
return CONTROL_FALSE;
|
||||
}
|
||||
|
||||
float volume;
|
||||
@@ -398,6 +400,8 @@ static int thread_control_shared(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
mute = *(bool *)arg;
|
||||
ISimpleAudioVolume_SetMute(state->pAudioVolume, mute, NULL);
|
||||
return CONTROL_OK;
|
||||
case AOCONTROL_HAS_PER_APP_VOLUME:
|
||||
return CONTROL_TRUE;
|
||||
}
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ static const struct wasapi_sample_fmt wasapi_formats[] = {
|
||||
static void wasapi_get_best_sample_formats(
|
||||
int src_format, struct wasapi_sample_fmt *out_formats)
|
||||
{
|
||||
int mp_formats[AF_FORMAT_COUNT + 1];
|
||||
int mp_formats[AF_FORMAT_COUNT];
|
||||
af_get_best_sample_formats(src_format, mp_formats);
|
||||
for (int n = 0; mp_formats[n]; n++) {
|
||||
for (int i = 0; wasapi_formats[i].mp_format; i++) {
|
||||
|
||||
+2
-12
@@ -68,7 +68,7 @@ struct ao {
|
||||
char *redirect;
|
||||
|
||||
// Internal events (use ao_request_reload(), ao_hotplug_event())
|
||||
atomic_uint events_;
|
||||
atomic_int events_;
|
||||
|
||||
// Float gain multiplicator
|
||||
mp_atomic_float gain;
|
||||
@@ -130,15 +130,6 @@ struct ao_driver {
|
||||
const char *name;
|
||||
// Description shown with --ao=help.
|
||||
const char *description;
|
||||
// This requires waiting for a AO_EVENT_INITIAL_UNBLOCK event before the
|
||||
// first play() call is done. Encode mode uses this, and push mode
|
||||
// respects it automatically (don't use with pull mode).
|
||||
bool initially_blocked;
|
||||
// Whether underruns are strictly _always_ reported via ao_underrun_event().
|
||||
// Do not set this to true if underruns may be missed in some way. If the
|
||||
// AO can't guarantee to play silence after underruns, it may be better not
|
||||
// to set this.
|
||||
bool reports_underruns;
|
||||
// Init the device using ao->format/ao->channels/ao->samplerate. If the
|
||||
// device doesn't accept these parameters, you can attempt to negotiate
|
||||
// fallback parameters, and set the ao format fields accordingly.
|
||||
@@ -148,7 +139,7 @@ struct ao_driver {
|
||||
void (*uninit)(struct ao *ao);
|
||||
// push based: see ao_reset()
|
||||
// pull based: stop the audio callback
|
||||
void (*reset)(struct ao *ao);
|
||||
void (*reset)(struct ao*ao);
|
||||
// push based: see ao_pause()
|
||||
void (*pause)(struct ao *ao);
|
||||
// push based: see ao_resume()
|
||||
@@ -211,7 +202,6 @@ struct pollfd;
|
||||
int ao_wait_poll(struct ao *ao, struct pollfd *fds, int num_fds,
|
||||
pthread_mutex_t *lock);
|
||||
void ao_wakeup_poll(struct ao *ao);
|
||||
void ao_underrun_event(struct ao *ao);
|
||||
|
||||
bool ao_chmap_sel_adjust(struct ao *ao, const struct mp_chmap_sel *s,
|
||||
struct mp_chmap *map);
|
||||
|
||||
+2
-7
@@ -153,13 +153,8 @@ int ao_read_data(struct ao *ao, void **data, int samples, int64_t out_time_us)
|
||||
int buffered_bytes = mp_ring_buffered(p->buffers[0]);
|
||||
bytes = MPMIN(buffered_bytes, full_bytes);
|
||||
|
||||
if (full_bytes > bytes && !atomic_load(&p->draining)) {
|
||||
if (ao->driver->reports_underruns) {
|
||||
ao_underrun_event(ao);
|
||||
} else {
|
||||
atomic_fetch_add(&p->underflow, (full_bytes - bytes) / ao->sstride);
|
||||
}
|
||||
}
|
||||
if (buffered_bytes < bytes && !atomic_load(&p->draining))
|
||||
atomic_fetch_add(&p->underflow, (bytes - buffered_bytes) / ao->sstride);
|
||||
|
||||
if (bytes > 0)
|
||||
atomic_store(&p->end_time_us, out_time_us);
|
||||
|
||||
+1
-16
@@ -56,7 +56,6 @@ struct ao_push_state {
|
||||
bool still_playing;
|
||||
bool need_wakeup;
|
||||
bool paused;
|
||||
bool initial_unblocked;
|
||||
|
||||
// Whether the current buffer contains the complete audio.
|
||||
bool final_chunk;
|
||||
@@ -358,8 +357,7 @@ static void *playthread(void *arg)
|
||||
mpthread_set_name("ao");
|
||||
pthread_mutex_lock(&p->lock);
|
||||
while (!p->terminate) {
|
||||
bool blocked = ao->driver->initially_blocked && !p->initial_unblocked;
|
||||
bool playing = (!p->paused || ao->stream_silence) && !blocked;
|
||||
bool playing = !p->paused || ao->stream_silence;
|
||||
if (playing)
|
||||
ao_play_data(ao);
|
||||
|
||||
@@ -504,19 +502,6 @@ int ao_play_silence(struct ao *ao, int samples)
|
||||
return ao->driver->play(ao, (void **)p->silence, samples, 0);
|
||||
}
|
||||
|
||||
void ao_unblock(struct ao *ao)
|
||||
{
|
||||
if (ao->api == &ao_api_push) {
|
||||
struct ao_push_state *p = ao->api_priv;
|
||||
pthread_mutex_lock(&p->lock);
|
||||
p->need_wakeup = true;
|
||||
p->initial_unblocked = true;
|
||||
wakeup_playthread(ao);
|
||||
pthread_cond_signal(&p->wakeup);
|
||||
pthread_mutex_unlock(&p->lock);
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef __MINGW32__
|
||||
|
||||
#include <poll.h>
|
||||
|
||||
+3
-7
@@ -1,14 +1,14 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python
|
||||
|
||||
# This script simply downloads waf to the current directory
|
||||
|
||||
from __future__ import print_function
|
||||
import os, sys, stat, hashlib, subprocess
|
||||
|
||||
WAFRELEASE = "waf-2.0.9"
|
||||
WAFRELEASE = "waf-1.9.8"
|
||||
WAFURLS = ["https://waf.io/" + WAFRELEASE,
|
||||
"http://www.freehackers.org/~tnagy/release/" + WAFRELEASE]
|
||||
SHA256HASH = "2a8e0816f023995e557f79ea8940d322bec18f286917c8f9a6fa2dc3875dfa48"
|
||||
SHA256HASH = "167dc42bab6d5bd823b798af195420319cb5c9b571e00db7d83df2a0fe1f4dbf"
|
||||
|
||||
if os.path.exists("waf"):
|
||||
wafver = subprocess.check_output([sys.executable, './waf', '--version']).decode()
|
||||
@@ -16,10 +16,6 @@ if os.path.exists("waf"):
|
||||
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)
|
||||
|
||||
try:
|
||||
from urllib.request import urlopen, URLError
|
||||
except:
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
FFMPEG_SYSROOT="${HOME}/deps/sysroot"
|
||||
MPV_INSTALL_PREFIX="${HOME}/out/mpv"
|
||||
MPV_VARIANT="${TRAVIS_OS_NAME}"
|
||||
|
||||
if [[ -d "./build/${MPV_VARIANT}" ]] ; then
|
||||
rm -rf "./build/${MPV_VARIANT}"
|
||||
fi
|
||||
|
||||
if [[ ! -e "./waf" ]] ; then
|
||||
python3 ./bootstrap.py
|
||||
fi
|
||||
|
||||
PKG_CONFIG_PATH="${FFMPEG_SYSROOT}/lib/pkgconfig/" CC="${CC}" CXX="${CXX}" python3 \
|
||||
./waf configure \
|
||||
--variant="${MPV_VARIANT}" \
|
||||
--prefix="${MPV_INSTALL_PREFIX}" \
|
||||
--enable-{gl,iconv,lcms2,libass,libass-osd,libmpv-shared,lua,jpeg,plain-gl,zlib} \
|
||||
--enable-{cocoa,coreaudio,gl-cocoa,macos-cocoa-cb,macos-touchbar,videotoolbox-gl}
|
||||
|
||||
python3 ./waf build --variant="${MPV_VARIANT}" -j4
|
||||
|
||||
python3 ./waf install --variant="${MPV_VARIANT}"
|
||||
@@ -1,33 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
_mingw_sysroot=/usr/$TARGET/sysroot
|
||||
_mingw_prefix=$_mingw_sysroot/mingw
|
||||
_mingw_exec_prefix=$_mingw_prefix
|
||||
_mingw_libdir=$_mingw_exec_prefix/lib
|
||||
_mingw_datadir=$_mingw_prefix/share
|
||||
|
||||
export PKG_CONFIG_PATH="$_mingw_libdir/pkgconfig:$_mingw_datadir/pkgconfig";
|
||||
export CC=$TARGET-gcc
|
||||
export CXX=$TARGET-g++
|
||||
export AR=$TARGET-ar
|
||||
export NM=$TARGET-nm
|
||||
export RANLIB=$TARGET-ranlib
|
||||
export CFLAGS="-O2 -mtune=intel -g -ggdb -pipe -Wall --param=ssp-buffer-size=4 -mms-bitfields -fmessage-length=0 -D_FORTIFY_SOURCE=2 -fexceptions -fasynchronous-unwind-tables -fstack-protector-strong -fno-ident"
|
||||
export LDFLAGS="-Wl,--no-keep-memory -fstack-protector-strong"
|
||||
|
||||
python3 ./waf configure \
|
||||
--enable-static-build \
|
||||
--enable-libmpv-shared \
|
||||
--enable-lua \
|
||||
--enable-javascript \
|
||||
--enable-libarchive \
|
||||
--enable-libass \
|
||||
--enable-libbluray \
|
||||
--enable-dvdnav \
|
||||
--enable-uchardet \
|
||||
--enable-vulkan \
|
||||
--enable-shaderc \
|
||||
--enable-rubberband \
|
||||
--enable-lcms2
|
||||
python3 ./waf build --verbose
|
||||
@@ -1,14 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
python3 ./waf configure \
|
||||
--enable-cdda \
|
||||
--enable-dvbin \
|
||||
--enable-dvdnav \
|
||||
--enable-libarchive \
|
||||
--enable-libmpv-shared \
|
||||
--enable-libsmbclient \
|
||||
--enable-manpage-build \
|
||||
--enable-shaderc \
|
||||
--enable-vulkan
|
||||
python3 ./waf build --verbose
|
||||
@@ -1,38 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
FFMPEG_SRC_DIR="${HOME}/deps/src/ffmpeg"
|
||||
FFMPEG_BUILD_DIR="${FFMPEG_SRC_DIR}/${TRAVIS_OS_NAME}"
|
||||
FFMPEG_SYSROOT="${HOME}/deps/sysroot"
|
||||
FFMPEG_HASH="18928e2bb4568cbe5e9061c3e6b63559392af3d2"
|
||||
|
||||
# Get the sauce if not around
|
||||
if [[ ! -d "${FFMPEG_SRC_DIR}" ]] ; then
|
||||
git clone "https://git.videolan.org/git/ffmpeg.git" "${FFMPEG_SRC_DIR}"
|
||||
fi
|
||||
|
||||
# pop into FFmpeg's source dir and clean up & check out our wanted revision
|
||||
pushd "${FFMPEG_SRC_DIR}"
|
||||
git reset --hard HEAD && git clean -dfx
|
||||
git checkout "${FFMPEG_HASH}"
|
||||
popd
|
||||
|
||||
# If a build dir of the same type is around, clean it up
|
||||
if [[ -d "${FFMPEG_BUILD_DIR}" ]] ; then
|
||||
rm -rf "${FFMPEG_BUILD_DIR}"
|
||||
fi
|
||||
|
||||
# Create and move into the build dir, configure and build!
|
||||
mkdir -p "${FFMPEG_BUILD_DIR}" && pushd "${FFMPEG_BUILD_DIR}"
|
||||
|
||||
PKG_CONFIG_PATH="${FFMPEG_SYSROOT}/lib/pkgconfig/" ../configure \
|
||||
--disable-{autodetect,stripping} \
|
||||
--cc="${CC}" \
|
||||
--cxx="${CXX}" \
|
||||
--prefix="${FFMPEG_SYSROOT}" \
|
||||
--enable-{zlib,securetransport,videotoolbox}
|
||||
|
||||
make -j4 && make install && popd
|
||||
|
||||
exit 0
|
||||
Loaded 100 of 554 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user