Compare commits

..
6 Commits
Author SHA1 Message Date
Kevin Mitchell 7214f1f076 Release 0.28.2 2018-02-12 18:07:05 -08:00
Ricardo Constantino 789e04e9a0 ytdl_hook: whitelist subtitle URLs as well
This was overlooked when doing the whitelisting for video and audio to
fix #5456.
2018-02-12 17:43:55 -08:00
Kevin Mitchell 2ae812ec6d Release 0.28.1 2018-02-10 05:53:02 -08:00
Ricardo Constantino 417fe22f67 ytdl_hook: fix safe url checking with EDL urls 2018-02-10 05:49:33 -08:00
Ricardo Constantino b889623768 ytdl_hook: move url_is_safe earlier in code
lua isn't javascript.
2018-02-10 05:49:19 -08:00
Ricardo Constantino db0af67e7e ytdl_hook: whitelist protocols from urls retrieved from youtube-dl
Not very clean since there's a lot of potential unsafe urls that youtube-dl
can give us, depending on whether it's a single url, split tracks,
playlists, segmented dash, etc.
2018-02-10 05:48:45 -08:00
554 changed files with 44039 additions and 58766 deletions

No files matched your search

+6 -6
View File
@@ -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
+6 -4
View File
@@ -1,5 +1,7 @@
Read this before you submit this pull request:
https://github.com/mpv-player/mpv/blob/master/DOCS/contribute.md
Ideally, you shouldn't need enter any text here, and your commit messages should
explain your changes sufficiently (especially why they are needed). Read
https://github.com/mpv-player/mpv/blob/master/DOCS/contribute.md for coding
style and development conventions. Remove this text block, but if you haven't
agreed to it before, leave the following sentence in place:
Reading this link and following the rules will get your pull request reviewed
and merged faster. Nobody wants lazy pull requests.
I agree that my changes can be relicensed to LGPL 2.1 or later.
+23 -49
View File
@@ -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
+24 -4
View File
@@ -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).
+1 -77
View File
@@ -16,7 +16,7 @@ The version number is the same as used for MPV_CLIENT_API_VERSION (see
``client.h`` how to convert between major/minor version numbers and the flat
32 bit integer).
Also, read the section ``Compatibility`` in ``client.h``, and compatibility.rst.
Also, read the section ``Compatibility`` in ``client.h``.
Options, commands, properties
=============================
@@ -32,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
-177
View File
@@ -1,177 +0,0 @@
CLI and API compatibility policy
================================
Human users and API users rely on the mpv/libmpv/scripting/IPC interface not
breaking their use case. On the other hand, active development occasionally
requires breaking things, such as removing old options or changing options in
a way that may break.
This document lists rules when, what, and how incompatible changes can be made.
It's interesting both for mpv developers, who want to change user-visible parts,
and mpv users, who want to know what is guaranteed to remain stable.
Any of the rules below may be overridden by statements in more specific
documentation (for example, if the manpage says that a particular option may be
removed any time, it means that, and the option probably won't even go through
deprecation).
Additions
---------
Additions are basically always allowed. API users etc. are supposed to deal with
the possibility that for example new API functions are added. Some parts of the
API may document how they are extended.
Options, commands, properties, events, hooks (command interface)
----------------------------------------------------------------
All of these are important for interfacing both with end users and API users
(which include Lua scripts, libmpv, and the JSON IPC). As such, they constitute
a large part of the user interface and APIs.
All incompatible changes to this must be declared in interface-changes.rst.
(This may also list compatible additions, but it's not a requirement.)
Degrees of importance and compatibility preservation
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Critical and central parts of the command interface have the strictest
requirements. It may not be reasonable to break them, and other means to achieve
some change have to be found. For example, the "seek" command is a bit of a
mess, but since changing it would likely affect almost every user, it may be
impossible to break at least the commonly used syntax. If changed anyway, there
should be a deprecation period of at least 1 year, during which the command
still works, and possibly a warning should remain even after this.
Important/often used parts must be deprecated for at least 2 releases before
they can be broken. There must be at least 1 release where the deprecated
functionality still works, and a replacement is available (if technically
reasonable). For example, a feature deprecated in mpv 0.30.0 may be removed in
mpv 0.32.0. Minor releases do not count towards this.
Less useful parts can be broken immediately, but must come with some sort of
removal warning-
Parts for debugging and testing may be removed any time, potentially even
without any sort of documentation.
Currently, the importance of a part is not documented and not even well-defined,
which is probably a mistake.
Renaming or removing options
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Typically, renaming an option can be done in a compatible way with OPT_REPLACED.
You may need to check whether the corresponding properties still work (including
messy details like observing properties for changes).
OPT_REMOVED can be used to inform the user of alternatives or reasons for the
removal, which is better than an option not found error. Likewise,
m_option.deprecation_message should be set to something helpful.
Both OPT_REPLACED and OPT_REMOVED can remain in the code for a long time, since
they're unintrusive and hopefully make incompatible changes less painful for
users.
Scripting APIs
--------------
This affects internal scripting APIs (currently Lua and JavaScript).
Vaguely the same rules as with the command interface apply. Since there is a
large number of scripts, an effort should be made to provide compatibility
for old scripts, but it does not need to be stronger than that of the command
interface.
Undocumented parts of the scripting APIs are _not_ guaranteed for compatibility.
This applies especially for internals. Languages like Lua do not have strict
access control (nor does the mpv code try to emulate any), so if a script
accesses private parts, and breaks on the next mpv release, it's not mpv's
problem.
JSON IPC
--------
The JSON IPC is a thin protocol wrapping the libmpv API and the command
interface. Compatibility-wise, it's about the same as the scripting APIs.
The JSON protocol commands should remain as compatible as possible, and it
should probably accept the current way commands are delimited (line breaks)
forever.
The protocol may accept non-standard JSON extensions, but only standard JSON
(possibly with restrictions) is guaranteed for compatibility. Clients which want
to remain compatible should not use any extensions.
CLI
---
Things such as default key bindings do not necessarily require compatibility.
However, the release notes should be extremely clear on changes to "important"
key bindings. Bindings which restore the old behavior should be added to
restore-old-bindings.conf.
Some option parsing is CLI-only and not available from libmpv or scripting. No
compatibility guarantees come with them. However, the rules which mpv uses to
distinguish between options and filenames must remain consistent (if the
non-deprecated options syntax is used).
Terminal and log output
-----------------------
There are no compatibility guarantees for the terminal output, or the text
logged via ``MPV_EVENT_LOG_MESSAGE`` and similar APIs. In particular, scripts
invoking mpv CLI are extremely discouraged from trying to parse text output,
and should use other mechanisms such as the JSON IPC.
Protocols, filters, demuxers, etc.
----------------------------------
Which of these are present is generally not guaranteed, and can even depend
on compile time settings.
The filter list and their sub-options are considered part of the
command-interface.
libmpv C API
------------
The libmpv client API (such as ``<libmpv/client.h>``) mostly gives access to
the command interface. The API itself (if looked at as a component separate
from the command interface) is intended to be extremely stable.
All API changes are documented in client-api-changes.rst.
API compatibility
^^^^^^^^^^^^^^^^^
The API is *always* compatible. Incompatible changes are only allowed on major
API version changes (see ``MPV_CLIENT_API_VERSION``). A major version change is
an extremely rare event, which means usually no API symbols are ever removed.
Essentially removing API functions by making them always return an error, or
making it do nothing is allowed in cases where it is unlikely to break most
clients, but requires a deprecation period of 2 releases. (This has happened to
``mpv_suspend()`` for example.)
API symbols can be deprecated. This should be clearly marked in the doxygen
with ``@deprecated``, and if possible, the affected API symbols should not be
visible if the API user defines ``MPV_ENABLE_DEPRECATED`` to 0.
ABI compatibility
^^^^^^^^^^^^^^^^^
The ABI must never be broken, except on major API version changes. For example,
constants don't change their values.
Structs are tricky. If a struct can be allocated by a user (such as ``mpv_node``),
no fields can be added. (Unless it's an union, and the addition does not change
the offset or alignment of any of the fields or the struct itself. This has
happened to ``mpv_node`` in the past.) If a struct is allocated by libmpv only,
new fields can be appended to the end (for example ``mpv_event``).
The ABI is only backward compatible. This means if a host application is linked
to an older libmpv, and libmpv is updated to a newer version, it will still
work (as in not causing any undefined behavior).
Forward compatibility (an application would work with an older libmpv than it
was linked to) is not required.
+3 -7
View File
@@ -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
View File
@@ -5,60 +5,35 @@ General
-------
The main contact for mpv development is IRC, specifically #mpv
and #mpv-devel on Freenode. Github is used for code review and
long term discussions.
and #mpv-devel on Freenode.
Sending patches
---------------
- Make a github pull request, or send a link to a plaintext patch created with
``git format-patch``.
- Plain diffs posted as pastebins are not acceptable! (Especially if the http
link returns HTML.) They only cause extra work for everyone, because they lack
commit message and authorship information.
- Never send patches to any of the developers email addresses.
- If your changes are not supposed to be merged immediately, mark them as
"[RFC]" in the commit message or the pull request title.
- Be sure to test your changes. If you didn't, please say so in the commit
message and the pull request text.
Copyright of contributions
--------------------------
- The copyright belongs to contributors. The project is a collaborative work. By
sending your changes, you agree to license your contributions according to the
requirements of this project.
``git format-patch``. diffs posted as pastebins (especially if the http link
returns HTML) just cause extra work for everyone, because they lack commit
message and authorship information.
- All new code must be LGPLv2.1+ licensed, or come with the implicit agreement
that it will be relicensed to LGPLv2.1+ later (see ``Copyright`` in the
repository root directory).
- 100% compatible licenses are allowed too.
- Changes in files with more liberal licenses (such as BSD, MIT, or ISC) are
assumed to be dual-licensed under LGPLv2.1+ and the license indicated in the
file header.
- You must be either the exclusive author of the patch, or acknowledge all
authors involved in the commit message. If you take 3rd party code, authorship
and copyright must be properly acknowledged. If you're making changes on
behalf of your employer, and the employer owns the copyright, you must mention
this. If the license of the code is not LGPLv2.1+, you must mention this.
- These license statements are legally binding.
- Don't use fake names (something that looks like an actual name, and may be
and copyright must be properly acknowledged. If the license of the code is not
LGPLv2.1+, this must be mentioned.
- Don't use fake names (something that looks like an actual names, and may be
someone else's name, but is not your legal name). Using a pseudonyms is
allowed if it can be used to identify or contact you, even if whatever
account you used to submit the patch dies.
- Do not add your name to the license header. This convention is not used by
this project, and neither copyright law nor any of the used licenses require
it.
Write good commit messages
--------------------------
- When creating pull requests, be sure to test your changes. If you didn't,
please say so in the pull request message.
- Write informative commit messages. Use present tense to describe the
situation with the patch applied, and past tense for the situation before
the change.
- The subject line (the first line in a commit message) must contain a
- The subject line (the first line in a commit message) should contain a
prefix identifying the sub system, followed by a short description what
impact this commit has. This subject line and the commit message body
must not be longer than 72 characters per line, because it messes up the
shouldn't be longer than 72 characters per line, because it messes up the
output of many git tools otherwise.
For example, you fixed a crash in af_volume.c:
@@ -68,11 +43,7 @@ Write good commit messages
Having a prefix gives context, and is especially useful when trying to find
a specific change by looking at the history, or when running ``git blame``.
- The first word after the ``:`` is lower case.
- Don't end the subject line with a ``.``.
- Put an empty line between the subject line and the commit message.
If this is missing, it will break display in common git tools.
- The body of the commit message (everything else after the subject line) must
- The body of the commit message (everything else after the subject line) should
be as informative as possible and contain everything that isn't obvious. Don't
hesitate to dump as much information as you can - it doesn't cost you
anything. Put some effort into it. If someone finds a bug months or years
@@ -81,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
-99
View File
@@ -100,18 +100,6 @@ entries affect all other file entries in the EDL file. Their format is highly
implementation specific. They should generally follow the file header, and come
before any file entries.
Disabling chapter generation and copying
========================================
By default, chapters from the source ranges are copied to the virtual file's
chapters. Also, a chapter is inserted after each range. This can be disabled
with the ``no_chapters`` header.
Example::
!no_chapters
MP4 DASH
========
@@ -143,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
View File
@@ -3,7 +3,8 @@ General usage
::
mpv infile -o outfile [-of outfileformat] [-ofopts formatoptions] [-orawts] \
mpv infile -o outfile [-of outfileformat] [-ofopts formatoptions] \
[-ofps outfps | -oautofps] [-oharddup] [-ocopyts | -orawts] [-oneverdrop] \
[(any other mpv options)] \
-ovc outvideocodec [-ovcopts outvideocodecoptions] \
-oac outaudiocodec [-oacopts outaudiocodecoptions]
@@ -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
View File
@@ -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
View File
@@ -5,8 +5,7 @@ Audio filters allow you to modify the audio stream and its properties. The
syntax is:
``--af=...``
Setup a chain of audio filters. See ``--vf`` (`VIDEO FILTERS`_) for the
full syntax.
Setup a chain of audio filters. See ``--vf`` for the syntax.
.. note::
@@ -24,6 +23,37 @@ See ``--vf`` group of options for info on how ``--af-defaults``, ``--af-add``,
Available filters are:
``lavrresample[=option1:option2:...]``
This filter uses libavresample (or libswresample, depending on the build)
to change sample rate, sample format, or channel layout of the audio stream.
This filter is automatically enabled if the audio output does not support
the audio configuration of the file being played.
It supports only the following sample formats: u8, s16, s32, float.
``filter-size=<length>``
Length of the filter with respect to the lower sampling rate. (default:
16)
``phase-shift=<count>``
Log2 of the number of polyphase entries. (..., 10->1024, 11->2048,
12->4096, ...) (default: 10->1024)
``cutoff=<cutoff>``
Cutoff frequency (0.0-1.0), default set depending upon filter length.
``linear``
If set then filters will be linearly interpolated between polyphase
entries. (default: no)
``no-detach``
Do not detach if input and output audio format/rate/channels match.
(If you just want to set defaults for this filter that will be used
even by automatically inserted lavrresample instances, you should
prefer setting them with ``--af-defaults=lavrresample:...``.)
``normalize=<yes|no|auto>``
Whether to normalize when remixing channel layouts (default: auto).
``auto`` uses the value set by ``--audio-normalize-downmix``.
``o=<string>``
Set AVOptions on the SwrContext or AVAudioResampleContext. These should
be documented by FFmpeg or Libav.
``lavcac3enc[=options]``
Encode multi-channel audio to AC-3 at runtime using libavcodec. Supports
16-bit native-endian input format, maximum 6 channels. The output is
@@ -61,7 +91,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
View File
@@ -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
-107
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large. Load diff
+10 -41
View File
@@ -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
View File
@@ -27,16 +27,16 @@ otherwise, the documented Lua options, script directories, loading, etc apply to
JavaScript files too.
Script initialization and lifecycle is the same as with Lua, and most of the Lua
functions at the modules ``mp``, ``mp.utils``, ``mp.msg`` and ``mp.options`` are
available to JavaScript with identical APIs - including running commands,
getting/setting properties, registering events/key-bindings/hooks, etc.
functions at the modules ``mp``, ``mp.utils`` and ``mp.msg`` are available to
JavaScript with identical APIs - including running commands, getting/setting
properties, registering events/key-bindings/property-changes/hooks, etc.
Differences from Lua
--------------------
No need to load modules. ``mp``, ``mp.utils``, ``mp.msg`` and ``mp.options``
are preloaded, and you can use e.g. ``var cwd = mp.utils.getcwd();`` without
prior setup.
No need to load modules. ``mp``, ``mp.utils`` and ``mp.msg`` are preloaded, and
you can use e.g. ``var cwd = mp.utils.getcwd();`` without prior setup.
``mp.options`` is currently not implemented, but ``mp.get_opt(...)`` is.
Errors are slightly different. Where the Lua APIs return ``nil`` for error,
the JavaScript ones return ``undefined``. Where Lua returns ``something, error``
@@ -67,6 +67,10 @@ Unsupported Lua APIs and their JS alternatives
``mp.add_periodic_timer(seconds, fn)`` JS: ``id = setInterval(fn, ms)``
``mp.register_idle(fn)`` JS: ``id = setTimeout(fn)``
``mp.unregister_idle(fn)`` JS: ``clearTimeout(id)``
``utils.parse_json(str [, trail])`` JS: ``JSON.parse(str)``
``utils.format_json(v)`` JS: ``JSON.stringify(v)``
@@ -83,12 +87,14 @@ Unsupported Lua APIs and their JS alternatives
``mp.dispatch_events([allow_wait])`` see event loop below.
``mp.options`` module is not implemented currently for JS.
Scripting APIs - identical to Lua
---------------------------------
(LE) - Last-Error, indicates that ``mp.last_error()`` can be used after the
call to test for success (empty string) or failure (non empty reason string).
Where the Lua APIs use ``nil`` to indicate error, JS APIs use ``undefined``.
Otherwise, where the Lua APIs return ``nil`` on error, JS returns ``undefined``.
``mp.command(string)`` (LE)
@@ -96,11 +102,6 @@ Where the Lua APIs use ``nil`` to indicate error, JS APIs use ``undefined``.
``mp.command_native(table [,def])`` (LE)
``id = mp.command_native_async(table [,fn])`` (LE) Notes: ``id`` is true-thy on
success, ``fn`` is called always a-sync, ``error`` is empty string on success.
``mp.abort_async_command(id)``
``mp.get_property(name [,def])`` (LE)
``mp.get_property_osd(name [,def])`` (LE)
@@ -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
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large. Load diff
+4 -92
View File
@@ -128,7 +128,7 @@ Configuration
-------------
The OSC offers limited configuration through a config file
``script-opts/osc.conf`` placed in mpv's user dir and through the
``lua-settings/osc.conf`` placed in mpv's user dir and through the
``--script-opts`` command-line option. Options provided through the command-line
will override those from the config file.
@@ -167,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
View File
@@ -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
View File
@@ -1,11 +1,8 @@
VIDEO FILTERS
=============
Video filters allow you to modify the video stream and its properties. All of
the information described in this section applies to audio filters as well
(generally using the prefix ``--af`` instead of ``--vf``).
The exact syntax is:
Video filters allow you to modify the video stream and its properties. The
syntax is:
``--vf=<filter1[=parameter1:parameter2:...],filter2,...>``
Setup a chain of video filters. This consists on the filter name, and an
@@ -16,7 +13,7 @@ The exact syntax is:
name is an arbitrary user-given name, which identifies the filter. This
is only needed if you want to toggle the filter at runtime.
A ``!`` before the filter name means the filter is disabled by default. It
A ``!`` before the filter name means the filter is enabled by default. It
will be skipped on filter creation. This is also useful for runtime filter
toggling.
@@ -44,18 +41,11 @@ The exact syntax is:
the ``lavfi`` filter, which uses a very similar syntax as mpv (MPlayer
historically) to specify filters and their parameters.
Filters can be manipulated at run time. You can use ``@`` labels as described
above in combination with the ``vf`` command (see `COMMAND INTERFACE`_) to get
more control over this. Initially disabled filters with ``!`` are useful for
this as well.
You can also set defaults for each filter. The defaults are applied before the
normal filter parameters. This is deprecated and never worked for the
libavfilter bridge.
normal filter parameters.
``--vf-defaults=<filter1[=parameter1:parameter2:...],filter2,...>``
Set defaults for each filter. (Deprecated. ``--af-defaults`` is deprecated
as well.)
Set defaults for each filter.
.. note::
@@ -85,35 +75,15 @@ libavfilter bridge.
Video filters are managed in lists. There are a few commands to manage the
filter list.
``--vf-append=filter``
Appends the filter given as arguments to the filter list.
``--vf-add=<filter1[,filter2,...]>``
Appends the filters given as arguments to the filter list.
``--vf-add=filter``
Appends the filter given as arguments to the filter list. (Passing multiple
filters is currently still possible, but deprecated.)
``--vf-pre=<filter1[,filter2,...]>``
Prepends the filters given as arguments to the filter list.
``--vf-pre=filter``
Prepends the filters given as arguments to the filter list. (Passing
multiple filters is currently still possible, but deprecated.)
``--vf-remove=filter``
Deletes the filter from the list. The filter can be either given the way it
was added (filter name and its full argument list), or by label (prefixed
with ``@``). Matching of filters works as follows: if either of the compared
filters has a label set, only the labels are compared. If none of the
filters have a label, the filter name, arguments, and argument order are
compared. (Passing multiple filters is currently still possible, but
deprecated.)
``-vf-toggle=filter``
Add the given filter to the list if it was not present yet, or remove it
from the list if it was present. Matching of filters works as described in
``--vf-remove``.
``--vf-del=filter``
Sort of like ``--vf-remove``, but also accepts an index number. Index
numbers start at 0, negative numbers address the end of the list (-1 is the
last). Deprecated.
``--vf-del=<index1[,index2,...]>``
Deletes the filters at the given indexes. Index numbers start at 0,
negative numbers address the end of the list (-1 is the last).
``--vf-clr``
Completely empties the filter list.
@@ -124,61 +94,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
View File
@@ -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.
+2 -1
View File
@@ -144,6 +144,7 @@ Mac OS X
* Native OpenGL backend.
* Cocoa event loop is independent from MPlayer's event loop, so user
actions like accessing menus and live resizing do not block the playback.
* Apple Remote support.
* Media Keys support.
* VDA support using libavcodec hwaccel API instead of FFmpeg's decoder with up
to 2-2.5x reduction in CPU usage.
@@ -247,7 +248,7 @@ Command Line Switches
``-msglevel`` ``--msg-level`` (changed semantics)
``-msgmodule`` ``--msg-module``
``-name`` ``--x11-name``
``-noar`` ``(removed; replaced by MediaPlayer framework)``
``-noar`` ``--no-input-appleremote``
``-noautosub`` ``--no-sub-auto``
``-noconsolecontrols`` ``--no-input-terminal``
``-nosound`` ``--no-audio``
+3 -7
View File
@@ -20,28 +20,24 @@ Release procedure
While on master:
- Update the `RELEASE_NOTES` file, replacing the previous release notes.
- Update the `RELEASE_NOTES` file.
- Update the `VERSION` file.
- Update `DOCS/client-api-changes.rst` and `DOCS/interface-changes.rst`
(in particular, update the last version numbers if necessary)
- Create signed commit with changes.
- Commit changes.
- Create signed tag v0.X.Y.
- Add -UNKNOWN suffix to version in `VERSION` file.
- Commit changes, push release branch (`release/0.X`) and tag to GitHub.
- Commit changes, push branch and tag to GitHub.
- Create a new GitHub release using the content of `RELEASE_NOTES` related to
the new version.
If necessary (to e.g. exclude commits already on master), the release can
be done on a branch with different commit history. The release branch **must**
then be merged to master so `git describe` will pick up the tag.
Release notes template
----------------------
+12 -427
View File
@@ -15,7 +15,7 @@ player/*.c:
* parse command line, add files from the command line to playlist
(m_config_parse_mp_command_line())
* check help options etc. (call handle_help_options()), possibly exit
* call mp_play_files() function that works down the playlist:
* call play_files() function that works down the playlist:
* run idle loop (idle_loop()), until there are files in the
playlist or an exit command was given (only if --idle it set)
* actually load and play a file in play_current_file():
@@ -30,7 +30,7 @@ player/*.c:
* determine next entry on the playlist to play
* loop, or exit if no next file or quit is requested
(see enum stop_play_reason)
* call mp_destroy()
* call exit_player_with_rc()
* run_playloop():
* calls fill_audio_out_buffers()
This checks whether new audio needs to be decoded, and pushes it
@@ -44,9 +44,9 @@ player/*.c:
Things worth saying about the playback core:
- most state is in MPContext (core.h), which is not available to the
subsystems (and should not be made available)
subsystems
- the currently played tracks are in mpctx->current_tracks, and decoder
state in track.dec/d_sub
state in track.d_video/d_audio/d_sub
- the other subsystems rarely call back into the frontend, and the frontend
polls them instead (probably a good thing)
- one exceptions are wakeup callbacks, which notify a "higher" component
@@ -68,7 +68,6 @@ ta.h & ta.c:
as in it doesn't require valgrind.) You can enable it by setting the
MPV_LEAK_REPORT environment variable to "1":
export MPV_LEAK_REPORT=1
Or permanently by building with --enable-ta-leak-report.
This will list all unfree'd allocations on exit.
Documentation can be found here:
@@ -104,45 +103,13 @@ options/options.h, options/options.c
options.c. Most default values for options and MPOpts are in
mp_default_opts at the end of options.c.
MPOpts is unfortunately quite monolithic, but is being incrementally broken
up into sub-structs. Many components have their own sub-option structs
separate from MPOpts. New options should be bound to the component that uses
them. Add a new option table/struct if needed.
The global MPOpts still contains the sub-structs as fields, which serves to
link them to the option parser. For example, an entry like this may be
typical:
OPT_SUBSTRUCT("", demux_opts, demux_conf, 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.
+60 -32
View File
@@ -1,6 +1,8 @@
![mpv logo](https://raw.githubusercontent.com/mpv-player/mpv.io/master/source/images/mpv-logo-128.png)
![http://mpv.io/](https://raw.githubusercontent.com/mpv-player/mpv.io/master/source/images/mpv-logo-128.png)
# mpv
## mpv
--------------
* [External links](#external-links)
@@ -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
View File
@@ -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
+3 -1
View File
@@ -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 \
+9 -9
View File
@@ -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
View File
@@ -20,24 +20,12 @@ def is_user_lib(objfile, libname):
not "libc." in libname and \
not "libgcc." in libname and \
not os.path.basename(libname) == 'Python' and \
not os.path.basename(objfile) in libname and \
not "libswift" in libname
not os.path.basename(objfile) in libname
def otool(objfile):
command = "otool -L '%s' | grep -e '\t' | awk '{ print $1 }'" % objfile
output = subprocess.check_output(command, shell = True, universal_newlines=True)
return set(filter(partial(is_user_lib, objfile), output.split()))
def get_rpaths_dev_tools(binary):
command = "otool -l '%s' | grep -A2 LC_RPATH | grep path | grep \"Xcode\|CommandLineTools\"" % binary
result = subprocess.check_output(command, shell = True, universal_newlines=True)
pathRe = re.compile("^\s*path (.*) \(offset \d*\)$")
output = []
for line in result.splitlines():
output.append(pathRe.search(line).group(1).strip())
return output
command = "otool -L %s | grep -e '\t' | awk '{ print $1 }'" % objfile
output = subprocess.check_output(command, shell = True)
return filter(partial(is_user_lib, objfile), output.split())
def install_name_tool_change(old, new, objfile):
subprocess.call(["install_name_tool", "-change", old, new, objfile])
@@ -45,15 +33,9 @@ def install_name_tool_change(old, new, objfile):
def install_name_tool_id(name, objfile):
subprocess.call(["install_name_tool", "-id", name, objfile])
def install_name_tool_add_rpath(rpath, binary):
subprocess.call(["install_name_tool", "-add_rpath", rpath, binary])
def install_name_tool_delete_rpath(rpath, binary):
subprocess.call(["install_name_tool", "-delete_rpath", rpath, binary])
def libraries(objfile, result = dict()):
libs_list = otool(objfile)
result[objfile] = libs_list
result[objfile] = set(libs_list)
for lib in libs_list:
if lib not in result:
@@ -61,19 +43,29 @@ def libraries(objfile, result = dict()):
return result
def leafs(libs_dict, processed = []):
result = []
processed = set(processed)
for objfile, libs in libs_dict.items():
if libs <= processed:
result.append(objfile)
return result
def lib_path(binary):
return os.path.join(os.path.dirname(binary), 'lib')
def lib_name(lib):
return os.path.join("@executable_path", "lib", os.path.basename(lib))
def process_libraries(libs_dict, binary):
libs_set = set(libs_dict)
# Remove binary from libs_set to prevent a duplicate of the binary being
# added to the libs directory.
libs_set.remove(binary)
def process_libraries(libs_dict, binary, processed = []):
ls = leafs(libs_dict, processed)
diff = set(ls) - set(processed)
if diff == set([binary]):
return
for src in libs_set:
for src in diff:
name = lib_name(src)
dst = os.path.join(lib_path(binary), os.path.basename(src))
@@ -84,47 +76,19 @@ def process_libraries(libs_dict, binary):
if src in libs_dict[binary]:
install_name_tool_change(src, name, binary)
for p in libs_set:
for p in processed:
if p in libs_dict[src]:
install_name_tool_change(p, lib_name(p), dst)
def process_swift_libraries(binary):
command = ['xcrun', '--find', 'swift-stdlib-tool']
swiftStdlibTool = subprocess.check_output(command, universal_newlines=True).strip()
# from xcode11 on the dynamic swift libs reside in a separate directory from
# the std one, might need versioned paths for future swift versions
swiftLibPath = os.path.join(swiftStdlibTool, '../../lib/swift-5.0/macosx')
swiftLibPath = os.path.abspath(swiftLibPath)
command = [swiftStdlibTool, '--copy', '--platform', 'macosx', '--scan-executable', binary, '--destination', lib_path(binary)]
if os.path.exists(swiftLibPath):
command.extend(['--source-libraries', swiftLibPath])
subprocess.check_output(command, universal_newlines=True)
print(">> setting additional rpath for swift libraries")
install_name_tool_add_rpath("@executable_path/lib", binary)
def remove_dev_tools_rapths(binary):
for path in get_rpaths_dev_tools(binary):
install_name_tool_delete_rpath(path, binary)
process_libraries(libs_dict, binary, ls)
def main():
binary = os.path.abspath(sys.argv[1])
if not os.path.exists(lib_path(binary)):
os.makedirs(lib_path(binary))
print(">> gathering all linked libraries")
libs = libraries(binary)
print(">> copying and processing all linked libraries")
print(libs)
process_libraries(libs, binary)
print(">> removing rpath definitions towards dev tools")
remove_dev_tools_rapths(binary)
print(">> copying and processing swift libraries")
process_swift_libraries(binary)
if __name__ == "__main__":
main()
+14 -66
View File
@@ -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
-105
View File
@@ -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)
-37
View File
@@ -1,37 +0,0 @@
-- Test a float property which internally uses NaN.
-- Run with --no-config (or just scale-param1 not set).
local utils = require 'mp.utils'
prop_name = "scale-param1"
-- internal NaN, return string "default" instead of NaN
v = mp.get_property_native(prop_name, "fail")
print("Exp:", "string", "\"default\"")
print("Got:", type(v), utils.to_string(v))
v = mp.get_property(prop_name)
print("Exp:", "default")
print("Got:", v)
-- not representable -> return provided fallback value
v = mp.get_property_number(prop_name, -100)
print("Exp:", -100)
print("Got:", v)
mp.set_property_native(prop_name, 123)
v = mp.get_property_number(prop_name, -100)
print("Exp:", "number", 123)
print("Got:", type(v), utils.to_string(v))
-- try to set an actual NaN
st, msg = mp.set_property_number(prop_name, 0.0/0)
print("Exp:", nil, "<message>")
print("Got:", st, msg)
-- set default
mp.set_property(prop_name, "default")
v = mp.get_property(prop_name)
print("Exp:", "default")
print("Got:", v)
-266
View File
@@ -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
View File
@@ -213,7 +213,6 @@ elements_matroska = (
'Tag*, 7373, sub', (
'Targets, 63c0, sub', (
'TargetTypeValue, 68ca, uint',
'TargetType, 63ca, str',
'TargetTrackUID, 63c5, uint',
'TargetEditionUID, 63c9, uint',
'TargetChapterUID, 63c4, uint',
@@ -222,8 +221,7 @@ elements_matroska = (
'SimpleTag*, 67c8, sub', (
'TagName, 45a3, str',
'TagLanguage, 447a, str',
'TagString, 4487, str',
'TagDefault, 4484, uint',
'TagString, 4487, str'
),
),
),
+1 -1
View File
@@ -22,7 +22,7 @@ OS2Version: 3
OS2_WeightWidthSlopeOnly: 0
OS2_UseTypoMetrics: 0
CreationTime: 1408646554
ModificationTime: 1576096543
ModificationTime: 1492879730
PfmFamily: 81
TTFWeight: 400
TTFWidth: 5
-15
View File
@@ -1,15 +0,0 @@
StartChar: uniE112
Encoding: 57618 57618 30
Width: 768
VWidth: 1176
Flags: HW
LayerCount: 2
Fore
SplineSet
512 40 m 1
0 40 l 1
0 168 l 1
512 168 l 1
512 40 l 1
EndSplineSet
EndChar
-20
View File
@@ -1,20 +0,0 @@
StartChar: uniE113
Encoding: 57619 57619 31
Width: 622
VWidth: 1178
Flags: HW
LayerCount: 2
Fore
SplineSet
768 42 m 5
0 42 l 5
0 746 l 5
768 746 l 5
768 42 l 5
704 106 m 5
704 618 l 5
64 618 l 5
64 106 l 5
704 106 l 5
EndSplineSet
EndChar
-31
View File
@@ -1,31 +0,0 @@
StartChar: uniE114
Encoding: 57620 57620 32
Width: 896
VWidth: 1178
Flags: HW
LayerCount: 2
Fore
SplineSet
768 298 m 1
576 298 l 1
576 42 l 1
0 42 l 1
0 490 l 1
192 490 l 1
192 746 l 1
768 746 l 1
768 298 l 1
704 362 m 1
704 618 l 1
256 618 l 1
256 490 l 1
576 490 l 1
576 362 l 1
704 362 l 1
512 106 m 1
512 362 l 1
64 362 l 1
64 106 l 1
512 106 l 1
EndSplineSet
EndChar
-27
View File
@@ -1,27 +0,0 @@
StartChar: uniE115
Encoding: 57621 57621 33
Width: 844
VWidth: 1112
Flags: HW
LayerCount: 2
Fore
SplineSet
671 40 m 1
575 40 l 1
335 277 l 1
98 40 l 1
-1 40 l 1
-1 136 l 1
236 376 l 1
-1 613 l 1
-1 712 l 1
98 712 l 1
335 475 l 1
575 712 l 1
671 712 l 1
671 613 l 1
434 376 l 1
671 139 l 1
671 40 l 1
EndSplineSet
EndChar
+2 -2
View File
@@ -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 -1
View File
@@ -1,6 +1,6 @@
#!/bin/sh
if [ "x$TARGET" != "xx86_64-w64-mingw32" ]; then
if [ "x$LIBAV" != "xffmpeg-git" ] || [ "x$TRAVIS_OS_NAME" != "xlinux" ]; then
# trigger build only on one of the matrix nodes
exit;
fi
Executable
+283
View File
@@ -0,0 +1,283 @@
#!/usr/bin/perl
# Generate ZSH completion
#
# This file is part of mpv.
#
# mpv is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# mpv is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with mpv. If not, see <http://www.gnu.org/licenses/>.
#
use strict;
use warnings;
use warnings FATAL => 'uninitialized';
my $mpv = $ARGV[0] || 'mpv';
my @opts = parse_main_opts('--list-options', '^ (\-\-[^\s\*]*)\*?\s*(.*)');
die "Couldn't find any options" unless (@opts);
my @ao = parse_opts('--ao=help', '^ ([^\s\:]*)\s*: (.*)');
my @vo = parse_opts('--vo=help', '^ ([^\s\:]*)\s*: (.*)');
my @af = parse_opts('--af=help', '^ ([^\s\:]*)\s*: (.*)');
my @vf = parse_opts('--vf=help', '^ ([^\s\:]*)\s*: (.*)');
my @protos = parse_opts('--list-protocols', '^ ([^\s]*)');
my ($opts_str, $ao_str, $vo_str, $af_str, $vf_str, $protos_str);
$opts_str .= qq{ '$_' \\\n} foreach (@opts);
chomp $opts_str;
$ao_str .= qq{ '$_' \\\n} foreach (@ao);
chomp $ao_str;
$vo_str .= qq{ '$_' \\\n} foreach (@vo);
chomp $vo_str;
$af_str .= qq{ '$_' \\\n} foreach (@af);
chomp $af_str;
$vf_str .= qq{ '$_' \\\n} foreach (@vf);
chomp $vf_str;
$protos_str = join(' ', @protos);
my $runtime_completions = <<'EOS';
profile|show-profile)
local -a profiles
local current
for current in "${(@f)$($words[1] --profile=help)}"; do
current=${current//\*/\\\*}
current=${current//\:/\\\:}
current=${current//\[/\\\[}
current=${current//\]/\\\]}
if [[ $current =~ $'\t'([^$'\t']*)$'\t'(.*) ]]; then
if [[ -n $match[2] ]]; then
current="$match[1][$match[2]]"
else
current="$match[1]"
fi
profiles=($profiles $current)
fi
done
if [[ $state == show-profile ]]; then
# For --show-profile, only one allowed
if (( ${#profiles} > 0 )); then
_values 'profile' $profiles && rc=0
fi
else
# For --profile, multiple allowed
profiles=($profiles 'help[list profiles]')
_values -s , 'profile(s)' $profiles && rc=0
fi
;;
audio-device)
local -a audio_devices
local current
for current in "${(@f)$($words[1] --audio-device=help)}"; do
current=${current//\*/\\\*}
current=${current//\:/\\\:}
current=${current//\[/\\\[}
current=${current//\]/\\\]}
if [[ $current =~ ' '\'([^\']*)\'' \('(.*)'\)' ]]; then
audio_devices=($audio_devices "$match[1][$match[2]]")
fi
done
audio_devices=($audio_devices 'help[list audio devices]')
_values 'audio device' $audio_devices && rc=0
;;
EOS
chomp $runtime_completions;
my $tmpl = <<"EOS";
#compdef mpv
# For customization, see:
# https://github.com/mpv-player/mpv/wiki/Zsh-completion-customization
local curcontext="\$curcontext" state state_descr line
typeset -A opt_args
local -a match mbegin mend
local MATCH MBEGIN MEND
# By default, don't complete URLs unless no files match
local -a tag_order
zstyle -a ":completion:*:*:\$service:*" tag-order tag_order || \
zstyle ":completion:*:*:\$service:*" tag-order '!urls'
local rc=1
_arguments -C -S \\
$opts_str
'*:files:->mfiles' && rc=0
case \$state in
ao)
_values -s , 'audio outputs' \\
$ao_str
&& rc=0
;;
vo)
_values -s , 'video outputs' \\
$vo_str
&& rc=0
;;
af)
_values -s , 'audio filters' \\
$af_str
&& rc=0
;;
vf)
_values -s , 'video filters' \\
$vf_str
&& rc=0
;;
$runtime_completions
files)
compset -P '*,'
compset -S ',*'
_files -r ',/ \\t\\n\\-' && rc=0
;;
mfiles)
local expl
_tags files urls
while _tags; do
_requested files expl 'media file' _files && rc=0
if _requested urls; then
while _next_label urls expl URL; do
_urls "\$expl[@]" && rc=0
compadd -S '' "\$expl[@]" $protos_str && rc=0
done
fi
(( rc )) || return 0
done
;;
esac
return rc
EOS
print $tmpl;
sub parse_main_opts {
my ($cmd, $regex) = @_;
my @list;
my @lines = call_mpv($cmd);
foreach my $line (@lines) {
my ($name, $desc) = ($line =~ /^$regex/) or next;
next if ($desc eq 'removed' || $desc eq 'alias');
if ($desc =~ /^Flag/) {
push @list, $name;
$name =~ /^--(.*)/;
if ($1 !~ /^(\{|\}|v|list-options|really-quiet|no-.*)$/) {
push @list, "--no-$1";
}
} elsif ($desc =~ /^Print/) {
push @list, $name;
} else {
# Option takes argument
my $entry = $name;
$desc =~ s/\:/\\:/g;
$entry .= "=-:$desc:";
if ($desc =~ /^Choices\\: ([^(]*)/) {
my $choices = $1;
$choices =~ s/ +$//; # strip trailing space
$entry .= "($choices)";
# If "no" is one of the choices, it can also be
# negated like a flag (--no-whatever).
if ($choices =~ /\bno\b/) {
$name =~ s/^--/--no-/;
push @list, $name;
}
} elsif ($line =~ /\[file\]/) {
$entry .= '->files';
} elsif ($name =~ /^--(ao|vo|af|vf|profile|show-profile|audio-device)$/) {
$entry .= "->$1";
}
push @list, $entry;
}
}
# Sort longest first, because zsh won't complete an option listed
# after one that's a prefix of it.
@list = sort {
$a =~ /([^=]*)/; my $ma = $1;
$b =~ /([^=]*)/; my $mb = $1;
length($mb) <=> length($ma)
} @list;
return @list;
}
sub parse_opts {
my ($cmd, $regex) = @_;
my @list;
my @lines = call_mpv($cmd);
foreach my $line (@lines) {
if ($line !~ /^$regex/) {
next;
}
my $entry = $1;
if (defined $2) {
my $desc = $2;
$desc =~ s/\:/\\:/g;
$entry .= "[$desc]";
}
push @list, $entry
}
return @list;
}
sub call_mpv {
my ($cmd) = @_;
my $output = `"$mpv" --no-config $cmd`;
if ($? == -1) {
die "Could not run mpv: $!";
} elsif ((my $exit_code = $? >> 8) != 0) {
die "mpv returned $exit_code with output:\n$output";
}
return split /\n/, $output;
}
+1 -1
View File
@@ -1 +1 @@
0.31.0
0.28.2
+3 -10
View File
@@ -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:
+153 -258
View File
@@ -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;
}
+39
View File
@@ -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
View File
@@ -32,11 +32,6 @@ struct mp_aframe {
// We support spdif formats, which are allocated as AV_SAMPLE_FMT_S16.
int format;
double pts;
double speed;
};
struct avframe_opaque {
double speed;
};
static void free_frame(void *ptr)
@@ -48,11 +43,11 @@ static void free_frame(void *ptr)
struct mp_aframe *mp_aframe_create(void)
{
struct mp_aframe *frame = talloc_zero(NULL, struct mp_aframe);
frame->pts = MP_NOPTS_VALUE;
frame->av_frame = av_frame_alloc();
if (!frame->av_frame)
abort();
talloc_set_destructor(frame, free_frame);
mp_aframe_reset(frame);
return frame;
}
@@ -66,7 +61,6 @@ struct mp_aframe *mp_aframe_new_ref(struct mp_aframe *frame)
dst->chmap = frame->chmap;
dst->format = frame->format;
dst->pts = frame->pts;
dst->speed = frame->speed;
if (mp_aframe_is_allocated(frame)) {
if (av_frame_ref(dst->av_frame, frame->av_frame) < 0)
@@ -86,7 +80,6 @@ void mp_aframe_reset(struct mp_aframe *frame)
frame->chmap.num = 0;
frame->format = 0;
frame->pts = MP_NOPTS_VALUE;
frame->speed = 1.0;
}
// Remove all actual audio data and leave only the metadata.
@@ -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))
-17
View File
@@ -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
View File
@@ -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
View File
@@ -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
-3
View File
@@ -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,
+49
View File
@@ -0,0 +1,49 @@
/*
* This file is part of mpv.
*
* mpv is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* mpv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MPLAYER_AD_H
#define MPLAYER_AD_H
#include "common/codecs.h"
#include "demux/stheader.h"
#include "demux/demux.h"
#include "audio/format.h"
#include "audio/aframe.h"
#include "dec_audio.h"
struct mp_decoder_list;
/* interface of audio decoder drivers */
struct ad_functions {
const char *name;
void (*add_decoders)(struct mp_decoder_list *list);
int (*init)(struct dec_audio *da, const char *decoder);
void (*uninit)(struct dec_audio *da);
int (*control)(struct dec_audio *da, int cmd, void *arg);
// Return whether or not the packet has been consumed.
bool (*send_packet)(struct dec_audio *da, struct demux_packet *pkt);
// Return whether decoding is still going on (false if EOF was reached).
// Never returns false & *out set, but can return true with !*out.
bool (*receive_frame)(struct dec_audio *da, struct mp_aframe **out);
};
enum ad_ctrl {
ADCTRL_RESET = 1, // flush and reset state, e.g. after seeking
};
#endif /* MPLAYER_AD_H */
+62 -94
View File
@@ -27,32 +27,28 @@
#include <libavutil/intreadwrite.h>
#include "mpv_talloc.h"
#include "audio/aframe.h"
#include "audio/fmt-conversion.h"
#include "config.h"
#include "common/av_common.h"
#include "common/codecs.h"
#include "common/global.h"
#include "common/msg.h"
#include "demux/packet.h"
#include "demux/stheader.h"
#include "filters/f_decoder_wrapper.h"
#include "filters/filter_internal.h"
#include "options/m_config.h"
#include "options/options.h"
#include "ad.h"
#include "audio/fmt-conversion.h"
struct priv {
AVCodecContext *avctx;
AVFrame *avframe;
struct mp_chmap force_channel_map;
bool force_channel_map;
uint32_t skip_samples, trim_samples;
bool preroll_done;
double next_pts;
AVRational codec_timebase;
struct lavc_state state;
struct mp_decoder public;
};
static void uninit(struct dec_audio *da);
#define OPT_BASE_STRUCT struct ad_lavc_params
struct ad_lavc_params {
float ac3drc;
@@ -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
View File
@@ -24,16 +24,11 @@
#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>
#include "audio/aframe.h"
#include "audio/format.h"
#include "common/av_common.h"
#include "common/codecs.h"
#include "config.h"
#include "common/msg.h"
#include "demux/packet.h"
#include "demux/stheader.h"
#include "filters/f_decoder_wrapper.h"
#include "filters/filter_internal.h"
#include "common/av_common.h"
#include "options/options.h"
#include "ad.h"
#define OUTBUF_SIZE 65536
@@ -48,8 +43,8 @@ struct spdifContext {
struct mp_aframe *fmt;
int sstride;
struct mp_aframe_pool *pool;
struct mp_decoder public;
bool got_eof;
struct demux_packet *queued_packet;
};
static int write_packet(void *p, uint8_t *buf, int buf_size)
@@ -67,8 +62,7 @@ static int write_packet(void *p, uint8_t *buf, int buf_size)
return buf_size;
}
// (called on both filter destruction _and_ if lavf fails to init)
static void destroy(struct mp_filter *da)
static void uninit(struct dec_audio *da)
{
struct spdifContext *spdif_ctx = da->priv;
AVFormatContext *lavf_ctx = spdif_ctx->lavf_ctx;
@@ -80,11 +74,27 @@ static void destroy(struct mp_filter *da)
av_freep(&lavf_ctx->pb->buffer);
av_freep(&lavf_ctx->pb);
avformat_free_context(lavf_ctx);
talloc_free(spdif_ctx->queued_packet);
spdif_ctx->lavf_ctx = NULL;
}
}
static void determine_codec_params(struct mp_filter *da, AVPacket *pkt,
static int init(struct dec_audio *da, const char *decoder)
{
struct spdifContext *spdif_ctx = talloc_zero(NULL, struct spdifContext);
da->priv = spdif_ctx;
spdif_ctx->log = da->log;
spdif_ctx->use_dts_hd = da->opts->dtshd;
spdif_ctx->pool = mp_aframe_pool_create(spdif_ctx);
if (strcmp(decoder, "spdif_dts_hd") == 0)
spdif_ctx->use_dts_hd = true;
spdif_ctx->codec_id = mp_codec_to_av_codec_id(da->codec->codec);
return spdif_ctx->codec_id != AV_CODEC_ID_NONE;
}
static void determine_codec_params(struct dec_audio *da, AVPacket *pkt,
int *out_profile, int *out_rate)
{
struct spdifContext *spdif_ctx = da->priv;
@@ -147,7 +157,7 @@ done:
MP_WARN(da, "Failed to parse codec profile.\n");
}
static int init_filter(struct mp_filter *da, AVPacket *pkt)
static int init_filter(struct dec_audio *da, AVPacket *pkt)
{
struct spdifContext *spdif_ctx = da->priv;
@@ -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,
};
+290
View File
@@ -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;
}
+66
View File
@@ -0,0 +1,66 @@
/*
* This file is part of mpv.
*
* mpv is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* mpv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MPLAYER_DEC_AUDIO_H
#define MPLAYER_DEC_AUDIO_H
#include "audio/chmap.h"
#include "audio/aframe.h"
#include "demux/demux.h"
#include "demux/stheader.h"
struct mp_decoder_list;
struct dec_audio {
struct mp_log *log;
struct MPOpts *opts;
struct mpv_global *global;
const struct ad_functions *ad_driver;
struct sh_stream *header;
struct mp_codec_params *codec;
char *decoder_desc;
bool try_spdif;
struct mp_recorder_sink *recorder_sink;
// For free use by the ad_driver
void *priv;
// Strictly internal (dec_audio.c).
double pts; // endpts of previous frame
double start, end;
struct demux_packet *packet;
struct demux_packet *new_segment;
struct mp_aframe *current_frame;
int current_state;
};
struct mp_decoder_list *audio_decoder_list(void);
int audio_init_best_codec(struct dec_audio *d_audio);
void audio_uninit(struct dec_audio *d_audio);
void audio_work(struct dec_audio *d_audio);
int audio_get_frame(struct dec_audio *d_audio, struct mp_aframe **out_frame);
void audio_reset_decoding(struct dec_audio *d_audio);
// ad_spdif.c
struct mp_decoder_list *select_spdif_codec(const char *codec, const char *pref);
#endif /* MPLAYER_DEC_AUDIO_H */
+821
View File
@@ -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);
}
+161
View File
@@ -0,0 +1,161 @@
/*
* This file is part of mpv.
*
* mpv is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* mpv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MPLAYER_AF_H
#define MPLAYER_AF_H
#include <stdio.h>
#include <stdbool.h>
#include <sys/types.h>
#include "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
View File
@@ -15,14 +15,18 @@
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#include "audio/aframe.h"
#include "audio/format.h"
#include "filters/f_autoconvert.h"
#include "filters/filter_internal.h"
#include "filters/user_filters.h"
#include <stdlib.h>
#include <libavutil/common.h>
#include "options/m_option.h"
struct f_opts {
#include "audio/format.h"
#include "af.h"
struct priv {
struct m_config *config;
int in_format;
int in_srate;
struct m_channels in_channels;
@@ -33,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
View File
@@ -31,17 +31,14 @@
#include <libavutil/bswap.h>
#include <libavutil/mem.h>
#include "audio/aframe.h"
#include "audio/chmap_sel.h"
#include "audio/fmt-conversion.h"
#include "audio/format.h"
#include "config.h"
#include "common/av_common.h"
#include "common/common.h"
#include "filters/f_autoconvert.h"
#include "filters/f_utils.h"
#include "filters/filter_internal.h"
#include "filters/user_filters.h"
#include "options/m_option.h"
#include "af.h"
#include "audio/audio_buffer.h"
#include "audio/chmap_sel.h"
#include "audio/fmt-conversion.h"
#define AC3_MAX_CHANNELS 6
@@ -52,89 +49,173 @@ const uint16_t ac3_bitrate_tab[19] = {
160, 192, 224, 256, 320, 384, 448, 512, 576, 640
};
struct f_opts {
int add_iec61937_header;
int bit_rate;
int min_channel_num;
char *encoder;
char **avopts;
};
struct priv {
struct f_opts *opts;
struct mp_pin *in_pin;
struct mp_aframe *cur_format;
struct mp_aframe *in_frame;
struct mp_aframe_pool *out_pool;
// Data for specific instances of this filter
typedef struct af_ac3enc_s {
struct AVCodec *lavc_acodec;
struct AVCodecContext *lavc_actx;
int bit_rate;
struct mp_audio *input; // frame passed to libavcodec
struct mp_audio *pending; // unconsumed input data
int in_samples; // samples of input per AC3 frame
int out_samples; // upper bound on encoded output per AC3 frame
};
int64_t encoder_buffered;
static bool reinit(struct mp_filter *f)
int cfg_add_iec61937_header;
int cfg_bit_rate;
int cfg_min_channel_num;
char *cfg_encoder;
char **cfg_avopts;
} af_ac3enc_t;
// fmt carries the input format. Change it to the best next-possible format
// the encoder likely accepts.
static void select_encode_format(AVCodecContext *c, struct mp_audio *fmt)
{
struct priv *s = f->priv;
int formats[AF_FORMAT_COUNT];
af_get_best_sample_formats(fmt->format, formats);
mp_aframe_reset(s->cur_format);
for (int n = 0; formats[n]; n++) {
const enum AVSampleFormat *lf = c->codec->sample_fmts;
for (int i = 0; lf && lf[i] != AV_SAMPLE_FMT_NONE; i++) {
int mpfmt = af_from_avformat(lf[i]);
if (mpfmt && mpfmt == formats[n]) {
mp_audio_set_format(fmt, mpfmt);
goto done_fmt;
}
}
}
done_fmt: ;
int rate =
af_select_best_samplerate(fmt->rate, c->codec->supported_samplerates);
if (rate > 0)
fmt->rate = rate;
struct mp_chmap_sel sel = {0};
const uint64_t *lch = c->codec->channel_layouts;
for (int n = 0; lch && lch[n]; n++) {
struct mp_chmap chmap = {0};
mp_chmap_from_lavc(&chmap, lch[n]);
mp_chmap_sel_add_map(&sel, &chmap);
}
struct mp_chmap res = fmt->channels;
mp_chmap_sel_adjust(&sel, &res);
if (!mp_chmap_is_empty(&res))
mp_audio_set_channels(fmt, &res);
}
// Initialization and runtime control
static int control(struct af_instance *af, int cmd, void *arg)
{
af_ac3enc_t *s = af->priv;
static const int default_bit_rate[AC3_MAX_CHANNELS+1] = \
{0, 96000, 192000, 256000, 384000, 448000, 448000};
if (s->opts->add_iec61937_header) {
s->out_samples = AC3_FRAME_SIZE;
} else {
s->out_samples = AC3_MAX_CODED_FRAME_SIZE /
mp_aframe_get_sstride(s->in_frame);
switch (cmd){
case AF_CONTROL_REINIT: {
struct mp_audio *in = arg;
struct mp_audio orig_in = *in;
if (!af_fmt_is_pcm(in->format) || in->nch < s->cfg_min_channel_num)
return AF_DETACH;
// At least currently, the AC3 encoder doesn't export sample rates.
in->rate = 48000;
select_encode_format(s->lavc_actx, in);
af->data->rate = in->rate;
mp_audio_set_format(af->data, AF_FORMAT_S_AC3);
mp_audio_set_num_channels(af->data, 2);
if (!mp_audio_config_equals(in, &orig_in))
return AF_FALSE;
if (s->cfg_add_iec61937_header) {
s->out_samples = AC3_FRAME_SIZE;
} else {
s->out_samples = AC3_MAX_CODED_FRAME_SIZE / af->data->sstride;
}
mp_audio_copy_config(s->input, in);
talloc_free(s->pending);
s->pending = NULL;
MP_DBG(af, "reinit: %d, %d, %d.\n", in->nch, in->rate, s->in_samples);
int bit_rate = s->bit_rate ? s->bit_rate : default_bit_rate[in->nch];
if (s->lavc_actx->channels != in->nch ||
s->lavc_actx->sample_rate != in->rate ||
s->lavc_actx->bit_rate != bit_rate)
{
avcodec_close(s->lavc_actx);
// Put sample parameters
s->lavc_actx->sample_fmt = af_to_avformat(in->format);
s->lavc_actx->channels = in->nch;
s->lavc_actx->channel_layout = mp_chmap_to_lavc(&in->channels);
s->lavc_actx->sample_rate = in->rate;
s->lavc_actx->bit_rate = bit_rate;
if (avcodec_open2(s->lavc_actx, s->lavc_acodec, NULL) < 0) {
MP_ERR(af, "Couldn't open codec %s, br=%d.\n", "ac3", bit_rate);
return AF_ERROR;
}
if (s->lavc_actx->frame_size < 1) {
MP_ERR(af, "encoder didn't specify input frame size\n");
return AF_ERROR;
}
}
s->in_samples = s->lavc_actx->frame_size;
mp_audio_realloc(s->input, s->in_samples);
s->input->samples = 0;
s->encoder_buffered = 0;
return AF_OK;
}
int format = mp_aframe_get_format(s->in_frame);
int rate = mp_aframe_get_rate(s->in_frame);
struct mp_chmap chmap = {0};
mp_aframe_get_chmap(s->in_frame, &chmap);
int bit_rate = s->bit_rate;
if (!bit_rate && chmap.num < AC3_MAX_CHANNELS + 1)
bit_rate = default_bit_rate[chmap.num];
avcodec_close(s->lavc_actx);
// Put sample parameters
s->lavc_actx->sample_fmt = af_to_avformat(format);
s->lavc_actx->channels = chmap.num;
s->lavc_actx->channel_layout = mp_chmap_to_lavc(&chmap);
s->lavc_actx->sample_rate = rate;
s->lavc_actx->bit_rate = bit_rate;
if (avcodec_open2(s->lavc_actx, s->lavc_acodec, NULL) < 0) {
MP_ERR(f, "Couldn't open codec %s, br=%d.\n", "ac3", bit_rate);
return false;
case AF_CONTROL_RESET:
if (avcodec_is_open(s->lavc_actx))
avcodec_flush_buffers(s->lavc_actx);
talloc_free(s->pending);
s->pending = NULL;
s->input->samples = 0;
s->encoder_buffered = 0;
return AF_OK;
}
if (s->lavc_actx->frame_size < 1) {
MP_ERR(f, "encoder didn't specify input frame size\n");
return false;
}
mp_aframe_config_copy(s->cur_format, s->in_frame);
return true;
return AF_UNKNOWN;
}
static void reset(struct mp_filter *f)
// Deallocate memory
static void uninit(struct af_instance* af)
{
struct priv *s = f->priv;
af_ac3enc_t *s = af->priv;
TA_FREEP(&s->in_frame);
if (s) {
avcodec_free_context(&s->lavc_actx);
talloc_free(s->pending);
}
}
static void destroy(struct mp_filter *f)
static void update_delay(struct af_instance *af)
{
struct priv *s = f->priv;
af_ac3enc_t *s = af->priv;
af->delay = ((s->pending ? s->pending->samples : 0) + s->input->samples +
s->encoder_buffered) / (double)s->input->rate;
}
reset(f);
avcodec_free_context(&s->lavc_actx);
static int filter_frame(struct af_instance *af, struct mp_audio *audio)
{
af_ac3enc_t *s = af->priv;
// filter_output must have been called until no output was produced.
if (s->pending && s->pending->samples)
MP_ERR(af, "broken data flow\n");
talloc_free(s->pending);
s->pending = audio;
update_delay(af);
return 0;
}
static void swap_16(uint16_t *ptr, size_t size)
@@ -143,84 +224,105 @@ static void swap_16(uint16_t *ptr, size_t size)
ptr[n] = av_bswap16(ptr[n]);
}
static void process(struct mp_filter *f)
// Copy data from input frame to encode frame (because libavcodec wants a full
// AC3 frame for encoding, while filter input frames can be smaller or larger).
// Return true if the frame is complete.
static bool fill_buffer(struct af_instance *af)
{
struct priv *s = f->priv;
af_ac3enc_t *s = af->priv;
if (!mp_pin_in_needs_data(f->ppins[1]))
return;
af->delay = 0;
if (s->pending) {
if (!mp_audio_is_writeable(s->input))
assert(s->input->samples == 0); // we can't have sent a partial frame
mp_audio_realloc_min(s->input, s->in_samples);
int copy = MPMIN(s->in_samples - s->input->samples, s->pending->samples);
s->input->samples += copy;
mp_audio_copy(s->input, s->input->samples - copy, s->pending, 0, copy);
mp_audio_skip_samples(s->pending, copy);
}
update_delay(af);
return s->input->samples >= s->in_samples;
}
// Return <0 on error, 0 on need more input, 1 on success (and *frame set).
// To actually advance the read pointer, set s->input->samples=0 afterwards.
static int read_input_frame(struct af_instance *af, AVFrame *frame)
{
af_ac3enc_t *s = af->priv;
if (!fill_buffer(af))
return 0; // need more input
if (mp_audio_to_avframe(s->input, frame) < 0)
return -1;
return 1;
}
static int filter_out(struct af_instance *af)
{
af_ac3enc_t *s = af->priv;
if (!s->pending)
return 0;
AVFrame *frame = av_frame_alloc();
if (!frame) {
MP_FATAL(af, "Could not allocate memory \n");
return -1;
}
int err = -1;
bool err = true;
struct mp_aframe *out = NULL;
AVPacket pkt = {0};
av_init_packet(&pkt);
// Send input as long as it wants.
while (1) {
if (avcodec_is_open(s->lavc_actx)) {
int lavc_ret = avcodec_receive_packet(s->lavc_actx, &pkt);
if (lavc_ret >= 0)
break;
if (lavc_ret < 0 && lavc_ret != AVERROR(EAGAIN)) {
MP_FATAL(f, "Encode failed (receive).\n");
goto done;
}
}
AVFrame *frame = NULL;
struct mp_frame input = mp_pin_out_read(s->in_pin);
// The following code assumes no sample data buffering in the encoder.
if (input.type == MP_FRAME_EOF) {
mp_pin_in_write(f->ppins[1], input);
return;
} else if (input.type == MP_FRAME_AUDIO) {
TA_FREEP(&s->in_frame);
s->in_frame = input.data;
frame = mp_frame_to_av(input, NULL);
if (!frame)
goto done;
if (mp_aframe_get_channels(s->in_frame) < s->opts->min_channel_num) {
// Just pass it through.
s->in_frame = NULL;
mp_pin_in_write(f->ppins[1], input);
return;
}
if (!mp_aframe_config_equals(s->in_frame, s->cur_format)) {
if (!reinit(f))
goto done;
}
} else if (input.type) {
err = read_input_frame(af, frame);
if (err < 0)
goto done;
} else {
return; // no data yet
}
if (err == 0)
break;
err = -1;
int lavc_ret = avcodec_send_frame(s->lavc_actx, frame);
av_frame_free(&frame);
if (lavc_ret < 0 && lavc_ret != AVERROR(EAGAIN)) {
MP_FATAL(f, "Encode failed (send).\n");
// On EAGAIN, we're supposed to read remaining output.
if (lavc_ret == AVERROR(EAGAIN))
break;
if (lavc_ret < 0) {
MP_FATAL(af, "Encode failed.\n");
goto done;
}
s->encoder_buffered += s->input->samples;
s->input->samples = 0;
}
int lavc_ret = avcodec_receive_packet(s->lavc_actx, &pkt);
if (lavc_ret == AVERROR(EAGAIN)) {
// Need to buffer more input.
err = 0;
goto done;
}
if (lavc_ret < 0) {
MP_FATAL(af, "Encode failed.\n");
goto done;
}
if (!s->in_frame)
MP_DBG(af, "avcodec_encode_audio got %d, pending %d.\n",
pkt.size, s->pending->samples + s->input->samples);
s->encoder_buffered -= AC3_FRAME_SIZE;
struct mp_audio *out =
mp_audio_pool_get(af->out_pool, af->data, s->out_samples);
if (!out)
goto done;
out = mp_aframe_create();
mp_aframe_set_format(out, AF_FORMAT_S_AC3);
mp_aframe_set_chmap(out, &(struct mp_chmap)MP_CHMAP_INIT_STEREO);
mp_aframe_set_rate(out, 48000);
if (mp_aframe_pool_allocate(s->out_pool, out, s->out_samples) < 0)
goto done;
int sstride = mp_aframe_get_sstride(out);
mp_aframe_copy_attributes(out, s->in_frame);
mp_audio_copy_attributes(out, s->pending);
int frame_size = pkt.size;
int header_len = 0;
char hdr[8];
if (s->opts->add_iec61937_header && pkt.size > 5) {
if (s->cfg_add_iec61937_header && pkt.size > 5) {
int bsmod = pkt.data[5] & 0x7;
int len = frame_size;
@@ -234,69 +336,48 @@ static void process(struct mp_filter *f)
AV_WL16(hdr + 6, len << 3); // number of bits in payload
}
if (frame_size > s->out_samples * sstride)
if (frame_size > out->samples * out->sstride)
abort();
uint8_t **planes = mp_aframe_get_data_rw(out);
if (!planes)
goto done;
char *buf = planes[0];
char *buf = (char *)out->planes[0];
memcpy(buf, hdr, header_len);
memcpy(buf + header_len, pkt.data, pkt.size);
memset(buf + header_len + pkt.size, 0,
frame_size - (header_len + pkt.size));
swap_16((uint16_t *)(buf + header_len), pkt.size / 2);
mp_aframe_set_size(out, frame_size / sstride);
mp_pin_in_write(f->ppins[1], MAKE_FRAME(MP_FRAME_AUDIO, out));
out = NULL;
out->samples = frame_size / out->sstride;
af_add_output_frame(af, out);
err = 0;
done:
av_packet_unref(&pkt);
talloc_free(out);
if (err)
mp_filter_internal_mark_failed(f);
av_frame_free(&frame);
update_delay(af);
return err;
}
static const struct mp_filter_info af_lavcac3enc_filter = {
.name = "lavcac3enc",
.priv_size = sizeof(struct priv),
.process = process,
.reset = reset,
.destroy = destroy,
};
static int af_open(struct af_instance* af){
static struct mp_filter *af_lavcac3enc_create(struct mp_filter *parent,
void *options)
{
struct mp_filter *f = mp_filter_create(parent, &af_lavcac3enc_filter);
if (!f) {
talloc_free(options);
return NULL;
}
af_ac3enc_t *s = af->priv;
af->control=control;
af->uninit=uninit;
af->filter_frame = filter_frame;
af->filter_out = filter_out;
mp_filter_add_pin(f, MP_PIN_IN, "in");
mp_filter_add_pin(f, MP_PIN_OUT, "out");
struct priv *s = f->priv;
s->opts = talloc_steal(s, options);
s->cur_format = talloc_steal(s, mp_aframe_create());
s->out_pool = mp_aframe_pool_create(s);
s->lavc_acodec = avcodec_find_encoder_by_name(s->opts->encoder);
s->lavc_acodec = avcodec_find_encoder_by_name(s->cfg_encoder);
if (!s->lavc_acodec) {
MP_ERR(f, "Couldn't find encoder %s.\n", s->opts->encoder);
goto error;
MP_ERR(af, "Couldn't find encoder %s.\n", s->cfg_encoder);
return AF_ERROR;
}
s->lavc_actx = avcodec_alloc_context3(s->lavc_acodec);
if (!s->lavc_actx) {
MP_ERR(f, "Audio LAVC, couldn't allocate context!\n");
goto error;
MP_ERR(af, "Audio LAVC, couldn't allocate context!\n");
return AF_ERROR;
}
if (mp_set_avopts(f->log, s->lavc_actx, s->opts->avopts) < 0)
goto error;
if (mp_set_avopts(af->log, s->lavc_actx, s->cfg_avopts) < 0)
return AF_ERROR;
// For this one, we require the decoder to expert lists of all supported
// parameters. (Not all decoders do that, but the ones we're interested
@@ -304,85 +385,50 @@ static struct mp_filter *af_lavcac3enc_create(struct mp_filter *parent,
if (!s->lavc_acodec->sample_fmts ||
!s->lavc_acodec->channel_layouts)
{
MP_ERR(f, "Audio encoder doesn't list supported parameters.\n");
goto error;
MP_ERR(af, "Audio encoder doesn't list supported parameters.\n");
return AF_ERROR;
}
if (s->opts->bit_rate) {
s->input = talloc_zero(s, struct mp_audio);
if (s->cfg_bit_rate) {
int i;
for (i = 0; i < 19; i++) {
if (ac3_bitrate_tab[i] == s->opts->bit_rate) {
if (ac3_bitrate_tab[i] == s->cfg_bit_rate) {
s->bit_rate = ac3_bitrate_tab[i] * 1000;
break;
}
}
if (i >= 19) {
MP_WARN(f, "unable set unsupported bitrate %d, using default "
MP_WARN(af, "unable set unsupported bitrate %d, 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,
};
+413
View File
@@ -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}
},
};
+173
View File
@@ -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
View File
@@ -20,350 +20,232 @@
#include <rubberband/rubberband-c.h>
#include "audio/aframe.h"
#include "audio/format.h"
#include "common/common.h"
#include "filters/f_autoconvert.h"
#include "filters/filter_internal.h"
#include "filters/user_filters.h"
#include "options/m_option.h"
// command line options
struct f_opts {
int transients, detector, phase, window,
smoothing, formant, pitch, channels;
double scale;
};
#include "af.h"
struct priv {
struct f_opts *opts;
struct mp_pin *in_pin;
struct mp_aframe *cur_format;
struct mp_aframe_pool *out_pool;
bool sent_final;
RubberBandState rubber;
double speed;
double pitch;
struct mp_aframe *pending;
struct mp_audio *pending;
bool needs_reset;
// Estimate how much librubberband has buffered internally.
// I could not find a way to do this with the librubberband API.
double rubber_delay;
// command line options
int opt_transients, opt_detector, opt_phase, opt_window,
opt_smoothing, opt_formant, opt_pitch, opt_channels;
};
static void update_speed(struct priv *p, double new_speed)
static void update_speed(struct af_instance *af, double new_speed)
{
struct priv *p = af->priv;
p->speed = new_speed;
if (p->rubber)
rubberband_set_time_ratio(p->rubber, 1.0 / p->speed);
rubberband_set_time_ratio(p->rubber, 1.0 / p->speed);
}
static bool update_pitch(struct priv *p, double new_pitch)
static void update_pitch(struct af_instance *af, double new_pitch)
{
if (new_pitch < 0.01 || new_pitch > 100.0)
return false;
struct priv *p = af->priv;
p->pitch = new_pitch;
if (p->rubber)
rubberband_set_pitch_scale(p->rubber, p->pitch);
return true;
rubberband_set_pitch_scale(p->rubber, p->pitch);
}
static bool init_rubberband(struct mp_filter *f)
static int control(struct af_instance *af, int cmd, void *arg)
{
struct priv *p = f->priv;
struct priv *p = af->priv;
assert(!p->rubber);
assert(p->pending);
switch (cmd) {
case AF_CONTROL_REINIT: {
struct mp_audio *in = arg;
struct mp_audio orig_in = *in;
struct mp_audio *out = af->data;
int opts = p->opts->transients | p->opts->detector | p->opts->phase |
p->opts->window | p->opts->smoothing | p->opts->formant |
p->opts->pitch | p-> opts->channels |
RubberBandOptionProcessRealTime;
in->format = AF_FORMAT_FLOATP;
mp_audio_copy_config(out, in);
int rate = mp_aframe_get_rate(p->pending);
int channels = mp_aframe_get_channels(p->pending);
if (mp_aframe_get_format(p->pending) != AF_FORMAT_FLOATP)
return false;
if (p->rubber)
rubberband_delete(p->rubber);
p->rubber = rubberband_new(rate, channels, opts, 1.0, 1.0);
if (!p->rubber) {
MP_FATAL(f, "librubberband initialization failed.\n");
return false;
int opts = p->opt_transients | p->opt_detector | p->opt_phase |
p->opt_window | p->opt_smoothing | p->opt_formant |
p->opt_pitch | p-> opt_channels |
RubberBandOptionProcessRealTime;
p->rubber = rubberband_new(in->rate, in->channels.num, opts, 1.0, 1.0);
if (!p->rubber) {
MP_FATAL(af, "librubberband initialization failed.\n");
return AF_ERROR;
}
update_speed(af, p->speed);
update_pitch(af, p->pitch);
control(af, AF_CONTROL_RESET, NULL);
return mp_audio_config_equals(in, &orig_in) ? AF_OK : AF_FALSE;
}
mp_aframe_config_copy(p->cur_format, p->pending);
update_speed(p, p->speed);
update_pitch(p, p->pitch);
return true;
case AF_CONTROL_SET_PLAYBACK_SPEED: {
update_speed(af, *(double *)arg);
return AF_OK;
}
case AF_CONTROL_RESET:
if (p->rubber)
rubberband_reset(p->rubber);
talloc_free(p->pending);
p->pending = NULL;
p->rubber_delay = 0;
return AF_OK;
case AF_CONTROL_COMMAND: {
char **args = arg;
if (!strcmp(args[0], "set-pitch")) {
char *endptr;
double pitch = strtod(args[1], &endptr);
if (*endptr || pitch < 0.01 || pitch > 100.0)
return CONTROL_ERROR;
update_pitch(af, pitch);
return CONTROL_OK;
} else {
return CONTROL_ERROR;
}
}
}
return AF_UNKNOWN;
}
static void process(struct mp_filter *f)
static int filter_frame(struct af_instance *af, struct mp_audio *data)
{
struct priv *p = f->priv;
struct priv *p = af->priv;
if (!mp_pin_in_needs_data(f->ppins[1]))
return;
talloc_free(p->pending);
p->pending = data;
while (!p->rubber || !p->pending || rubberband_available(p->rubber) <= 0) {
return 0;
}
static int filter_out(struct af_instance *af)
{
struct priv *p = af->priv;
while (rubberband_available(p->rubber) <= 0) {
const float *dummy[MP_NUM_CHANNELS] = {0};
const float **in_data = dummy;
size_t in_samples = 0;
if (p->pending) {
if (!p->pending->samples)
break;
bool eof = false;
if (!p->pending || !mp_aframe_get_size(p->pending)) {
struct mp_frame frame = mp_pin_out_read(p->in_pin);
if (frame.type == MP_FRAME_AUDIO) {
TA_FREEP(&p->pending);
p->pending = frame.data;
} else if (frame.type == MP_FRAME_EOF) {
eof = true;
} else if (frame.type) {
MP_ERR(f, "unexpected frame type\n");
goto error;
} else {
return; // no new data yet
}
}
assert(p->pending || eof);
if (!p->rubber) {
if (!p->pending) {
mp_pin_in_write(f->ppins[1], MP_EOF_FRAME);
return;
}
if (!init_rubberband(f))
goto error;
}
bool format_change =
p->pending && !mp_aframe_config_equals(p->pending, p->cur_format);
if (p->pending && !format_change) {
size_t needs = rubberband_get_samples_required(p->rubber);
uint8_t **planes = mp_aframe_get_data_ro(p->pending);
int num_planes = mp_aframe_get_planes(p->pending);
for (int n = 0; n < num_planes; n++)
in_data[n] = (void *)planes[n];
in_samples = MPMIN(mp_aframe_get_size(p->pending), needs);
}
bool final = format_change || eof;
if (!p->sent_final)
rubberband_process(p->rubber, in_data, in_samples, final);
p->sent_final |= final;
p->rubber_delay += in_samples;
if (p->pending && !format_change)
mp_aframe_skip_samples(p->pending, in_samples);
if (rubberband_available(p->rubber) > 0) {
if (eof)
mp_pin_out_repeat_eof(p->in_pin); // drain more next time
} else {
if (eof) {
mp_pin_in_write(f->ppins[1], MP_EOF_FRAME);
// recover from previous EOF
if (p->needs_reset) {
rubberband_reset(p->rubber);
p->rubber_delay = 0;
TA_FREEP(&p->pending);
p->sent_final = false;
return;
} else if (format_change) {
// go on with proper reinit on the next iteration
rubberband_delete(p->rubber);
p->sent_final = false;
p->rubber = NULL;
}
}
}
p->needs_reset = false;
assert(p->pending);
size_t needs = rubberband_get_samples_required(p->rubber);
in_data = (void *)&p->pending->planes;
in_samples = MPMIN(p->pending->samples, needs);
}
if (p->needs_reset)
break; // previous EOF
p->needs_reset = !p->pending; // EOF
rubberband_process(p->rubber, in_data, in_samples, p->needs_reset);
p->rubber_delay += in_samples;
if (!p->pending)
break;
mp_audio_skip_samples(p->pending, in_samples);
}
int out_samples = rubberband_available(p->rubber);
if (out_samples > 0) {
struct mp_aframe *out = mp_aframe_new_ref(p->cur_format);
if (mp_aframe_pool_allocate(p->out_pool, out, out_samples) < 0) {
talloc_free(out);
goto error;
}
struct mp_audio *out =
mp_audio_pool_get(af->out_pool, af->data, out_samples);
if (!out)
return -1;
if (p->pending)
mp_audio_copy_config(out, p->pending);
mp_aframe_copy_attributes(out, p->pending);
float **out_data = (void *)&out->planes;
out->samples = rubberband_retrieve(p->rubber, out_data, out->samples);
p->rubber_delay -= out->samples * p->speed;
float *out_data[MP_NUM_CHANNELS] = {0};
uint8_t **planes = mp_aframe_get_data_rw(out);
assert(planes);
int num_planes = mp_aframe_get_planes(out);
for (int n = 0; n < num_planes; n++)
out_data[n] = (void *)planes[n];
out_samples = rubberband_retrieve(p->rubber, out_data, out_samples);
if (!out_samples) {
mp_filter_internal_mark_progress(f); // unexpected, just try again
talloc_free(out);
return;
}
mp_aframe_set_size(out, out_samples);
p->rubber_delay -= out_samples * p->speed;
double pts = mp_aframe_get_pts(p->pending);
if (pts != MP_NOPTS_VALUE) {
// Note: rubberband_get_latency() does not do what you'd expect.
double delay = p->rubber_delay / mp_aframe_get_effective_rate(out);
mp_aframe_set_pts(out, pts - delay);
}
mp_aframe_mul_speed(out, p->speed);
mp_pin_in_write(f->ppins[1], MAKE_FRAME(MP_FRAME_AUDIO, out));
af_add_output_frame(af, out);
}
return;
error:
mp_filter_internal_mark_failed(f);
int delay_samples = p->rubber_delay;
if (p->pending)
delay_samples += p->pending->samples;
af->delay = delay_samples / (af->data->rate * p->speed);
return 0;
}
static bool command(struct mp_filter *f, struct mp_filter_command *cmd)
static void uninit(struct af_instance *af)
{
struct priv *p = f->priv;
switch (cmd->type) {
case MP_FILTER_COMMAND_TEXT: {
char *endptr = NULL;
double pitch = p->pitch;
if (!strcmp(cmd->cmd, "set-pitch")) {
pitch = strtod(cmd->arg, &endptr);
if (*endptr)
return false;
return update_pitch(p, pitch);
} else if (!strcmp(cmd->cmd, "multiply-pitch")) {
double mult = strtod(cmd->arg, &endptr);
if (*endptr || mult <= 0)
return false;
pitch *= mult;
return update_pitch(p, pitch);
}
return false;
}
case MP_FILTER_COMMAND_SET_SPEED:
update_speed(p, cmd->speed);
return true;
}
return false;
}
static void reset(struct mp_filter *f)
{
struct priv *p = f->priv;
if (p->rubber)
rubberband_reset(p->rubber);
p->rubber_delay = 0;
p->sent_final = false;
TA_FREEP(&p->pending);
}
static void destroy(struct mp_filter *f)
{
struct priv *p = f->priv;
struct priv *p = af->priv;
if (p->rubber)
rubberband_delete(p->rubber);
talloc_free(p->pending);
}
static const struct mp_filter_info af_rubberband_filter = {
.name = "rubberband",
.priv_size = sizeof(struct priv),
.process = process,
.command = command,
.reset = reset,
.destroy = destroy,
};
static struct mp_filter *af_rubberband_create(struct mp_filter *parent,
void *options)
static int af_open(struct af_instance *af)
{
struct mp_filter *f = mp_filter_create(parent, &af_rubberband_filter);
if (!f) {
talloc_free(options);
return NULL;
}
mp_filter_add_pin(f, MP_PIN_IN, "in");
mp_filter_add_pin(f, MP_PIN_OUT, "out");
struct priv *p = f->priv;
p->opts = talloc_steal(p, options);
p->speed = 1.0;
p->pitch = p->opts->scale;
p->cur_format = talloc_steal(p, mp_aframe_create());
p->out_pool = mp_aframe_pool_create(p);
struct mp_autoconvert *conv = mp_autoconvert_create(f);
if (!conv)
abort();
mp_autoconvert_add_afmt(conv, AF_FORMAT_FLOATP);
mp_pin_connect(conv->f->pins[0], f->ppins[0]);
p->in_pin = conv->f->pins[1];
return f;
af->control = control;
af->filter_frame = filter_frame;
af->filter_out = filter_out;
af->uninit = uninit;
return AF_OK;
}
#define OPT_BASE_STRUCT struct f_opts
const struct mp_user_filter_entry af_rubberband = {
.desc = {
.description = "Pitch conversion with librubberband",
.name = "rubberband",
.priv_size = sizeof(OPT_BASE_STRUCT),
.priv_defaults = &(const OPT_BASE_STRUCT) {
.scale = 1.0,
.pitch = RubberBandOptionPitchHighConsistency,
.transients = RubberBandOptionTransientsMixed,
.formant = RubberBandOptionFormantPreserved,
.channels = RubberBandOptionChannelsTogether,
},
.options = (const struct m_option[]) {
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
View File
@@ -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,
};
+47
View File
@@ -0,0 +1,47 @@
/*
* Copyright (C) 2002 Anders Johansson ajh@atri.curtin.edu.au
*
* This file is part of mpv.
*
* mpv is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* mpv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MPLAYER_EQUALIZER_H
#define MPLAYER_EQUALIZER_H
/* Equalizer plugin header file defines struct used for setting or
getting the gain of a specific channel and frequency */
typedef struct equalizer_s
{
float gain; // Gain in dB -15 - 15
int channel; // Channel number 0 - 5
int band; // Frequency band 0 - 9
}equalizer_t;
/* The different frequency bands are:
nr. center frequency
0 31.25 Hz
1 62.50 Hz
2 125.0 Hz
3 250.0 Hz
4 500.0 Hz
5 1.000 kHz
6 2.000 kHz
7 4.000 kHz
8 8.000 kHz
9 16.00 kHz
*/
#endif /* MPLAYER_EQUALIZER_H */
+72
View File
@@ -0,0 +1,72 @@
/*
* This file is part of mpv.
*
* mpv is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* mpv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#include <math.h>
#include <string.h>
#include "common/common.h"
#include "af.h"
/* Convert from ms to sample time */
int af_from_ms(int n, float* in, int* out, int rate, float mi, float ma)
{
int i = 0;
// Sanity check
if(!in || !out)
return AF_ERROR;
for(i=0;i<n;i++)
out[i]=(int)((float)rate * MPCLAMP(in[i],mi,ma)/1000.0);
return AF_OK;
}
/*
* test if output format matches
* af: audio filter
* out: needed format, will be overwritten by available
* format if they do not match
* returns: AF_FALSE if formats do not match, AF_OK if they match
*
* compares the format, rate and nch values of af->data with out
* Note: logically, *out=*af->data always happens, because out contains the
* format only, no actual audio data or memory allocations. *out always
* contains the parameters from af->data after the function returns.
*/
int af_test_output(struct af_instance* af, struct mp_audio* out)
{
if((af->data->format != out->format) ||
(af->data->bps != out->bps) ||
(af->data->rate != out->rate) ||
!mp_chmap_equals(&af->data->channels, &out->channels)){
*out = *af->data;
return AF_FALSE;
}
return AF_OK;
}
/* Soft clipping, the sound of a dream, thanks to Jon Wattes
post to Musicdsp.org */
float af_softclip(float a)
{
if (a >= M_PI/2)
return 1.0;
else if (a <= -M_PI/2)
return -1.0;
else
return sin(a);
}
-2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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,
};
-717
View File
@@ -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",
};
+3 -3
View File
@@ -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),
};
+2
View File
@@ -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;
}
+1 -17
View File
@@ -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;
+2
View File
@@ -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
View File
@@ -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
View File
@@ -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,
-5
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+2
View File
@@ -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;
}
+4
View File
@@ -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;
}
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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:
-26
View File
@@ -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}"
-33
View File
@@ -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
-14
View File
@@ -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
-38
View File
@@ -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