Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d893bebdc | ||
|
|
011b9dc46a | ||
|
|
83299b8382 | ||
|
|
899ce94693 |
No files matched your search
@@ -12,7 +12,7 @@ Releases are listed here: https://github.com/mpv-player/mpv/releases
|
||||
### Log file
|
||||
|
||||
Make a log file made with -v or --log-file=output.txt, paste it to
|
||||
https://0x0.st/ or a similar site, and replace this text with a link to it.
|
||||
http://sprunge.us or a similar site, and replace this text with a link to it.
|
||||
|
||||
Providing a log file is strongly encouraged. It is very helpful for reproducing
|
||||
bugs or getting important technical context. Without a log it might not be
|
||||
@@ -22,5 +22,4 @@ or ignored.
|
||||
### Sample files
|
||||
|
||||
Sample files needed to reproduce this issue can be uploaded to https://0x0.st/
|
||||
or similar sites. (Only needed if the issue cannot be reproduced without it.)
|
||||
Do not use garbage like "cloud storage", especially not Google Drive.
|
||||
or similar sites. (Only needed if the issue cannot be reproduced without it.)
|
||||
+24
-17
@@ -1,39 +1,46 @@
|
||||
language: generic
|
||||
language: c
|
||||
|
||||
os:
|
||||
- linux
|
||||
|
||||
services:
|
||||
- docker
|
||||
|
||||
sudo: required
|
||||
|
||||
#- osx
|
||||
env:
|
||||
matrix:
|
||||
- CONTAINER=registry.cirno.systems/kiwi/containers/mpv-ci:stable-deps CI_SCRIPT=ci/build-tumbleweed.sh CC=gcc
|
||||
- CONTAINER=registry.cirno.systems/kiwi/containers/mpv-ci:stable-deps CI_SCRIPT=ci/build-tumbleweed.sh CC=clang
|
||||
- CONTAINER=registry.cirno.systems/kiwi/containers/mpv-ci:snapshot-deps CI_SCRIPT=ci/build-tumbleweed.sh CC=gcc
|
||||
- CONTAINER=registry.cirno.systems/kiwi/containers/mpv-ci:snapshot-deps CI_SCRIPT=ci/build-tumbleweed.sh CC=clang
|
||||
- CONTAINER=registry.cirno.systems/kiwi/containers/mpv-ci-mingw64:i686 CI_SCRIPT=ci/build-mingw64.sh TARGET=i686-w64-mingw32
|
||||
- CONTAINER=registry.cirno.systems/kiwi/containers/mpv-ci-mingw64:x86_64 CI_SCRIPT=ci/build-mingw64.sh TARGET=x86_64-w64-mingw32
|
||||
- LIBAV=libav-git
|
||||
- LIBAV=ffmpeg-stable
|
||||
- LIBAV=ffmpeg-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:
|
||||
- docker pull $CONTAINER
|
||||
matrix:
|
||||
exclude:
|
||||
- os: osx
|
||||
env: LIBAV=libav-git
|
||||
- os: osx
|
||||
env: LIBAV=ffmpeg-git
|
||||
- os: osx
|
||||
compiler: gcc
|
||||
- os: linux
|
||||
env: LIBAV=ffmpeg-stable
|
||||
- os: linux
|
||||
compiler: clang
|
||||
|
||||
before_install: TOOLS/travis-deps libass-stable $LIBAV
|
||||
script:
|
||||
- ./bootstrap.py
|
||||
- docker run --env CC --env TARGET -v $TRAVIS_BUILD_DIR:/build $CONTAINER /bin/sh -c "cd /build && $CI_SCRIPT"
|
||||
- ./waf configure
|
||||
- ./waf build
|
||||
after_failure: cat ./build/config.log
|
||||
after_script: TOOLS/travis-rebuild-website
|
||||
|
||||
|
||||
@@ -1,85 +1,351 @@
|
||||
mpv is a fork of mplayer2, which is a fork of MPlayer.
|
||||
|
||||
mpv as a whole is licensed under the GNU General Public License GPL version 2
|
||||
or later (called GPLv2+ in this document, see LICENSE.GPL for full license
|
||||
text) by default, or the GNU Lesser General Public License LGPL version 2 or
|
||||
later (LGPLv2.1+ in this document, see LICENSE.LGPL for full license text) if
|
||||
built with the --enable-lgpl configure switch.
|
||||
mpv as a whole is licensed as GPL version 2 or later (see LICENSE). Most source
|
||||
files are GPLv2+, but some files are available under a more liberal license,
|
||||
such as LGPLv2.1+, BSD, MIT, ISC, and possibly others. Look at the copyright
|
||||
header of each source file, and grep the sources for "Copyright" if you need
|
||||
to know details. C source files without Copyright notice are licensed as
|
||||
LGPLv2.1+. Also see the list of files with specific licenses below (not all
|
||||
files can have a standard license header).
|
||||
|
||||
Most source files are LGPLv2.1+ or GPLv2+, but some files are available under
|
||||
more liberal licenses, such as BSD, MIT, ISC, and possibly others. Look at the
|
||||
copyright header of each source file, and grep the sources for "Copyright" if
|
||||
you need to know details. C source files without Copyright notice are usually
|
||||
licensed as LGPLv2.1+. Also see the list of files with specific licenses below
|
||||
(not all files can have a standard license header).
|
||||
|
||||
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
|
||||
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.
|
||||
All new contributions must be LGPLv2.1+ licensed, or if the changes are done on
|
||||
GPL code, must come with the implicit agreement that the project can relicense
|
||||
the code to LGPLv2.1+ at a later point without asking the contributor. (This
|
||||
is a safeguard for making potential relicensing of the project to LGPLv2.1+
|
||||
easier.) Using a more liberal license compatible to LGPLv2.1+ is also ok.
|
||||
|
||||
For information about authors and contributors, consult the git log, which
|
||||
contains the complete SVN and CVS history as well.
|
||||
|
||||
Note that mplayer2 as a whole is licensed under GPLv3+. This is because it uses
|
||||
a copy of talloc (part of Samba), which is LGPLv3+, and the next compatible
|
||||
license for this mix is GPLv3+.
|
||||
|
||||
MPlayer as a whole is licensed under GPLv2 (incompatible to GPLv3!), because
|
||||
some files are licensed to GPLv2 (and _not_ any later version of the license).
|
||||
In particular, this affects the file libmpdemux/demux_ty_osd.c. It is disabled
|
||||
under mplayer2, and has been removed from mpv.
|
||||
|
||||
"v2.1+" in this context means "version 2.1 or later".
|
||||
|
||||
Some libraries are GPLv2+ or GPLv3+ only. Building mpv with Samba support makes
|
||||
it GPLv3+.
|
||||
|
||||
mpv can be built as LGPLv2.1+ with the --enable-lgpl configure option. To add
|
||||
a LGPL mode to mpv, MPlayer code had to be relicensed from GPLv2+ to LGPLv2.1+
|
||||
by asking the MPlayer authors for permission. Since permission could not be
|
||||
obtained from everyone, LGPL mode disables the following features, some of
|
||||
them quite central:
|
||||
- Linux X11 video output
|
||||
- BSD audio output via OSS
|
||||
- NVIDIA/Linux hardware decoding (vdpau, although nvdec usually works)
|
||||
- Linux TV input
|
||||
- minor features: jack, DVD, CDDA, SMB, CACA, legacy direct3d VO
|
||||
Some of these will be fixed in the future. The intended use for LGPL mode is
|
||||
with libmpv, and currently it's not recommended to build mpv CLI in LGPL mode
|
||||
at all.
|
||||
Source files with specific licenses:
|
||||
- etc/input.conf is LGPLv2.1+
|
||||
- etc/builtin.conf is LGPLv2.1+
|
||||
- etc/encoding-profiles.conf is LGPLv2.1+
|
||||
- all mpv icons and derived files are LGPLv2.1+ (mpv-icon-8bit-16x16.png etc.)
|
||||
- everything else under etc/ is unknown
|
||||
- everything under DOCS/man/ is GPLv2+
|
||||
- sub/osd_font.otf is LGPLv2.1+
|
||||
- version.sh is LGPLv2+
|
||||
- bootstrap.py is unknown (probably GPLv2+ or LGPLv2+)
|
||||
- the build system (wscript, waftools/*) is LGPLv2+, some parts BSD
|
||||
|
||||
The following files are still GPL only (--enable-lgpl disables them):
|
||||
Some files are marked with "Almost LGPL." These files are GPL, but all authors
|
||||
have agreed to relicense them to LGPL. the problem is that one of the authors is
|
||||
Michael Niedermayer, who cited the following conditions for relicensing:
|
||||
|
||||
http://lists.mplayerhq.hu/pipermail/mplayer-dev-eng/2016-September/073535.html
|
||||
|
||||
We assume the first 3 conditions are fulfilled. The last condition can be
|
||||
interpreted as that his code can be changed to LGPL only as soon as the "core"
|
||||
of mpv changes to LGPL. We interpret "core" as something minimal, that can
|
||||
actually be built and run, with all GPL code disabled.
|
||||
|
||||
Some files are LGPLv3+. This is due to the contributions of a single developer
|
||||
going by the SVN username "iive". The chosen license of this project is
|
||||
LGPLv2.1+. The affected files will be changed to LGPLv2.1+ at the earliest
|
||||
opportunity, for example if his contributions disappear by being replaced
|
||||
or removed. All new contributions to these files are implied to be LGPLv2.1+.
|
||||
|
||||
LGPL relicensing status. Files which require some sort of action to get a basic
|
||||
LGPL player core (relicensing, replacement, or partial/full rewrites) are marked
|
||||
with an "x".
|
||||
|
||||
audio/decode/ad.h LGPL
|
||||
audio/decode/ad_lavc.c LGPL
|
||||
audio/decode/ad_spdif.c LGPL
|
||||
audio/decode/dec_audio.* LGPL
|
||||
x audio/filter/af.* must be killed (main author disagreed)
|
||||
(required for LGPL core: a new filter chain must be written)
|
||||
audio/filter/af_channel.c must be killed (main author disagreed)
|
||||
audio/filter/af_equalizer.c must be killed (main author disagreed)
|
||||
audio/filter/af_pan.c must be killed (main author disagreed)
|
||||
audio/filter/af_volume.c must be killed (main author disagreed)
|
||||
audio/filter/equalizer.h must be killed (main author disagreed)
|
||||
audio/filter/tools.c must be killed (main author disagreed)
|
||||
audio/filter/af_format.c LGPL
|
||||
audio/filter/af_lavc3enc.c LGPL
|
||||
audio/filter/af_lavfi.c LGPL
|
||||
audio/filter/af_scaletempo.c LGPL
|
||||
audio/filter/af_rubberband.c LGPL
|
||||
audio/out/ao.c LGPL
|
||||
audio/out/ao.h LGPL
|
||||
audio/out/ao_alsa.c extremely hard (original author did not decide)
|
||||
audio/out/ao_audiounit.m LGPL
|
||||
audio/out/ao_coreaudio.c LGPL
|
||||
audio/out/ao_coreaudio_chmap.c LGPL
|
||||
audio/out/ao_coreaudio_chmap.h LGPL
|
||||
audio/out/ao_coreaudio_exclusiv LGPL
|
||||
audio/out/ao_coreaudio_properti LGPL
|
||||
audio/out/ao_coreaudio_properti LGPL
|
||||
audio/out/ao_coreaudio_utils.c LGPL
|
||||
audio/out/ao_coreaudio_utils.h LGPL
|
||||
audio/out/ao_jack.c will stay GPL
|
||||
audio/out/ao_lavc.c LGPL
|
||||
audio/out/ao_null.c LGPL
|
||||
audio/out/ao_openal.c LGPL
|
||||
audio/out/ao_opensles.c LGPL
|
||||
audio/out/ao_oss.c will stay GPL
|
||||
audio/out/ao_pcm.c LGPL
|
||||
audio/out/ao_pulse.c LGPL
|
||||
audio/out/ao_rsound.c LGPL
|
||||
audio/out/ao_sdl.c LGPL
|
||||
audio/out/ao_sndio.c LGPL (BSD)
|
||||
audio/out/ao_wasapi.c LGPL
|
||||
audio/out/ao_wasapi_changenotif LGPL
|
||||
audio/out/ao_wasapi.h LGPL
|
||||
audio/out/ao_wasapi_utils.c LGPL
|
||||
audio/out/internal.h LGPL
|
||||
audio/out/pull.c LGPL
|
||||
audio/out/push.c LGPL
|
||||
x audio/audio.* very hard (mp_audio based of anders' af_audio)
|
||||
(required for LGPL core: determine if the struct can be used anyway, or
|
||||
devise a cheap AVFrame wrapper)
|
||||
audio/audio_buffer.* LGPL
|
||||
audio/chmap.* LGPL
|
||||
audio/chmap_sel.* LGPL
|
||||
audio/fmt-conversion.* LGPL
|
||||
audio/format.* LGPL
|
||||
common/av_common.* LGPL
|
||||
common/av_log.c almost LGPL
|
||||
common/av_log.h LGPL
|
||||
common/codecs.* LGPL
|
||||
common/common.* LGPL
|
||||
common/encode.h LGPL
|
||||
common/encode_lavc.* LGPL
|
||||
common/global.h LGPL
|
||||
common/msg.c almost LGPL
|
||||
common/msg_control.h LGPL
|
||||
common/msg.h LGPL
|
||||
common/playlist.* LGPL
|
||||
common/recorder.* LGPL
|
||||
common/tags.* LGPL
|
||||
common/version.c LGPL
|
||||
demux/codec_tags.* LGPL
|
||||
demux/cue.* LGPL
|
||||
demux/demux.* LGPL
|
||||
demux/demux_cue.c LGPL
|
||||
demux/demux_disc.c LGPL
|
||||
demux/demux_edl.c LGPL
|
||||
demux/demux_lavf.c almost LGPL
|
||||
demux/demux_libarchive.c LGPL
|
||||
demux/demux_mf.c LGPLv3+
|
||||
demux/demux_mkv.c LGPL (mostly)
|
||||
demux/demux_mkv_timeline.c LGPL
|
||||
demux/demux_null.c LGPL
|
||||
demux/demux_playlist.c LGPL
|
||||
demux/demux_rar.c LGPL
|
||||
demux/demux_raw.c almost LGPL
|
||||
demux/demux_timeline.c LGPL
|
||||
demux/demux_tv.c will stay GPL
|
||||
demux/ebml.* LGPL
|
||||
demux/matroska.h LGPL
|
||||
demux/packet.* LGPL
|
||||
demux/stheader.h LGPL
|
||||
demux/timeline.* LGPL
|
||||
input/cmd_* LGPL
|
||||
input/event.* LGPL
|
||||
input/input.* LGPL
|
||||
input/ipc.c LGPL
|
||||
input/ipc-unix.c LGPL
|
||||
input/ipc-win.c LGPL
|
||||
input/keycodes.* LGPL
|
||||
input/pipe-win32.c LGPL
|
||||
libmpv/*.* LGPL
|
||||
misc/*.* LGPL
|
||||
options/m_config.* LGPL
|
||||
options/m_option.c almost LGPL
|
||||
options/m_option.h LGPL
|
||||
options/m_property.* LGPL
|
||||
options/options.* LGPL
|
||||
options/parse_commandline.* LGPL
|
||||
options/parse_configfile.* LGPL
|
||||
options/path.* LGPL
|
||||
osdep/android/* LGPL (BSD)
|
||||
osdep/ar/* LGPL (BSD)
|
||||
osdep/atomic.h LGPL
|
||||
osdep/compiler.h LGPL
|
||||
osdep/endian.h LGPL
|
||||
osdep/glob-win.c LGPL
|
||||
osdep/io.* LGPL
|
||||
osdep/macosx_application.h LGPL
|
||||
osdep/macosx_application.m LGPL
|
||||
osdep/macosx_application_objc.h LGPL
|
||||
osdep/macosx_compat.h LGPL
|
||||
osdep/macosx_events.* LGPL
|
||||
osdep/macosx_events_objc.h LGPL
|
||||
osdep/macosx_touchbar.* LGPL
|
||||
osdep/macosx_versions.h LGPL
|
||||
osdep/main-fn-cocoa.c LGPL
|
||||
osdep/main-fn.h LGPL
|
||||
osdep/main-fn-unix.c LGPL
|
||||
osdep/main-fn-win.c LGPL
|
||||
osdep/mpv.exe.manifest LGPL
|
||||
osdep/mpv.rc LGPL
|
||||
osdep/path.h LGPL
|
||||
osdep/path-macosx.m LGPL
|
||||
osdep/path-unix.c LGPL
|
||||
osdep/path-win.c LGPL
|
||||
osdep/semaphore.h LGPL
|
||||
osdep/semaphore_osx.c LGPL
|
||||
osdep/strnlen.h LGPL
|
||||
osdep/subprocess.* LGPL
|
||||
osdep/subprocess-posix.c LGPL
|
||||
osdep/subprocess-win.c LGPL
|
||||
osdep/terminal.h LGPL
|
||||
osdep/terminal-unix.c LGPL
|
||||
osdep/terminal-win.c LGPL
|
||||
osdep/threads.* LGPL
|
||||
osdep/timer.c LGPL
|
||||
osdep/timer.h LGPL
|
||||
osdep/timer-darwin.c LGPL (MIT)
|
||||
osdep/timer-linux.c LGPL
|
||||
osdep/timer-win2.c LGPL
|
||||
osdep/w32_keyboard.c LGPL
|
||||
osdep/w32_keyboard.h LGPL
|
||||
osdep/win32-console-wrapper.c LGPL (BSD)
|
||||
osdep/win32/* LGPL (ISC)
|
||||
osdep/windows_utils.* LGPL
|
||||
x player/audio.c LGPL (dysfunctional due to libaf)
|
||||
player/client.* LGPL (ISC)
|
||||
player/command.c LGPL
|
||||
player/command.h LGPL
|
||||
player/configfiles.c LGPL
|
||||
player/core.h LGPL
|
||||
player/external_files.* LGPL
|
||||
player/lavfi.* LGPL
|
||||
player/loadfile.c LGPL
|
||||
player/lua/*.* LGPL
|
||||
player/lua.c LGPL
|
||||
player/misc.c LGPL
|
||||
player/osd.c LGPL
|
||||
player/playloop.c LGPL
|
||||
player/screenshot.* LGPL
|
||||
player/scripting.* LGPL
|
||||
player/sub.c LGPL
|
||||
player/video.c LGPL
|
||||
stream/ai_* will stay GPL (TV code)
|
||||
stream/audio_in.* will stay GPL (TV code)
|
||||
stream/cache.c LGPLv3+
|
||||
stream/cache_file.c LGPL
|
||||
stream/cookies.* LGPL
|
||||
stream/dvb* must stay GPL
|
||||
stream/frequencies.* must stay GPL
|
||||
stream/rar.* LGPL
|
||||
stream/stream_avdevice.c LGPL
|
||||
stream/stream_bluray.c LGPL
|
||||
stream/stream.* LGPL
|
||||
stream/stream_cb.c LGPL
|
||||
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_edl.c LGPL
|
||||
stream/stream_file.c LGPL
|
||||
stream/stream_lavf.c LGPL
|
||||
stream/stream_libarchive.* LGPL
|
||||
stream/stream_memory.c LGPL
|
||||
stream/stream_mf.c LGPL
|
||||
stream/stream_null.c LGPL
|
||||
stream/stream_rar.c LGPL
|
||||
stream/stream_smb.c will stay GPLv3
|
||||
stream/stream_tv.c will stay GPL
|
||||
stream/tv* will stay GPL
|
||||
sub/* LGPL
|
||||
ta/* LGPL (ISC)
|
||||
video/decode/d3d.* LGPL
|
||||
video/decode/dec_video.* almost LGPL
|
||||
video/decode/hw_cuda.c LGPL
|
||||
video/decode/hw_d3d11va.c LGPL
|
||||
video/decode/hw_dxva2.c LGPL
|
||||
video/decode/hw_videotoolbox.c LGPL
|
||||
video/decode/lavc.h almost LGPLv3+
|
||||
video/decode/vd.h LGPL
|
||||
video/decode/vd_lavc.c almost LGPLv3+
|
||||
video/filter/refqueue.* LGPL
|
||||
video/filter/vf.c LGPL
|
||||
video/filter/vf.h LGPL (mostly)
|
||||
video/filter/vf_buffer.c LGPL
|
||||
video/filter/vf_crop.c will be deleted
|
||||
video/filter/vf_d3d11vpp.c LGPL
|
||||
video/filter/vf_dsize.c will be deleted
|
||||
video/filter/vf_expand.c will be deleted
|
||||
video/filter/vf_flip.c will be deleted
|
||||
video/filter/vf_format.c will be deleted
|
||||
video/filter/vf_gradfun.c will be deleted
|
||||
video/filter/vf_lavfi.* LGPL
|
||||
video/filter/vf_mirror.c will be deleted
|
||||
video/filter/vf_noformat.c will be deleted
|
||||
video/filter/vf_pullup.c will be deleted
|
||||
video/filter/vf_rotate.c will be deleted
|
||||
video/filter/vf_scale.c will be deleted
|
||||
video/filter/vf_stereo3d.c will be deleted
|
||||
video/filter/vf_sub.c will be deleted
|
||||
video/filter/vf_vapoursynth.c LGPL
|
||||
video/filter/vf_vavpp.c LGPL
|
||||
video/filter/vf_vdpaupp.c LGPL
|
||||
video/filter/vf_yadif.c will be deleted
|
||||
video/csputils.* LGPL
|
||||
video/fmt-conversion.* LGPL
|
||||
video/gpu_memcpy.* will be deleted
|
||||
video/hwdec.* LGPL
|
||||
video/image_writer.* LGPL
|
||||
video/img_format.* LGPL
|
||||
video/mp_image.* almost LGPL
|
||||
video/mp_image_pool.* LGPL
|
||||
video/out/aspect.* LGPL
|
||||
video/out/bitmap_packer.* LGPL
|
||||
video/out/cocoa* LGPL
|
||||
video/out/d3d_shader_420p.h LGPL
|
||||
video/out/d3d_shader_nv12.h LGPL
|
||||
video/out/d3d_shader_yuv.hlsl LGPL
|
||||
video/out/dither.* LGPL
|
||||
video/out/drm_common.* LGPL
|
||||
video/out/filter_kernels.* LGPL (BSD)
|
||||
video/out/opengl/hwdec_vaglx.c GPL
|
||||
video/out/opengl/* LGPL
|
||||
video/out/vo.c LGPL
|
||||
video/out/vo.h LGPL
|
||||
video/out/vo_caca.c unknown
|
||||
video/out/vo_direct3d.c unknown
|
||||
video/out/vo_drm.c LGPL
|
||||
video/out/vo_image.c LGPL
|
||||
video/out/vo_lavc.c LGPL
|
||||
video/out/vo_null.c LGPL
|
||||
video/out/vo_opengl.c LGPL
|
||||
video/out/vo_opengl_cb.c LGPL
|
||||
video/out/vo_rpi.c LGPL
|
||||
video/out/vo_sdl.c LGPL
|
||||
video/out/vo_tct.c LGPL
|
||||
video/out/vo_vaapi.c probably impossible (some company's code)
|
||||
video/out/vo_vdpau.c probably impossible (nVidia's code)
|
||||
video/out/vo_wayland.c LGPL
|
||||
video/out/vo_x11.c probably impossible
|
||||
video/out/vo_xv.c probably impossible
|
||||
video/out/w32_common.* LGPL
|
||||
video/out/wayland* LGPL
|
||||
video/out/win32/* LGPL
|
||||
video/out/win_state.* LGPL
|
||||
video/out/x11_common.* probably impossible
|
||||
video/out/x11_icon.bin LGPL
|
||||
video/sws_utils.* almost LGPL
|
||||
video/vaapi.* hard (GPL-only parts must be ifdefed)
|
||||
video/vdpau.c hard (GPL-only parts must be ifdefed)
|
||||
video/vdpau_functions.inc LGPL
|
||||
video/vdpau.h unknown
|
||||
video/vdpau_mixer.* actual code must be rewritten
|
||||
DOCS/man/ GPLv2+
|
||||
bootstrap.py unknown license, probably GPLv2+ or LGPLv2+
|
||||
etc/mplayer-input.conf unknown license, probably GPLv2+
|
||||
mpv.desktop unknown license, probably GPLv2+
|
||||
etc/restore-old-bindings.conf unknown license, probably GPLv2+
|
||||
|
||||
The following files contain some optional GPL code (--enable-lgpl disables it):
|
||||
|
||||
options/parse_commandline.c dvd:// expansion
|
||||
|
||||
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).
|
||||
video/vt.* LGPL
|
||||
@@ -32,67 +32,6 @@ API changes
|
||||
|
||||
::
|
||||
|
||||
--- 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
|
||||
- make --stop-playback-on-init-failure=no the default in libmpv (just
|
||||
like in mpv CLI)
|
||||
--- mpv 0.27.0 ---
|
||||
1.25 - remove setting "no-" options via mpv_set_option*(). (See corresponding
|
||||
deprecation in 0.23.0.)
|
||||
|
||||
+21
-19
@@ -101,14 +101,11 @@ Installing MSYS2
|
||||
|
||||
1. Download an installer from https://msys2.github.io/
|
||||
|
||||
Both the i686 and the x86_64 version of MSYS2 can build 32-bit and 64-bit
|
||||
mpv binaries when running on a 64-bit version of Windows, but the x86_64
|
||||
version is preferred since the larger address space makes it less prone to
|
||||
fork() errors.
|
||||
It doesn't matter whether the i686 or the x86_64 version is used. Both can
|
||||
build 32-bit and 64-bit binaries when running on a 64-bit version of Windows.
|
||||
|
||||
2. Start a MinGW-w64 shell (``mingw64.exe``). **Note:** This is different from
|
||||
the MSYS2 shell that is started from the final installation dialog. You must
|
||||
close that shell and open a new one.
|
||||
2. Start a MinGW-w64 shell (``mingw64.exe``). Note that this is different
|
||||
from the MSYS2 shell that is started from the final installation dialog.
|
||||
|
||||
For a 32-bit build, use ``mingw32.exe``.
|
||||
|
||||
@@ -132,37 +129,42 @@ Installing mpv dependencies
|
||||
|
||||
```bash
|
||||
# Install MSYS2 build dependencies and a MinGW-w64 compiler
|
||||
pacman -S git python $MINGW_PACKAGE_PREFIX-{pkg-config,gcc}
|
||||
pacman -S git mingw-w64-x86_64-pkg-config python mingw-w64-x86_64-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,angleproject-git}
|
||||
# Install the most important MinGW-w64 dependencies. libass, libbluray and
|
||||
# lcms2 are also pulled in as dependencies of ffmpeg.
|
||||
pacman -S mingw-w64-x86_64-ffmpeg mingw-w64-x86_64-libjpeg-turbo mingw-w64-x86_64-lua51
|
||||
|
||||
# Install additional (optional) dependencies
|
||||
pacman -S mingw-w64-x86_64-libdvdnav mingw-w64-x86_64-libguess mingw-w64-x86_64-angleproject-git
|
||||
```
|
||||
|
||||
For a 32-bit build, install ``mingw-w64-i686-*`` packages instead.
|
||||
|
||||
Building mpv
|
||||
------------
|
||||
|
||||
Clone the latest mpv from git and install waf. **Note:** ``/usr/bin/python3``
|
||||
is invoked directly here, since an MSYS2 version of Python is required.
|
||||
Clone the latest mpv from git and install waf:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/mpv-player/mpv.git && cd mpv
|
||||
/usr/bin/python3 bootstrap.py
|
||||
./bootstrap.py
|
||||
```
|
||||
|
||||
Finally, compile and install mpv. Binaries will be installed to
|
||||
``/mingw64/bin`` or ``/mingw32/bin``.
|
||||
``/mingw64/bin``.
|
||||
|
||||
```bash
|
||||
/usr/bin/python3 waf configure CC=gcc.exe --check-c-compiler=gcc --prefix=$MSYSTEM_PREFIX
|
||||
/usr/bin/python3 waf install
|
||||
# For a 32-bit build, use --prefix=/mingw32 instead
|
||||
./waf configure CC=gcc.exe --check-c-compiler=gcc --prefix=/mingw64
|
||||
./waf install
|
||||
```
|
||||
|
||||
Or, compile and install both libmpv and mpv:
|
||||
|
||||
```bash
|
||||
/usr/bin/python3 waf configure CC=gcc.exe --check-c-compiler=gcc --enable-libmpv-shared --prefix=$MSYSTEM_PREFIX
|
||||
/usr/bin/python3 waf install
|
||||
./waf configure CC=gcc.exe --check-c-compiler=gcc --enable-libmpv-shared --prefix=/mingw64
|
||||
./waf install
|
||||
```
|
||||
|
||||
Linking libmpv with MSVC programs
|
||||
|
||||
+1
-1
@@ -68,7 +68,7 @@ Sending patches
|
||||
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
|
||||
obscure corner cases and 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.
|
||||
|
||||
|
||||
+7
-6
@@ -3,7 +3,8 @@ General usage
|
||||
|
||||
::
|
||||
|
||||
mpv infile -o outfile [-of outfileformat] [-ofopts formatoptions] [-orawts] \
|
||||
mpv infile -o outfile [-of outfileformat] [-ofopts formatoptions] \
|
||||
[-ofps outfps | -oautofps] [-oharddup] [-ocopyts | -orawts] [-oneverdrop] \
|
||||
[(any other mpv options)] \
|
||||
-ovc outvideocodec [-ovcopts outvideocodecoptions] \
|
||||
-oac outaudiocodec [-oacopts outaudiocodecoptions]
|
||||
@@ -59,13 +60,13 @@ for.
|
||||
Typical MPEG-4 Part 2 ("ASP", "DivX") encoding, AVI container::
|
||||
|
||||
mpv infile -o outfile.avi \
|
||||
--vf=fps=25 \
|
||||
-ofps 25 \
|
||||
-ovc mpeg4 -ovcopts qscale=4 \
|
||||
-oac libmp3lame -oacopts ab=128k
|
||||
|
||||
Note: AVI does not support variable frame rate, so the fps filter must be used.
|
||||
The frame rate should ideally match the input (25 for PAL, 24000/1001 or
|
||||
30000/1001 for NTSC)
|
||||
Note: AVI does not support variable frame rate, so -ofps must be used. The
|
||||
frame rate should ideally match the input (25 for PAL, 24000/1001 or 30000/1001
|
||||
for NTSC)
|
||||
|
||||
Typical MPEG-4 Part 10 ("AVC", "H.264") encoding, Matroska (MKV) container::
|
||||
|
||||
@@ -128,7 +129,7 @@ What works
|
||||
==========
|
||||
|
||||
* Encoding at variable frame rate (default)
|
||||
* Encoding at constant frame rate using --vf=fps=RATE
|
||||
* Encoding at constant frame rate using -ofps framerate -oharddup
|
||||
* 2-pass encoding (specify flags=+pass1 in the first pass's -ovcopts, specify
|
||||
flags=+pass2 in the second pass)
|
||||
* Hardcoding subtitles using vobsub, ass or srt subtitle rendering (just
|
||||
|
||||
@@ -19,142 +19,6 @@ Interface changes
|
||||
|
||||
::
|
||||
|
||||
--- mpv 0.29.1 ---
|
||||
- add --cocoa-cb-sw-renderer to control the usage of Apple Software Renderer
|
||||
--- 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.
|
||||
- edition and disc title switching will now fully reload playback (may have
|
||||
consequences for scripts, client API, or when using file-local options)
|
||||
- remove deprecated ao/vo auto profiles. Consider using scripts like
|
||||
auto-profiles.lua instead.
|
||||
--- mpv 0.28.0 ---
|
||||
- rename --hwdec=mediacodec option to mediacodec-copy, to reflect
|
||||
conventions followed by other hardware video decoding APIs
|
||||
- drop previously deprecated --heartbeat-cmd and --heartbeat--interval
|
||||
options
|
||||
- rename --vo=opengl to --vo=gpu
|
||||
- rename --opengl-backend to --gpu-context
|
||||
- rename --opengl-shaders to --glsl-shaders
|
||||
- rename --opengl-shader-cache-dir to --gpu-shader-cache-dir
|
||||
- rename --opengl-tex-pad-x/y to --gpu-tex-pad-x/y
|
||||
- rename --opengl-fbo-format to --fbo-format
|
||||
- rename --opengl-gamma to --gamma-factor
|
||||
- rename --opengl-debug to --gpu-debug
|
||||
- rename --opengl-sw to --gpu-sw
|
||||
- rename --opengl-vsync-fences to --swapchain-depth, and the interpretation
|
||||
slightly changed. Now defaults to 3.
|
||||
- rename the built-in profile `opengl-hq` to `gpu-hq`
|
||||
- the semantics of --opengl-es=yes are slightly changed -> now requires GLES
|
||||
- remove the (deprecated) alias --gpu-context=drm-egl
|
||||
- remove the (deprecated) --vo=opengl-hq
|
||||
- remove --opengl-es=force2 (use --opengl-es=yes --opengl-restrict=300)
|
||||
- the --msg-level option now affects --log-file
|
||||
- drop "audio-out-detected-device" property - this was unavailable on all
|
||||
audio output drivers for quite a while (coreaudio used to provide it)
|
||||
- deprecate --videotoolbox-format (use --hwdec-image-format, which affects
|
||||
most other hwaccels)
|
||||
- remove deprecated --demuxer-max-packets
|
||||
- remove most of the deprecated audio and video filters
|
||||
- remove the deprecated --balance option/property
|
||||
- rename the --opengl-hwdec-interop option to --gpu-hwdec-interop, and
|
||||
change some of its semantics: extend it take the strings "auto" and
|
||||
"all". "all" loads all backends. "auto" behaves like "all" for
|
||||
vo_opengl_cb, while on vo_gpu it loads nothing, but allows on demand
|
||||
loading by the decoder. The empty string as option value behaves like
|
||||
"auto". Old --hwdec values do not work anymore.
|
||||
This option is hereby declared as unstable and may change any time - its
|
||||
old use is deprecated, and it has very little use outside of debugging
|
||||
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
|
||||
backwards compatibility)
|
||||
- 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
|
||||
|
||||
+181
-30
@@ -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::
|
||||
|
||||
@@ -30,12 +29,6 @@ Available filters are:
|
||||
This filter is automatically enabled if the audio output does not support
|
||||
the audio configuration of the file being played.
|
||||
|
||||
.. warning::
|
||||
|
||||
Deprecated. Either use the ``--audio-resample-...`` options to customize
|
||||
resampling, or the libavfilter ``--af=aresample`` filter, which has its
|
||||
own options.
|
||||
|
||||
It supports only the following sample formats: u8, s16, s32, float.
|
||||
|
||||
``filter-size=<length>``
|
||||
@@ -53,8 +46,7 @@ Available filters are:
|
||||
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 the ``--audio-resample-...`` options.) This
|
||||
does not do anything anymore and the filter will never detach.
|
||||
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``.
|
||||
@@ -99,7 +91,82 @@ 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``
|
||||
``equalizer=g1:g2:g3:...:g10``
|
||||
10 octave band graphic equalizer, implemented using 10 IIR band-pass
|
||||
filters. This means that it works regardless of what type of audio is
|
||||
being played back. The center frequencies for the 10 bands are:
|
||||
|
||||
=== ==========
|
||||
No. frequency
|
||||
=== ==========
|
||||
0 31.25 Hz
|
||||
1 62.50 Hz
|
||||
2 125.00 Hz
|
||||
3 250.00 Hz
|
||||
4 500.00 Hz
|
||||
5 1.00 kHz
|
||||
6 2.00 kHz
|
||||
7 4.00 kHz
|
||||
8 8.00 kHz
|
||||
9 16.00 kHz
|
||||
=== ==========
|
||||
|
||||
If the sample rate of the sound being played is lower than the center
|
||||
frequency for a frequency band, then that band will be disabled. A known
|
||||
bug with this filter is that the characteristics for the uppermost band
|
||||
are not completely symmetric if the sample rate is close to the center
|
||||
frequency of that band. This problem can be worked around by upsampling
|
||||
the sound using a resampling filter before it reaches this filter.
|
||||
|
||||
``<g1>:<g2>:<g3>:...:<g10>``
|
||||
floating point numbers representing the gain in dB for each frequency
|
||||
band (-12-12)
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
``mpv --af=equalizer=11:11:10:5:0:-12:0:5:12:12 media.avi``
|
||||
Would amplify the sound in the upper and lower frequency region
|
||||
while canceling it almost completely around 1 kHz.
|
||||
|
||||
``channels=nch[:routes]``
|
||||
Can be used for adding, removing, routing and copying audio channels. If
|
||||
only ``<nch>`` is given, the default routing is used. It works as follows:
|
||||
If the number of output channels is greater than the number of input
|
||||
channels, empty channels are inserted (except when mixing from mono to
|
||||
stereo; then the mono channel is duplicated). If the number of output
|
||||
channels is less than the number of input channels, the exceeding
|
||||
channels are truncated.
|
||||
|
||||
``<nch>``
|
||||
number of output channels (1-8)
|
||||
``<routes>``
|
||||
List of ``,`` separated routes, in the form ``from1-to1,from2-to2,...``.
|
||||
Each pair defines where to route each channel. There can be at most
|
||||
8 routes. Without this argument, the default routing is used. Since
|
||||
``,`` is also used to separate filters, you must quote this argument
|
||||
with ``[...]`` or similar.
|
||||
|
||||
.. admonition:: Examples
|
||||
|
||||
``mpv --af=channels=4:[0-1,1-0,2-2,3-3] media.avi``
|
||||
Would change the number of channels to 4 and set up 4 routes that
|
||||
swap channel 0 and channel 1 and leave channel 2 and 3 intact.
|
||||
Observe that if media containing two channels were played back,
|
||||
channels 2 and 3 would contain silence but 0 and 1 would still be
|
||||
swapped.
|
||||
|
||||
``mpv --af=channels=6:[0-0,0-1,0-2,0-3] media.avi``
|
||||
Would change the number of channels to 6 and set up 4 routes that
|
||||
copy channel 0 to channels 0 to 3. Channel 4 and 5 will contain
|
||||
silence.
|
||||
|
||||
.. note::
|
||||
|
||||
You should probably not use this filter. If you want to change the
|
||||
output channel layout, try the ``format`` filter, which can make mpv
|
||||
automatically up- and downmix standard channel layouts.
|
||||
|
||||
``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
|
||||
@@ -128,6 +195,8 @@ Available filters are:
|
||||
Force mixing to a specific channel layout. See ``--audio-channels`` option
|
||||
for possible values.
|
||||
|
||||
``<out-format>``
|
||||
|
||||
``<out-srate>``
|
||||
|
||||
``<out-channels>``
|
||||
@@ -136,6 +205,107 @@ Available filters are:
|
||||
used to do conversion itself, unlike this one which lets the filter system
|
||||
handle the conversion.
|
||||
|
||||
``volume[=<volumedb>[:...]]``
|
||||
Implements software volume control. Use this filter with caution since it
|
||||
can reduce the signal to noise ratio of the sound. In most cases it is
|
||||
best to use the *Master* volume control of your sound card or the volume
|
||||
knob on your amplifier.
|
||||
|
||||
*WARNING*: This filter is deprecated. Use the top-level options like
|
||||
``--volume`` and ``--replaygain...`` instead.
|
||||
|
||||
*NOTE*: This filter is not reentrant and can therefore only be enabled
|
||||
once for every audio stream.
|
||||
|
||||
``<volumedb>``
|
||||
Sets the desired gain in dB for all channels in the stream from -200 dB
|
||||
to +60 dB, where -200 dB mutes the sound completely and +60 dB equals a
|
||||
gain of 1000 (default: 0).
|
||||
``replaygain-track``
|
||||
Adjust volume gain according to the track-gain replaygain value stored
|
||||
in the file metadata.
|
||||
``replaygain-album``
|
||||
Like replaygain-track, but using the album-gain value instead.
|
||||
``replaygain-preamp``
|
||||
Pre-amplification gain in dB to apply to the selected replaygain gain
|
||||
(default: 0).
|
||||
``replaygain-clip=yes|no``
|
||||
Prevent clipping caused by replaygain by automatically lowering the
|
||||
gain (default). Use ``replaygain-clip=no`` to disable this.
|
||||
``replaygain-fallback``
|
||||
Gain in dB to apply if the file has no replay gain tags. This option
|
||||
is always applied if the replaygain logic is somehow inactive. If this
|
||||
is applied, no other replaygain options are applied.
|
||||
``softclip``
|
||||
Turns soft clipping on. Soft-clipping can make the
|
||||
sound more smooth if very high volume levels are used. Enable this
|
||||
option if the dynamic range of the loudspeakers is very low.
|
||||
|
||||
*WARNING*: This feature creates distortion and should be considered a
|
||||
last resort.
|
||||
``s16``
|
||||
Force S16 sample format if set. Lower quality, but might be faster
|
||||
in some situations.
|
||||
``detach``
|
||||
Remove the filter if the volume is not changed at audio filter config
|
||||
time. Useful with replaygain: if the current file has no replaygain
|
||||
tags, then the filter will be removed if this option is enabled.
|
||||
(If ``--softvol=yes`` is used and the player volume controls are used
|
||||
during playback, a different volume filter will be inserted.)
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
``mpv --af=volume=10.1 media.avi``
|
||||
Would amplify the sound by 10.1 dB and hard-clip if the sound level
|
||||
is too high.
|
||||
|
||||
``pan=n:[<matrix>]``
|
||||
Mixes channels arbitrarily. Basically a combination of the volume and the
|
||||
channels filter that can be used to down-mix many channels to only a few,
|
||||
e.g. stereo to mono, or vary the "width" of the center speaker in a
|
||||
surround sound system. This filter is hard to use, and will require some
|
||||
tinkering before the desired result is obtained. The number of options for
|
||||
this filter depends on the number of output channels. An example how to
|
||||
downmix a six-channel file to two channels with this filter can be found
|
||||
in the examples section near the end.
|
||||
|
||||
``<n>``
|
||||
Number of output channels (1-8).
|
||||
``<matrix>``
|
||||
A list of values ``[L00,L01,L02,...,L10,L11,L12,...,Ln0,Ln1,Ln2,...]``,
|
||||
where each element ``Lij`` means how much of input channel i is mixed
|
||||
into output channel j (range 0-1). So in principle you first have n
|
||||
numbers saying what to do with the first input channel, then n numbers
|
||||
that act on the second input channel etc. If you do not specify any
|
||||
numbers for some input channels, 0 is assumed.
|
||||
Note that the values are separated by ``,``, which is already used
|
||||
by the option parser to separate filters. This is why you must quote
|
||||
the value list with ``[...]`` or similar.
|
||||
|
||||
.. admonition:: Examples
|
||||
|
||||
``mpv --af=pan=1:[0.5,0.5] media.avi``
|
||||
Would downmix from stereo to mono.
|
||||
|
||||
``mpv --af=pan=3:[1,0,0.5,0,1,0.5] media.avi``
|
||||
Would give 3 channel output leaving channels 0 and 1 intact, and mix
|
||||
channels 0 and 1 into output channel 2 (which could be sent to a
|
||||
subwoofer for example).
|
||||
|
||||
.. note::
|
||||
|
||||
If you just want to force remixing to a certain output channel layout,
|
||||
it is easier to use the ``format`` filter. For example,
|
||||
``mpv '--af=format=channels=5.1' '--audio-channels=5.1'`` would always force
|
||||
remixing audio to 5.1 and output it like this.
|
||||
|
||||
This filter supports the following ``af-command`` commands:
|
||||
|
||||
``set-matrix``
|
||||
Set the ``<matrix>`` argument dynamically. This can be used to change
|
||||
the mixing matrix at runtime, without reinitializing the entire filter
|
||||
chain.
|
||||
|
||||
``scaletempo[=option1:option2:...]``
|
||||
Scales audio tempo without altering pitch, optionally synced to playback
|
||||
speed (default).
|
||||
@@ -225,12 +395,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.
|
||||
|
||||
@@ -245,16 +409,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
-19
@@ -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
|
||||
@@ -209,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
|
||||
|
||||
|
||||
+72
-36
@@ -8,8 +8,9 @@ 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.
|
||||
@@ -21,16 +22,50 @@ You can encode files from one format/codec to another using this facility.
|
||||
``--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.
|
||||
@@ -47,22 +82,30 @@ You can encode files from one format/codec to another using this facility.
|
||||
``--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.
|
||||
|
||||
@@ -80,12 +123,26 @@ You can encode files from one format/codec to another using this facility.
|
||||
``--ovcopts-add=<options1[,options2,...]>``
|
||||
Appends the options given as arguments to the options list.
|
||||
|
||||
``--ovcopts=""``
|
||||
``--ovcopts-pre=<options1[,options2,...]>``
|
||||
Prepends the options given as arguments to the options list.
|
||||
|
||||
``--ovcopts-del=<index1[,index2,...]>``
|
||||
Deletes the options at the given indexes. Index numbers start at 0,
|
||||
negative numbers address the end of the list (-1 is the last).
|
||||
|
||||
``--ovcopts-clr``
|
||||
Completely empties the options list.
|
||||
|
||||
``--ovfirst``
|
||||
Force the video stream to become the first stream in the output.
|
||||
By default, the order is unspecified. Deprecated.
|
||||
By default, the order is unspecified.
|
||||
|
||||
``--ocopyts``
|
||||
Copies input pts to the output video (not supported by some output
|
||||
container formats, e.g. AVI). Discontinuities are still fixed.
|
||||
By default, audio pts are set to playback time and video pts are
|
||||
synchronized to match audio pts, as some output formats do not support
|
||||
anything else.
|
||||
|
||||
``--orawts``
|
||||
Copies input pts to the output video (not supported by some output
|
||||
@@ -93,27 +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.
|
||||
|
||||
.. 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.
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
"``--oremove-metadata=comment,genre``"
|
||||
excludes copying of the the comment and genre tags to the output
|
||||
file.
|
||||
+73
-146
@@ -350,7 +350,7 @@ List of Input Commands
|
||||
Print text to stdout. The string can contain properties (see
|
||||
`Property Expansion`_).
|
||||
|
||||
``show-text "<string>" [<duration>|-1 [<level>]]``
|
||||
``show-text "<string>" [<duration>|- [<level>]]``
|
||||
Show text on the OSD. The string can contain properties, which are expanded
|
||||
as described in `Property Expansion`_. This can be used to show playback
|
||||
time, filename, and so on.
|
||||
@@ -499,29 +499,35 @@ Input Commands that are Possibly Subject to Change
|
||||
|
||||
- ``a vf set flip`` turn video upside-down on the ``a`` key
|
||||
- ``b vf set ""`` remove all video filters on ``b``
|
||||
- ``c vf toggle gradfun`` toggle debanding on ``c``
|
||||
- ``c vf toggle lavfi=gradfun`` toggle debanding on ``c``
|
||||
|
||||
.. admonition:: Example how to toggle disabled filters at runtime
|
||||
|
||||
- Add something like ``vf-add=@deband:!gradfun`` to ``mpv.conf``.
|
||||
The ``@deband:`` is the label, an arbitrary, user-given name for this
|
||||
filter entry. The ``!`` before the filter name disables the filter by
|
||||
default. Everything after this is the normal filter name and possibly
|
||||
filter parameters, like in the normal ``--vf`` syntax.
|
||||
- Add something ``vf-add=@deband:!lavfi=[gradfun]`` to ``mpv.conf``. The
|
||||
``@deband:`` is the label, and ``deband`` is an arbitrary, user-given
|
||||
name for this filter entry. The ``!`` before the filter name disables
|
||||
the filter by default. Everything after this is the normal filter name
|
||||
and the filter parameters.
|
||||
- Add ``a vf toggle @deband`` to ``input.conf``. This toggles the
|
||||
"disabled" flag for the filter with the label ``deband`` when the
|
||||
``a`` key is hit.
|
||||
"disabled" flag for the filter identified with ``deband``.
|
||||
|
||||
``cycle-values ["!reverse"] <property> "<value1>" "<value2>" ...``
|
||||
Cycle through a list of values. Each invocation of the command will set the
|
||||
given property to the next value in the list. The command will use the
|
||||
current value of the property/option, and use it to determine the current
|
||||
position in the list of values. Once it has found it, it will set the
|
||||
next value in the list (wrapping around to the first item if needed).
|
||||
given property to the next value in the list. The command maintains an
|
||||
internal counter which value to pick next, and which is initially 0. It is
|
||||
reset to 0 once the last value is reached.
|
||||
|
||||
The internal counter is associated using the property name and the value
|
||||
list. If multiple commands (bound to different keys) use the same name
|
||||
and value list, they will share the internal counter.
|
||||
|
||||
The special argument ``!reverse`` can be used to cycle the value list in
|
||||
reverse. The only advantage is that you don't need to reverse the value
|
||||
list yourself when adding a second key binding for cycling backwards.
|
||||
reverse. Compared with a command that just lists the value in reverse, this
|
||||
command will actually share the internal counter with the forward-cycling
|
||||
key binding (as long as the rest of the arguments are the same).
|
||||
|
||||
Note that there is a static limit of (as of this writing) 10 arguments
|
||||
(this limit could be raised on demand).
|
||||
|
||||
``enable-section "<section>" [flags]``
|
||||
Enable all key bindings in the named input section.
|
||||
@@ -723,26 +729,7 @@ Input Commands that are Possibly Subject to Change
|
||||
merely sets all option values listed within the profile.
|
||||
|
||||
``load-script "<path>"``
|
||||
Load a script, similar to the ``--script`` option. Whether this waits for
|
||||
the script to finish initialization or not changed multiple times, and the
|
||||
future behavior is left undefined.
|
||||
|
||||
``change-list "<option>" "<operation>" "<value>"``
|
||||
This command changes list options as described in `List Options`_. The
|
||||
``<option>`` parameter is the normal option name, while ``<operation>`` is
|
||||
the suffix or action used on the option.
|
||||
|
||||
Some operations take no value, but the command still requires the value
|
||||
parameter. In these cases, the value must be an empty string.
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
``change-list glsl-shaders append file.glsl``
|
||||
|
||||
Add a filename to the ``glsl-shaders`` list. The command line
|
||||
equivalent is ``--glsl-shaders-append=file.glsl`` or alternatively
|
||||
``--glsl-shader=file.glsl``.
|
||||
|
||||
Load a script, similar to the ``--script`` option.
|
||||
|
||||
Undocumented commands: ``tv-last-channel`` (TV/DVB only),
|
||||
``ao-reload`` (experimental/internal).
|
||||
@@ -757,49 +744,6 @@ and obscure way to handle events that require stricter coordination. There are
|
||||
no API stability guarantees made. Not following the protocol exactly can make
|
||||
the player freeze randomly. Basically, nobody should use this API.
|
||||
|
||||
The C API is described in the header files. The Lua API is described in the
|
||||
Lua section.
|
||||
|
||||
The following hooks are currently defined:
|
||||
|
||||
``on_load``
|
||||
Called when a file is to be opened, before anything is actually done.
|
||||
For example, you could read and write the ``stream-open-filename``
|
||||
property to redirect an URL to something else (consider support for
|
||||
streaming sites which rarely give the user a direct media URL), or
|
||||
you could set per-file options with by setting the property
|
||||
``file-local-options/<option name>``. The player will wait until all
|
||||
hooks are run.
|
||||
|
||||
``on_load_fail``
|
||||
Called after after a file has been opened, but failed to. This can be
|
||||
used to provide a fallback in case native demuxers failed to recognize
|
||||
the file, instead of always running before the native demuxers like
|
||||
``on_load``. Demux will only be retried if ``stream-open-filename``
|
||||
was changed.
|
||||
|
||||
``on_preloaded``
|
||||
Called after a file has been opened, and before tracks are selected and
|
||||
decoders are created. This has some usefulness if an API users wants
|
||||
to select tracks manually, based on the set of available tracks. It's
|
||||
also useful to initialize ``--lavfi-complex`` in a specific way by API,
|
||||
without having to "probe" the available streams at first.
|
||||
|
||||
Note that this does not yet apply default track selection. Which operations
|
||||
exactly can be done and not be done, and what information is available and
|
||||
what is not yet available yet, is all subject to change.
|
||||
|
||||
``on_unload``
|
||||
Run before closing a file, and before actually uninitializing
|
||||
everything. It's not possible to resume playback in this state.
|
||||
|
||||
Legacy hook API
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
.. warning::
|
||||
|
||||
The legacy API is deprecated and will be removed soon.
|
||||
|
||||
There are two special commands involved. Also, the client must listen for
|
||||
client messages (``MPV_EVENT_CLIENT_MESSAGE`` in the C API).
|
||||
|
||||
@@ -821,6 +765,7 @@ client messages (``MPV_EVENT_CLIENT_MESSAGE`` in the C API).
|
||||
used to correctly handle multiple hooks registered by the same client,
|
||||
as long as the ``id`` argument is unique in the client)
|
||||
3. something undefined, used by the hook mechanism to track hook execution
|
||||
(currently, it's the hook-name, but this might change without warning)
|
||||
|
||||
Upon receiving this message, the client can handle the event. While doing
|
||||
this, the player core will still react to requests, but playback will
|
||||
@@ -834,6 +779,32 @@ client messages (``MPV_EVENT_CLIENT_MESSAGE`` in the C API).
|
||||
argument of the client message that starts hook execution for the
|
||||
current client.
|
||||
|
||||
The following hooks are currently defined:
|
||||
|
||||
``on_load``
|
||||
Called when a file is to be opened, before anything is actually done.
|
||||
For example, you could read and write the ``stream-open-filename``
|
||||
property to redirect an URL to something else (consider support for
|
||||
streaming sites which rarely give the user a direct media URL), or
|
||||
you could set per-file options with by setting the property
|
||||
``file-local-options/<option name>``. The player will wait until all
|
||||
hooks are run.
|
||||
|
||||
``on_preloaded``
|
||||
Called after a file has been opened, and before tracks are selected and
|
||||
decoders are created. This has some usefulness if an API users wants
|
||||
to select tracks manually, based on the set of available tracks. It's
|
||||
also useful to initialize ``--lavfi-complex`` in a specific way by API,
|
||||
without having to "probe" the available streams at first.
|
||||
|
||||
Note that this does not yet apply default track selection. Which operations
|
||||
exactly can be done and not be done, and what information is available and
|
||||
what is not yet available yet, is all subject to change.
|
||||
|
||||
``on_unload``
|
||||
Run before closing a file, and before actually uninitializing
|
||||
everything. It's not possible to resume playback in this state.
|
||||
|
||||
Input Command Prefixes
|
||||
----------------------
|
||||
|
||||
@@ -942,8 +913,8 @@ Property list
|
||||
|
||||
``file-size``
|
||||
Length in bytes of the source file/stream. (This is the same as
|
||||
``${stream-end}``. For segmented/multi-part files, this will return the
|
||||
size of the main or manifest file, whatever it is.)
|
||||
``${stream-end}``. For ordered chapters and such, the
|
||||
size of the currently played segment is returned.)
|
||||
|
||||
``estimated-frame-count``
|
||||
Total number of frames in current file.
|
||||
@@ -983,7 +954,8 @@ Property list
|
||||
|
||||
``stream-path``
|
||||
Filename (full path) of the stream layer filename. (This is probably
|
||||
useless and is almost never different from ``path``.)
|
||||
useless. It looks like this can be different from ``path`` only when
|
||||
using e.g. ordered chapters.)
|
||||
|
||||
``stream-pos``
|
||||
Raw byte position in source stream. Technically, this returns the position
|
||||
@@ -1287,58 +1259,6 @@ Property list
|
||||
Returns ``yes`` if the demuxer is idle, which means the demuxer cache is
|
||||
filled to the requested amount, and is currently not reading more data.
|
||||
|
||||
``demuxer-cache-state``
|
||||
Various undocumented or half-documented things.
|
||||
|
||||
Each entry in ``seekable-ranges`` represents a region in the demuxer cache
|
||||
that can be seeked to. If there are multiple demuxers active, this only
|
||||
returns information about the "main" demuxer, but might be changed in
|
||||
future to return unified information about all demuxers. The ranges are in
|
||||
arbitrary order. Often, ranges will overlap for a bit, before being joined.
|
||||
In broken corner cases, ranges may overlap all over the place.
|
||||
|
||||
The end of a seek range is usually smaller than the value returned by the
|
||||
``demuxer-cache-time`` property, because that property returns the guessed
|
||||
buffering amount, while the seek ranges represent the buffered data that
|
||||
can actually be used for cached seeking.
|
||||
|
||||
``fw-bytes`` is the number of bytes of packets buffered in the range
|
||||
starting from the current decoding position.
|
||||
|
||||
When querying the property with the client API using ``MPV_FORMAT_NODE``,
|
||||
or with Lua ``mp.get_property_native``, this will return a mpv_node with
|
||||
the following contents:
|
||||
|
||||
::
|
||||
|
||||
MPV_FORMAT_NODE_MAP
|
||||
"seekable-ranges" MPV_FORMAT_NODE_ARRAY
|
||||
MPV_FORMAT_NODE_MAP
|
||||
"start" MPV_FORMAT_DOUBLE
|
||||
"end" MPV_FORMAT_DOUBLE
|
||||
"fw-bytes" MPV_FORMAT_INT64
|
||||
|
||||
Other fields (might be changed or removed in the future):
|
||||
|
||||
``eof``
|
||||
True if the reader thread has hit the end of the file.
|
||||
|
||||
``underrun``
|
||||
True if the reader thread could not satisfy a decoder's request for a
|
||||
new packet.
|
||||
|
||||
``idle``
|
||||
True if the thread is currently not reading.
|
||||
|
||||
``total-bytes``
|
||||
Sum of packet bytes (plus some overhead estimation) of the entire packet
|
||||
queue, including cached seekable ranges.
|
||||
|
||||
``fw-bytes``
|
||||
Sum of packet bytes (plus some overhead estimation) of the readahead
|
||||
packet queue (packets between current decoder reader positions and
|
||||
demuxer position).
|
||||
|
||||
``demuxer-via-network``
|
||||
Returns ``yes`` if the stream demuxed via the main demuxer is most likely
|
||||
played via network. What constitutes "network" is not always clear, might
|
||||
@@ -1367,8 +1287,8 @@ Property list
|
||||
``seeking``
|
||||
Returns ``yes`` if the player is currently seeking, or otherwise trying
|
||||
to restart playback. (It's possible that it returns ``yes`` while a file
|
||||
is loadedThis is because the same underlying code is used for seeking and
|
||||
resyncing.)
|
||||
is loaded, or when switching ordered chapter segments. This is because
|
||||
the same underlying code is used for seeking and resyncing.)
|
||||
|
||||
``mixer-active``
|
||||
Return ``yes`` if the audio mixer is active, ``no`` otherwise.
|
||||
@@ -1464,9 +1384,9 @@ Property list
|
||||
``hwdec-interop``
|
||||
This returns the currently loaded hardware decoding/output interop driver.
|
||||
This is known only once the VO has opened (and possibly later). With some
|
||||
VOs (like ``gpu``), this might be never known in advance, but only when
|
||||
VOs (like ``opengl``), this might be never known in advance, but only when
|
||||
the decoder attempted to create the hw decoder successfully. (Using
|
||||
``--gpu-hwdec-interop`` can load it eagerly.) If there are multiple
|
||||
``--opengl-hwdec-interop`` can load it eagerly.) If there are multiple
|
||||
drivers loaded, they will be separated by ``,``.
|
||||
|
||||
If no VO is active or no interop driver is known, this property is
|
||||
@@ -1476,6 +1396,9 @@ Property list
|
||||
multiple interop drivers for the same hardware decoder, depending on
|
||||
platform and VO.
|
||||
|
||||
This is somewhat similar to the ``--opengl-hwdec-interop`` option, but
|
||||
it returns the actually loaded backend, not the value of this option.
|
||||
|
||||
``video-format``
|
||||
Video format as string.
|
||||
|
||||
@@ -1594,12 +1517,12 @@ Property list
|
||||
redrawing and frame display being somewhat disconnected, and you might
|
||||
have to pause and force a redraw.
|
||||
|
||||
Sub-properties::
|
||||
Sub-properties:
|
||||
|
||||
video-frame-info/picture-type
|
||||
video-frame-info/interlaced
|
||||
video-frame-info/tff
|
||||
video-frame-info/repeat
|
||||
``video-frame-info/picture-type``
|
||||
``video-frame-info/interlaced``
|
||||
``video-frame-info/tff``
|
||||
``video-frame-info/repeat``
|
||||
|
||||
``container-fps``
|
||||
Container FPS. This can easily contain bogus values. For videos that use
|
||||
@@ -2094,6 +2017,10 @@ Property list
|
||||
``current-ao``
|
||||
Current audio output driver (name as used with ``--ao``).
|
||||
|
||||
``audio-out-detected-device``
|
||||
Return the audio device selected by the AO driver (only implemented for
|
||||
some drivers: currently only ``coreaudio``).
|
||||
|
||||
``working-directory``
|
||||
Return the working directory of the mpv process. Can be useful for JSON IPC
|
||||
users, because the command line player usually works with relative paths.
|
||||
@@ -2108,6 +2035,9 @@ Property list
|
||||
List of decoders supported. This lists decoders which can be passed to
|
||||
``--vd`` and ``--ad``.
|
||||
|
||||
``family``
|
||||
Decoder driver. Usually ``lavc`` for libavcodec.
|
||||
|
||||
``codec``
|
||||
Canonical codec name, which identifies the format the decoder can
|
||||
handle.
|
||||
@@ -2128,6 +2058,7 @@ Property list
|
||||
|
||||
MPV_FORMAT_NODE_ARRAY
|
||||
MPV_FORMAT_NODE_MAP (for each decoder entry)
|
||||
"family" MPV_FORMAT_STRING
|
||||
"codec" MPV_FORMAT_STRING
|
||||
"driver" MPV_FORMAT_STRING
|
||||
"description" MPV_FORMAT_STRING
|
||||
@@ -2137,10 +2068,6 @@ Property list
|
||||
The encoder names (``driver`` entries) can be passed to ``--ovc`` and
|
||||
``--oac`` (without the ``lavc:`` prefix required by ``--vd`` and ``--ad``).
|
||||
|
||||
``demuxer-lavf-list``
|
||||
List of available libavformat demuxers' names. This can be used to check
|
||||
for support for a specific format or use with ``--demuxer-lavf-format``.
|
||||
|
||||
``mpv-version``
|
||||
Return the mpv version/copyright string. Depending on how the binary was
|
||||
built, it might contain either a release version, or just a git hash.
|
||||
|
||||
+8
-1
@@ -180,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
|
||||
|
||||
+14
-31
@@ -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,6 +87,8 @@ 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
|
||||
---------------------------------
|
||||
|
||||
@@ -138,10 +144,6 @@ Otherwise, where the Lua APIs return ``nil`` on error, JS returns ``undefined``.
|
||||
|
||||
``mp.get_wakeup_pipe()``
|
||||
|
||||
``mp.register_idle(fn)``
|
||||
|
||||
``mp.unregister_idle(fn)``
|
||||
|
||||
``mp.enable_messages(level)``
|
||||
|
||||
``mp.register_script_message(name, fn)``
|
||||
@@ -162,14 +164,10 @@ Otherwise, where the Lua APIs return ``nil`` on error, JS returns ``undefined``.
|
||||
|
||||
``mp.msg.debug(...)``
|
||||
|
||||
``mp.msg.trace(...)``
|
||||
|
||||
``mp.utils.getcwd()`` (LE)
|
||||
|
||||
``mp.utils.readdir(path [, filter])`` (LE)
|
||||
|
||||
``mp.utils.file_info(path)`` (LE)
|
||||
|
||||
``mp.utils.split_path(path)``
|
||||
|
||||
``mp.utils.join_path(p1, p2)``
|
||||
@@ -178,12 +176,8 @@ Otherwise, where the Lua APIs return ``nil`` on error, JS returns ``undefined``.
|
||||
|
||||
``mp.utils.subprocess_detached(t)``
|
||||
|
||||
``mp.utils.getpid()`` (LE)
|
||||
|
||||
``mp.add_hook(type, priority, fn)``
|
||||
|
||||
``mp.options.read_options(obj [, identifier])`` (types: string/boolean/number)
|
||||
|
||||
Additional utilities
|
||||
--------------------
|
||||
|
||||
@@ -228,7 +222,7 @@ Note: ``read_file`` and ``write_file`` throw on errors, allow text content only.
|
||||
|
||||
``exit()`` (global)
|
||||
Make the script exit at the end of the current event loop iteration.
|
||||
Note: please remove added key bindings before calling ``exit()``.
|
||||
Note: please reomve added key bindings before calling ``exit()``.
|
||||
|
||||
``mp.utils.compile_js(fname, content_str)``
|
||||
Compiles the JS code ``content_str`` as file name ``fname`` (without loading
|
||||
@@ -267,7 +261,7 @@ 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)``
|
||||
------------------------------------
|
||||
@@ -320,10 +314,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);
|
||||
}
|
||||
@@ -343,12 +333,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``.
|
||||
+8
-47
@@ -39,10 +39,7 @@ option. Some scripts are loaded internally (like ``--osc``). Each script runs in
|
||||
its own thread. Your script is first run "as is", and once that is done, the event loop
|
||||
is entered. This event loop will dispatch events received by mpv and call your
|
||||
own event handlers which you have registered with ``mp.register_event``, or
|
||||
timers added with ``mp.add_timeout`` or similar. Note that since the
|
||||
script starts execution concurrently with player initialization, some properties
|
||||
may not be populated with meaningful values until the relevant subsystems have
|
||||
initialized.
|
||||
timers added with ``mp.add_timeout`` or similar.
|
||||
|
||||
When the player quits, all scripts will be asked to terminate. This happens via
|
||||
a ``shutdown`` event, which by default will make the event loop return. If your
|
||||
@@ -63,10 +60,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
|
||||
------------
|
||||
@@ -430,7 +424,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``
|
||||
@@ -490,17 +484,16 @@ with ``require 'mp.msg'``.
|
||||
|
||||
``msg.log(level, ...)``
|
||||
The level parameter is the message priority. It's a string and one of
|
||||
``fatal``, ``error``, ``warn``, ``info``, ``v``, ``debug``, ``trace``. The
|
||||
user's settings will determine which of these messages will be
|
||||
visible. Normally, all messages are visible, except ``v``, ``debug`` and
|
||||
``trace``.
|
||||
``fatal``, ``error``, ``warn``, ``info``, ``v``, ``debug``. The user's
|
||||
settings will determine which of these messages will be visible. Normally,
|
||||
all messages are visible, except ``v`` and ``debug``.
|
||||
|
||||
The parameters after that are all converted to strings. Spaces are inserted
|
||||
to separate multiple parameters.
|
||||
|
||||
You don't need to add newlines.
|
||||
|
||||
``msg.fatal(...)``, ``msg.error(...)``, ``msg.warn(...)``, ``msg.info(...)``, ``msg.verbose(...)``, ``msg.debug(...)``, ``msg.trace(...)``
|
||||
``msg.fatal(...)``, ``msg.error(...)``, ``msg.warn(...)``, ``msg.info(...)``, ``msg.verbose(...)``, ``msg.debug(...)``
|
||||
All of these are shortcuts and equivalent to the corresponding
|
||||
``msg.log(level, ...)`` call.
|
||||
|
||||
@@ -534,7 +527,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.
|
||||
|
||||
@@ -595,34 +588,6 @@ strictly part of the guaranteed API.
|
||||
|
||||
On error, ``nil, error`` is returned.
|
||||
|
||||
``utils.file_info(path)``
|
||||
Stats the given path for information and returns a table with the
|
||||
following entries:
|
||||
|
||||
``mode``
|
||||
protection bits (on Windows, always 755 (octal) for directories
|
||||
and 644 (octal) for files)
|
||||
``size``
|
||||
size in bytes
|
||||
``atime``
|
||||
time of last access
|
||||
``mtime``
|
||||
time of last modification
|
||||
``ctime``
|
||||
time of last metadata change (Linux) / time of creation (Windows)
|
||||
``is_file``
|
||||
Whether ``path`` is a regular file (boolean)
|
||||
``is_dir``
|
||||
Whether ``path`` is a directory (boolean)
|
||||
|
||||
``mode`` and ``size`` are integers.
|
||||
Timestamps (``atime``, ``mtime`` and ``ctime``) are integer seconds since
|
||||
the Unix epoch (Unix time).
|
||||
The booleans ``is_file`` and ``is_dir`` are provided as a convenience;
|
||||
they can be and are derived from ``mode``.
|
||||
|
||||
On error (eg. path does not exist), ``nil, error`` is returned.
|
||||
|
||||
``utils.split_path(path)``
|
||||
Split a path into directory component and filename component, and return
|
||||
them. The first return value is always the directory. The second return
|
||||
@@ -685,10 +650,6 @@ strictly part of the guaranteed API.
|
||||
|
||||
The function returns ``nil``.
|
||||
|
||||
``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
|
||||
|
||||
+20
-128
@@ -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,28 +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).
|
||||
|
||||
(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.
|
||||
@@ -473,7 +435,6 @@ Suffix Meaning
|
||||
-del Delete an existing item by integer index
|
||||
-pre Prepend 1 or more items
|
||||
-set Set a list of items
|
||||
-toggle Append an item, or remove if if it already exists
|
||||
============= ===============================================
|
||||
|
||||
Although some operations allow specifying multiple ``,``-separated items, using
|
||||
@@ -481,15 +442,10 @@ this is strongly discouraged and deprecated, except for ``-set``.
|
||||
|
||||
Without suffix, the action taken is normally ``-set``.
|
||||
|
||||
Some options (like ``--sub-file``, ``--audio-file``, ``--glsl-shader``) are
|
||||
Some options (like ``--sub-file``, ``--audio-file``, ``--opengl-shader``) are
|
||||
aliases for the proper option with ``-append`` action. For example,
|
||||
``--sub-file`` is an alias for ``--sub-files-append``.
|
||||
|
||||
Some options only support a subset of the above.
|
||||
|
||||
Options of this type can be changed at runtime using the ``change-list``
|
||||
command, which takes the suffix as separate operation parameter.
|
||||
|
||||
Playing DVDs
|
||||
------------
|
||||
|
||||
@@ -554,8 +510,8 @@ setting them to *no*. Even suboptions can be specified in this way.
|
||||
|
||||
::
|
||||
|
||||
# Use GPU-accelerated video output by default.
|
||||
vo=gpu
|
||||
# Use opengl video output by default.
|
||||
vo=opengl
|
||||
# Use quotes for text that can contain spaces:
|
||||
status-msg="Time: ${time-pos}"
|
||||
|
||||
@@ -611,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
|
||||
|
||||
::
|
||||
@@ -631,7 +582,7 @@ or at runtime with the ``apply-profile <name>`` command.
|
||||
[slow]
|
||||
profile-desc="some profile name"
|
||||
# reference a builtin profile
|
||||
profile=gpu-hq
|
||||
profile=opengl-hq
|
||||
|
||||
[fast]
|
||||
vo=vdpau
|
||||
@@ -732,49 +683,8 @@ listed.
|
||||
(``drop-frame-count`` property.)
|
||||
- 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 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.)
|
||||
|
||||
|
||||
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 *additional* data buffered in the stream cache in
|
||||
kilobytes. (``demuxer-cache-duration`` and ``cache-used`` properties.)
|
||||
|
||||
|
||||
PROTOCOLS
|
||||
@@ -814,15 +724,14 @@ 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.
|
||||
|
||||
``bluray://`` is an alias.
|
||||
``<number>`` (select playlist with the same index). You can list
|
||||
the available playlists with ``--msg-level=bd=v``.
|
||||
|
||||
``dvd://[title|[starttitle]-endtitle][/device]`` ``--dvd-device=PATH``
|
||||
|
||||
@@ -835,7 +744,7 @@ PROTOCOLS
|
||||
``dvdread://...:``
|
||||
|
||||
Play a DVD using the old libdvdread code. This is what MPlayer and
|
||||
older mpv versions used for ``dvd://``. Use is discouraged. It's
|
||||
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).
|
||||
|
||||
@@ -882,25 +791,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``
|
||||
|
||||
@@ -983,8 +877,6 @@ works like in older mpv releases. The profiles are currently defined as follows:
|
||||
|
||||
.. include:: osc.rst
|
||||
|
||||
.. include:: stats.rst
|
||||
|
||||
.. include:: lua.rst
|
||||
|
||||
.. include:: javascript.rst
|
||||
@@ -1174,7 +1066,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.
|
||||
|
||||
|
||||
+348
-734
File diff suppressed because it is too large.
Load diff
+1
-15
@@ -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.
|
||||
|
||||
@@ -171,15 +171,6 @@ Configurable Options
|
||||
marker with guide), or bar (fill).
|
||||
Default pre-0.21.0 was 'slider'.
|
||||
|
||||
``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.
|
||||
|
||||
``deadzonesize``
|
||||
Default: 0.5
|
||||
|
||||
@@ -281,11 +272,6 @@ Configurable Options
|
||||
|
||||
Display timecodes with milliseconds
|
||||
|
||||
``seekranges``
|
||||
Default: yes
|
||||
|
||||
Display seekable ranges on the seekbar
|
||||
|
||||
``visibility``
|
||||
Default: auto (auto hide/show on mouse move)
|
||||
|
||||
|
||||
@@ -1,162 +0,0 @@
|
||||
STATS
|
||||
=====
|
||||
|
||||
This builtin script displays information and statistics for the currently
|
||||
played file. It is enabled by default if mpv was compiled with Lua support.
|
||||
It can be disabled entirely using the ``--load-stats-overlay=no`` option.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
The following key bindings are active by default unless something else is
|
||||
already bound to them:
|
||||
|
||||
==== ==============================================
|
||||
i Show stats for a fixed duration
|
||||
I Toggle stats (shown until toggled again)
|
||||
==== ==============================================
|
||||
|
||||
While the stats are visible on screen the following key bindings are active,
|
||||
regardless of existing bindings. They allow you to switch between *pages* of
|
||||
stats:
|
||||
|
||||
==== ==================
|
||||
1 Show usual stats
|
||||
2 Show frame timings
|
||||
==== ==================
|
||||
|
||||
Font
|
||||
~~~~
|
||||
|
||||
For optimal visual experience, a font with support for many font weights and
|
||||
monospaced digits is recommended. By default, the open source font
|
||||
`Source Sans Pro <https://github.com/adobe-fonts/source-sans-pro>`_ is used.
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
This script can be customized through a config file ``script-opts/stats.conf``
|
||||
placed in mpv's user directory and through the ``--script-opts`` command-line
|
||||
option. The configuration syntax is described in `ON SCREEN CONTROLLER`_.
|
||||
|
||||
Configurable Options
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
``key_oneshot``
|
||||
Default: i
|
||||
``key_toggle``
|
||||
Default: I
|
||||
|
||||
Key bindings to display stats.
|
||||
|
||||
``key_page_1``
|
||||
Default: 1
|
||||
``key_page_2``
|
||||
Default: 2
|
||||
|
||||
Key bindings for page switching while stats are displayed.
|
||||
|
||||
``duration``
|
||||
Default: 4
|
||||
|
||||
How long the stats are shown in seconds (oneshot).
|
||||
|
||||
``redraw_delay``
|
||||
Default: 1
|
||||
|
||||
How long it takes to refresh the displayed stats in seconds (toggling).
|
||||
|
||||
``persistent_overlay``
|
||||
Default: no
|
||||
|
||||
When `no`, other scripts printing text to the screen can overwrite the
|
||||
displayed stats. When `yes`, displayed stats are persistently shown for the
|
||||
respective duration. This can result in overlapping text when multiple
|
||||
scripts decide to print text at the same time.
|
||||
|
||||
``plot_perfdata``
|
||||
Default: yes
|
||||
|
||||
Show graphs for performance data (page 2).
|
||||
|
||||
``plot_vsync_ratio``
|
||||
Default: yes
|
||||
``plot_vsync_jitter``
|
||||
Default: yes
|
||||
|
||||
Show graphs for vsync and jitter values (page 1). Only when toggled.
|
||||
|
||||
``flush_graph_data``
|
||||
Default: yes
|
||||
|
||||
Clear data buffers used for drawing graphs when toggling.
|
||||
|
||||
``font``
|
||||
Default: Source Sans Pro
|
||||
|
||||
Font name. Should support as many font weights as possible for optimal
|
||||
visual experience.
|
||||
|
||||
``font_mono``
|
||||
Default: Source Sans Pro
|
||||
|
||||
Font name for parts where monospaced characters are necessary to align
|
||||
text. Currently, monospaced digits are sufficient.
|
||||
|
||||
``font_size``
|
||||
Default: 8
|
||||
|
||||
Font size used to render text.
|
||||
|
||||
``font_color``
|
||||
Default: FFFFFF
|
||||
|
||||
Font color.
|
||||
|
||||
``border_size``
|
||||
Default: 0.8
|
||||
|
||||
Size of border drawn around the font.
|
||||
|
||||
``border_color``
|
||||
Default: 262626
|
||||
|
||||
Color of drawn border.
|
||||
|
||||
``alpha``
|
||||
Default: 11
|
||||
|
||||
Transparency for drawn text.
|
||||
|
||||
``plot_bg_border_color``
|
||||
Default: 0000FF
|
||||
|
||||
Border color used for drawing graphs.
|
||||
|
||||
``plot_bg_color``
|
||||
Default: 262626
|
||||
|
||||
Background color used for drawing graphs.
|
||||
|
||||
``plot_color``
|
||||
Default: FFFFFF
|
||||
|
||||
Color used for drawing graphs.
|
||||
|
||||
Note: colors are given as hexadecimal values and use ASS tag order: BBGGRR
|
||||
(blue green red).
|
||||
|
||||
Different key bindings
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A different key binding can be defined with the aforementioned options
|
||||
``key_oneshot`` and ``key_toggle`` but also with commands in ``input.conf``,
|
||||
for example::
|
||||
|
||||
e script-binding stats/display-stats
|
||||
E script-binding stats/display-stats-toggle
|
||||
|
||||
Using ``input.conf``, it is also possible to directly display a certain page::
|
||||
|
||||
i script-binding stats/display-page-1
|
||||
e script-binding stats/display-page-2
|
||||
+341
-47
@@ -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,20 +75,15 @@ libavfilter bridge.
|
||||
Video filters are managed in lists. There are a few commands to manage 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-add=<filter1[,filter2,...]>``
|
||||
Appends 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-pre=<filter1[,filter2,...]>``
|
||||
Prepends the filters given as arguments to the filter list.
|
||||
|
||||
``--vf-del=filter``
|
||||
Deletes the filter. The filter can even given the way it was added (filter
|
||||
name and its full argument list), by label (prefixed with ``@``), or as
|
||||
index number. Index numbers start at 0, negative numbers address the end of
|
||||
the list (-1 is the last). (Passing multiple filters is currently still
|
||||
possible, but 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.
|
||||
@@ -109,8 +94,165 @@ 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:
|
||||
|
||||
``crop[=w:h:x:y]``
|
||||
Crops the given part of the image and discards the rest. Useful to remove
|
||||
black bands from widescreen videos.
|
||||
|
||||
``<w>,<h>``
|
||||
Cropped width and height, defaults to original width and height.
|
||||
``<x>,<y>``
|
||||
Position of the cropped picture, defaults to center.
|
||||
|
||||
``expand[=w:h:x:y:aspect:round]``
|
||||
Expands (not scales) video resolution to the given value and places the
|
||||
unscaled original at coordinates x, y.
|
||||
|
||||
``<w>,<h>``
|
||||
Expanded width,height (default: original width,height). Negative
|
||||
values for w and h are treated as offsets to the original size.
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
``expand=0:-50:0:0``
|
||||
Adds a 50 pixel border to the bottom of the picture.
|
||||
|
||||
``<x>,<y>``
|
||||
position of original image on the expanded image (default: center)
|
||||
|
||||
``<aspect>``
|
||||
Expands to fit an aspect instead of a resolution (default: 0).
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
``expand=800::::4/3``
|
||||
Expands to 800x600, unless the source is higher resolution, in
|
||||
which case it expands to fill a 4/3 aspect.
|
||||
|
||||
``<round>``
|
||||
Rounds up to make both width and height divisible by <r> (default: 1).
|
||||
|
||||
``flip``
|
||||
Flips the image upside down.
|
||||
|
||||
``mirror``
|
||||
Mirrors the image on the Y axis.
|
||||
|
||||
``rotate[=0|90|180|270]``
|
||||
Rotates the image by a multiple of 90 degrees clock-wise.
|
||||
|
||||
``scale[=w:h:param:param2:chr-drop:noup:arnd``
|
||||
Scales the image with the software scaler (slow) and performs a YUV<->RGB
|
||||
color space conversion (see also ``--sws``).
|
||||
|
||||
All parameters are optional.
|
||||
|
||||
``<w>:<h>``
|
||||
scaled width/height (default: original width/height)
|
||||
|
||||
:0: scaled d_width/d_height
|
||||
:-1: original width/height
|
||||
:-2: Calculate w/h using the other dimension and the prescaled
|
||||
aspect ratio.
|
||||
:-3: Calculate w/h using the other dimension and the original
|
||||
aspect ratio.
|
||||
:-(n+8): Like -n above, but rounding the dimension to the closest
|
||||
multiple of 16.
|
||||
|
||||
``<param>[:<param2>]`` (see also ``--sws``)
|
||||
Set some scaling parameters depending on the type of scaler selected
|
||||
with ``--sws``::
|
||||
|
||||
--sws=2 (bicubic): B (blurring) and C (ringing)
|
||||
0.00:0.60 default
|
||||
0.00:0.75 VirtualDub's "precise bicubic"
|
||||
0.00:0.50 Catmull-Rom spline
|
||||
0.33:0.33 Mitchell-Netravali spline
|
||||
1.00:0.00 cubic B-spline
|
||||
|
||||
--sws=7 (Gaussian): sharpness (0 (soft) - 100 (sharp))
|
||||
|
||||
--sws=9 (Lanczos): filter length (1-10)
|
||||
|
||||
``<chr-drop>``
|
||||
chroma skipping
|
||||
|
||||
:0: Use all available input lines for chroma (default).
|
||||
:1: Use only every 2. input line for chroma.
|
||||
:2: Use only every 4. input line for chroma.
|
||||
:3: Use only every 8. input line for chroma.
|
||||
|
||||
``<noup>``
|
||||
Disallow upscaling past the original dimensions.
|
||||
|
||||
:0: Allow upscaling (default).
|
||||
:1: Disallow upscaling if one dimension exceeds its original value.
|
||||
:2: Disallow upscaling if both dimensions exceed their original values.
|
||||
|
||||
``<arnd>``
|
||||
Accurate rounding for the vertical scaler, which may be faster or
|
||||
slower than the default rounding.
|
||||
|
||||
:no: Disable accurate rounding (default).
|
||||
:yes: Enable accurate rounding.
|
||||
|
||||
``dsize[=w:h:aspect-method:r:aspect]``
|
||||
Changes the intended display aspect at an arbitrary point in the
|
||||
filter chain. Aspect can be given as a fraction (4/3) or floating point
|
||||
number (1.33). Note that this filter does *not* do any scaling itself; it
|
||||
just affects what later scalers (software or hardware) will do when
|
||||
auto-scaling to the correct aspect.
|
||||
|
||||
``<w>,<h>``
|
||||
New aspect ratio given by a display width and height. Unlike older mpv
|
||||
versions or MPlayer, this does not set the display size.
|
||||
|
||||
Can also be these special values:
|
||||
|
||||
:0: original display width and height
|
||||
:-1: original video width and height (default)
|
||||
:-2: Calculate w/h using the other dimension and the original display
|
||||
aspect ratio.
|
||||
:-3: Calculate w/h using the other dimension and the original video
|
||||
aspect ratio.
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
``dsize=800:-2``
|
||||
Specifies a display resolution of 800x600 for a 4/3 aspect
|
||||
video, or 800x450 for a 16/9 aspect video.
|
||||
|
||||
``<aspect-method>``
|
||||
Modifies width and height according to original aspect ratios.
|
||||
|
||||
:-1: Ignore original aspect ratio (default).
|
||||
:0: Keep display aspect ratio by using ``<w>`` and ``<h>`` as maximum
|
||||
resolution.
|
||||
:1: Keep display aspect ratio by using ``<w>`` and ``<h>`` as minimum
|
||||
resolution.
|
||||
:2: Keep video aspect ratio by using ``<w>`` and ``<h>`` as maximum
|
||||
resolution.
|
||||
:3: Keep video aspect ratio by using ``<w>`` and ``<h>`` as minimum
|
||||
resolution.
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
``dsize=800:600:0``
|
||||
Specifies a display resolution of at most 800x600, or smaller,
|
||||
in order to keep aspect.
|
||||
|
||||
``<r>``
|
||||
Rounds up to make both width and height divisible by ``<r>``
|
||||
(default: 1).
|
||||
|
||||
``<aspect>``
|
||||
Force an aspect ratio.
|
||||
|
||||
``format=fmt=<value>:colormatrix=<value>:...``
|
||||
Restricts the color space for the next filter without doing any conversion.
|
||||
Use together with the scale filter for a real conversion.
|
||||
@@ -121,7 +263,9 @@ Available mpv-only filters are:
|
||||
|
||||
``<fmt>``
|
||||
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
|
||||
@@ -129,7 +273,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
|
||||
@@ -172,7 +316,7 @@ Available mpv-only filters are:
|
||||
used to override the setting.
|
||||
|
||||
This option only affects video output drivers that perform color
|
||||
management, for example ``gpu`` with the ``target-prim`` or
|
||||
management, for example ``opengl`` with the ``target-prim`` or
|
||||
``icc-profile`` suboptions set.
|
||||
|
||||
If this option is set to ``auto`` (which is the default), the video's
|
||||
@@ -281,6 +425,16 @@ Available mpv-only filters are:
|
||||
``<spherical-yaw>``, ``<spherical-pitch>``, ``<spherical-roll>``
|
||||
Reference angle in degree, if spherical video is used.
|
||||
|
||||
``noformat[=fmt]``
|
||||
Restricts the color space for the next filter without doing any conversion.
|
||||
Unlike the format filter, this will allow any color space except the one
|
||||
you specify.
|
||||
|
||||
.. note:: For a list of available formats, see ``noformat=fmt=help``.
|
||||
|
||||
``<fmt>``
|
||||
Format name, e.g. rgb15, bgr24, 420p, etc. (default: 420p).
|
||||
|
||||
``lavfi=graph[:sws-flags[:o=opts]]``
|
||||
Filter video using FFmpeg's libavfilter.
|
||||
|
||||
@@ -295,14 +449,11 @@ Available mpv-only filters are:
|
||||
|
||||
If you want to use the full filter syntax with this option, you have
|
||||
to quote the filter graph in order to prevent mpv's syntax and the
|
||||
filter graph syntax from clashing. To prevent a quoting and escaping
|
||||
mess, consider using ``--lavfi-complex`` if you know which video
|
||||
track you want to use from the input file. (There is only one video
|
||||
track for nearly all video files anyway.)
|
||||
filter graph syntax from clashing.
|
||||
|
||||
.. admonition:: Examples
|
||||
|
||||
``--vf=lavfi=[gradfun=20:30,vflip]``
|
||||
``-vf lavfi=[gradfun=20:30,vflip]``
|
||||
``gradfun`` filter with nonsense parameters, followed by a
|
||||
``vflip`` filter. (This demonstrates how libavfilter takes a
|
||||
graph and not just a single filter.) The filter graph string is
|
||||
@@ -333,6 +484,62 @@ Available mpv-only filters are:
|
||||
``'--vf=lavfi=yadif:o="threads=2,thread_type=slice"'``
|
||||
forces a specific threading configuration.
|
||||
|
||||
``pullup[=jl:jr:jt:jb:sb:mp]``
|
||||
Pulldown reversal (inverse telecine) filter, capable of handling mixed
|
||||
hard-telecine, 24000/1001 fps progressive, and 30000/1001 fps progressive
|
||||
content. The ``pullup`` filter makes use of future context in making its
|
||||
decisions. It is stateless in the sense that it does not lock onto a pattern
|
||||
to follow, but it instead looks forward to the following fields in order to
|
||||
identify matches and rebuild progressive frames.
|
||||
|
||||
``jl``, ``jr``, ``jt``, and ``jb``
|
||||
These options set the amount of "junk" to ignore at the left, right,
|
||||
top, and bottom of the image, respectively. Left/right are in units of
|
||||
8 pixels, while top/bottom are in units of 2 lines. The default is 8
|
||||
pixels on each side.
|
||||
|
||||
``sb`` (strict breaks)
|
||||
Setting this option to 1 will reduce the chances of ``pullup``
|
||||
generating an occasional mismatched frame, but it may also cause an
|
||||
excessive number of frames to be dropped during high motion sequences.
|
||||
Conversely, setting it to -1 will make ``pullup`` match fields more
|
||||
easily. This may help process video with slight blurring between the
|
||||
fields, but may also cause interlaced frames in the output.
|
||||
|
||||
``mp`` (metric plane)
|
||||
This option may be set to ``u`` or ``v`` to use a chroma plane instead of the
|
||||
luma plane for doing ``pullup``'s computations. This may improve accuracy
|
||||
on very clean source material, but more likely will decrease accuracy,
|
||||
especially if there is chroma noise (rainbow effect) or any grayscale
|
||||
video. The main purpose of setting ``mp`` to a chroma plane is to reduce
|
||||
CPU load and make pullup usable in realtime on slow machines.
|
||||
|
||||
``yadif=[mode:interlaced-only]``
|
||||
Yet another deinterlacing filter
|
||||
|
||||
``<mode>``
|
||||
:frame: Output 1 frame for each frame.
|
||||
:field: Output 1 frame for each field (default).
|
||||
:frame-nospatial: Like ``frame`` but skips spatial interlacing check.
|
||||
:field-nospatial: Like ``field`` but skips spatial interlacing check.
|
||||
|
||||
``<interlaced-only>``
|
||||
:no: Deinterlace all frames.
|
||||
:yes: Only deinterlace frames marked as interlaced (default).
|
||||
|
||||
This filter is automatically inserted when using the ``d`` key (or any
|
||||
other key that toggles the ``deinterlace`` property or when using the
|
||||
``--deinterlace`` switch), assuming the video output does not have native
|
||||
deinterlacing support.
|
||||
|
||||
If you just want to set the default mode, put this filter and its options
|
||||
into ``--vf-defaults`` instead, and enable deinterlacing with ``d`` or
|
||||
``--deinterlace``.
|
||||
|
||||
Also, note that the ``d`` key is stupid enough to insert a deinterlacer twice
|
||||
when inserting yadif with ``--vf``, so using the above methods is
|
||||
recommended.
|
||||
|
||||
``sub=[=bottom-margin:top-margin]``
|
||||
Moves subtitle rendering to an arbitrary point in the filter chain, or force
|
||||
subtitle rendering in the video filter as opposed to using video output OSD
|
||||
@@ -351,6 +558,91 @@ Available mpv-only filters are:
|
||||
subtitle colors and video under the influence of the video equalizer
|
||||
settings.
|
||||
|
||||
``stereo3d[=in:out]``
|
||||
Stereo3d converts between different stereoscopic image formats.
|
||||
|
||||
``<in>``
|
||||
Stereoscopic image format of input. Possible values:
|
||||
|
||||
``sbsl`` or ``side_by_side_left_first``
|
||||
side by side parallel (left eye left, right eye right)
|
||||
``sbsr`` or ``side_by_side_right_first``
|
||||
side by side crosseye (right eye left, left eye right)
|
||||
``abl`` or ``above_below_left_first``
|
||||
above-below (left eye above, right eye below)
|
||||
``abr`` or ``above_below_right_first``
|
||||
above-below (right eye above, left eye below)
|
||||
``ab2l`` or ``above_below_half_height_left_first``
|
||||
above-below with half height resolution (left eye above, right eye
|
||||
below)
|
||||
``ab2r`` or ``above_below_half_height_right_first``
|
||||
above-below with half height resolution (right eye above, left eye
|
||||
below)
|
||||
|
||||
``<out>``
|
||||
Stereoscopic image format of output. Possible values are all the input
|
||||
formats as well as:
|
||||
|
||||
``arcg`` or ``anaglyph_red_cyan_gray``
|
||||
anaglyph red/cyan gray (red filter on left eye, cyan filter on
|
||||
right eye)
|
||||
``arch`` or ``anaglyph_red_cyan_half_color``
|
||||
anaglyph red/cyan half colored (red filter on left eye, cyan filter
|
||||
on right eye)
|
||||
``arcc`` or ``anaglyph_red_cyan_color``
|
||||
anaglyph red/cyan color (red filter on left eye, cyan filter on
|
||||
right eye)
|
||||
``arcd`` or ``anaglyph_red_cyan_dubois``
|
||||
anaglyph red/cyan color optimized with the least-squares
|
||||
projection of Dubois (red filter on left eye, cyan filter on right
|
||||
eye)
|
||||
``agmg`` or ``anaglyph_green_magenta_gray``
|
||||
anaglyph green/magenta gray (green filter on left eye, magenta
|
||||
filter on right eye)
|
||||
``agmh`` or ``anaglyph_green_magenta_half_color``
|
||||
anaglyph green/magenta half colored (green filter on left eye,
|
||||
magenta filter on right eye)
|
||||
``agmc`` or ``anaglyph_green_magenta_color``
|
||||
anaglyph green/magenta colored (green filter on left eye, magenta
|
||||
filter on right eye)
|
||||
``aybg`` or ``anaglyph_yellow_blue_gray``
|
||||
anaglyph yellow/blue gray (yellow filter on left eye, blue filter
|
||||
on right eye)
|
||||
``aybh`` or ``anaglyph_yellow_blue_half_color``
|
||||
anaglyph yellow/blue half colored (yellow filter on left eye, blue
|
||||
filter on right eye)
|
||||
``aybc`` or ``anaglyph_yellow_blue_color``
|
||||
anaglyph yellow/blue colored (yellow filter on left eye, blue
|
||||
filter on right eye)
|
||||
``irl`` or ``interleave_rows_left_first``
|
||||
Interleaved rows (left eye has top row, right eye starts on next
|
||||
row)
|
||||
``irr`` or ``interleave_rows_right_first``
|
||||
Interleaved rows (right eye has top row, left eye starts on next
|
||||
row)
|
||||
``ml`` or ``mono_left``
|
||||
mono output (left eye only)
|
||||
``mr`` or ``mono_right``
|
||||
mono output (right eye only)
|
||||
|
||||
``gradfun[=strength[:radius|:size=<size>]]``
|
||||
Fix the banding artifacts that are sometimes introduced into nearly flat
|
||||
regions by truncation to 8-bit color depth. Interpolates the gradients that
|
||||
should go where the bands are, and dithers them.
|
||||
|
||||
``<strength>``
|
||||
Maximum amount by which the filter will change any one pixel. Also the
|
||||
threshold for detecting nearly flat regions (default: 1.5).
|
||||
|
||||
``<radius>``
|
||||
Neighborhood to fit the gradient to. Larger radius makes for smoother
|
||||
gradients, but also prevents the filter from modifying pixels near
|
||||
detailed regions (default: disabled).
|
||||
|
||||
``<size>``
|
||||
size of the filter in percent of the image diagonal size. This is
|
||||
used to calculate the final radius size (default: 1).
|
||||
|
||||
``vapoursynth=file:buffered-frames:concurrent-frames``
|
||||
Loads a VapourSynth filter script. This is intended for streamed
|
||||
processing: mpv actually provides a source filter, instead of using a
|
||||
@@ -468,7 +760,7 @@ Available mpv-only filters are:
|
||||
other userdata type will result in hard crashes.
|
||||
|
||||
``vavpp``
|
||||
VA-AP-API video post processing. Works with ``--vo=vaapi`` and ``--vo=gpu``
|
||||
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).
|
||||
@@ -478,22 +770,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
|
||||
@@ -505,12 +793,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>``
|
||||
@@ -548,7 +836,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).
|
||||
@@ -562,10 +850,16 @@ 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.
|
||||
|
||||
``buffer=<num>``
|
||||
Buffer ``<num>`` frames in the filter chain. This filter is probably pretty
|
||||
useless, except for debugging. (Note that this won't help to smooth out
|
||||
latencies with decoding, because the filter will never output a frame if
|
||||
the buffer isn't full, except on EOF.)
|
||||
+46
-72
@@ -14,7 +14,7 @@ in the list.
|
||||
|
||||
See ``--vo=help`` for a list of compiled-in video output drivers.
|
||||
|
||||
The recommended output driver is ``--vo=gpu``, which is the default. All
|
||||
The recommended output driver is ``--vo=opengl``, which is the default. All
|
||||
other drivers are for compatibility or special purposes. If the default
|
||||
does not work, it will fallback to other drivers (in the same order as
|
||||
listed by ``--vo=help``).
|
||||
@@ -273,34 +273,37 @@ Available video output drivers are:
|
||||
``--vo-direct3d-exact-backbuffer``
|
||||
Always resize the backbuffer to window size.
|
||||
|
||||
``gpu``
|
||||
General purpose, customizable, GPU-accelerated video output driver. It
|
||||
supports extended scaling methods, dithering, color management, custom
|
||||
shaders, HDR, and more.
|
||||
``opengl``
|
||||
OpenGL video output driver. It supports extended scaling methods, dithering
|
||||
and color management.
|
||||
|
||||
See `GPU renderer options`_ for options specific to this VO.
|
||||
See `OpenGL renderer options`_ for options specific to this VO.
|
||||
|
||||
By default, it tries to use fast and fail-safe settings. Use the
|
||||
``gpu-hq`` profile to use this driver with defaults set to high quality
|
||||
rendering. The profile can be applied with ``--profile=gpu-hq`` and its
|
||||
contents can be viewed with ``--show-profile=gpu-hq``.
|
||||
``opengl-hq`` profile to use this driver with defaults set to high
|
||||
quality rendering. (This profile is also the replacement for
|
||||
``--vo=opengl-hq``.) The profile can be applied with ``--profile=opengl-hq``
|
||||
and its contents can be viewed with ``--show-profile=opengl-hq``.
|
||||
|
||||
This VO abstracts over several possible graphics APIs and windowing
|
||||
contexts, which can be influenced using the ``--gpu-api`` and
|
||||
``--gpu-context`` options.
|
||||
Requires at least OpenGL 2.1.
|
||||
|
||||
Some features are available with OpenGL 3 capable graphics drivers only
|
||||
(or if the necessary extensions are available).
|
||||
|
||||
OpenGL ES 2.0 and 3.0 are supported as well.
|
||||
|
||||
Hardware decoding over OpenGL-interop is supported to some degree. Note
|
||||
that in this mode, some corner case might not be gracefully handled, and
|
||||
color space conversion and chroma upsampling is generally in the hand of
|
||||
the hardware decoder APIs.
|
||||
|
||||
``gpu`` makes use of FBOs by default. Sometimes you can achieve better
|
||||
quality or performance by changing the ``--gpu-fbo-format`` option to
|
||||
``opengl`` makes use of FBOs by default. Sometimes you can achieve better
|
||||
quality or performance by changing the ``--opengl-fbo-format`` option to
|
||||
``rgb16f``, ``rgb32f`` or ``rgb``. Known problems include Mesa/Intel not
|
||||
accepting ``rgb16``, Mesa sometimes not being compiled with float texture
|
||||
support, and some OS X setups being very slow with ``rgb16`` but fast
|
||||
with ``rgb32f``. If you have problems, you can also try enabling the
|
||||
``--gpu-dumb-mode=yes`` option.
|
||||
``--opengl-dumb-mode=yes`` option.
|
||||
|
||||
``sdl``
|
||||
SDL 2.0+ Render video output driver, depending on system with or without
|
||||
@@ -324,7 +327,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:
|
||||
|
||||
@@ -428,21 +431,37 @@ Available video output drivers are:
|
||||
``--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).
|
||||
@@ -472,7 +491,7 @@ 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).
|
||||
need this, check the ``drm`` backend for ``opengl`` VO).
|
||||
|
||||
The following global options are supported by this video output:
|
||||
|
||||
@@ -485,51 +504,6 @@ Available video output drivers are:
|
||||
(default: empty)
|
||||
|
||||
``--drm-mode=<number>``
|
||||
Mode ID to use (resolution and frame rate).
|
||||
Mode ID to use (resolution, bit depth and frame rate).
|
||||
(default: 0)
|
||||
|
||||
``--drm-osd-plane-id=<number>``
|
||||
Select the DRM plane index to use for OSD (or OSD and video).
|
||||
Index is zero based, and related to crtc.
|
||||
When using this option with the drm_prime renderer, it will only affect
|
||||
the OSD contents. Otherwise it will set OSD & video plane.
|
||||
(default: primary plane)
|
||||
|
||||
``--drm-video-plane-id=<number>``
|
||||
Select the DRM plane index to use for video layer.
|
||||
Index is zero based, and related to crtc.
|
||||
This option only has effect when using the drm_prime renderer (which
|
||||
supports several layers) together with ``vo=gpu`` and ``gpu-context=drm``.
|
||||
(default: first overlay plane)
|
||||
|
||||
``--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.
|
||||
|
||||
Unless you have an intel graphics card, a recent kernel and a recent
|
||||
version of mesa (>=18) xrgb2101010 is unlikely to work for you.
|
||||
|
||||
This currently only has an effect when used together with the ``drm``
|
||||
backend for the ``gpu`` VO. The ``drm`` VO always uses xrgb8888.
|
||||
|
||||
``--drm-osd-size=<[WxH]>``
|
||||
Sets the OSD OpenGL size to the specified size. OSD will then be upscaled
|
||||
to the current screen resolution. This option can be useful when using
|
||||
several layers in high resolutions with a GPU which cannot handle it.
|
||||
Note : this option is only available with DRM atomic support.
|
||||
(default: display resolution)
|
||||
|
||||
``mediacodec_embed`` (Android)
|
||||
Renders ``IMGFMT_MEDIACODEC`` frames directly to an ``android.view.Surface``.
|
||||
Requires ``--hwdec=mediacodec`` for hardware decoding, along with
|
||||
``--vo=mediacodec_embed`` and ``--wid=(intptr_t)(*android.view.Surface)``.
|
||||
|
||||
Since this video output driver uses native decoding and rendering routines,
|
||||
many of mpv's features (subtitle rendering, OSD/OSC, video filters, etc)
|
||||
are not available with this driver.
|
||||
|
||||
To use hardware decoding with ``--vo-gpu`` instead, use
|
||||
``--hwdec=mediacodec-copy`` along with ``--gpu-context=android``.
|
||||
@@ -76,8 +76,8 @@ Video
|
||||
|
||||
* Wayland support.
|
||||
* Native support for VAAPI and VDA. Improved VDPAU video output.
|
||||
* Improved GPU-accelerated video output (see the ``gpu-hq`` preset).
|
||||
* Make hardware decoding work with the ``gpu`` video output.
|
||||
* Improved OpenGL output (see the ``opengl-hq`` video output).
|
||||
* Make hardware decoding work with the ``opengl`` video output.
|
||||
* Support for libavfilter (for video->video and audio->audio). This allows
|
||||
using most of FFmpeg's filters, which improve greatly on the old MPlayer
|
||||
filters in features, performance, and correctness.
|
||||
@@ -85,7 +85,7 @@ Video
|
||||
for BT.2020 (Ultra HD). linear XYZ (Digital Cinema) and SMPTE ST2084 (HDR)
|
||||
inputs.
|
||||
* Support for color managed displays, via ICC profiles.
|
||||
* High-quality image resamplers (see the ``--scale`` suboption).
|
||||
* High-quality image resamplers (see the ``opengl`` ``scale`` suboption).
|
||||
* Support for scaling in (sigmoidized) linear light.
|
||||
* Better subtitle rendering using libass by default.
|
||||
* Improvements when playing multiple files (``-fixed-vo`` is default, do not
|
||||
|
||||
@@ -177,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
|
||||
|
||||
File renamed without changes.
-502
@@ -1,502 +0,0 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 2.1, February 1999
|
||||
|
||||
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
[This is the first released version of the Lesser GPL. It also counts
|
||||
as the successor of the GNU Library Public License, version 2, hence
|
||||
the version number 2.1.]
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
Licenses are intended to guarantee your freedom to share and change
|
||||
free software--to make sure the software is free for all its users.
|
||||
|
||||
This license, the Lesser General Public License, applies to some
|
||||
specially designated software packages--typically libraries--of the
|
||||
Free Software Foundation and other authors who decide to use it. You
|
||||
can use it too, but we suggest you first think carefully about whether
|
||||
this license or the ordinary General Public License is the better
|
||||
strategy to use in any particular case, based on the explanations below.
|
||||
|
||||
When we speak of free software, we are referring to freedom of use,
|
||||
not price. Our General Public Licenses are designed to make sure that
|
||||
you have the freedom to distribute copies of free software (and charge
|
||||
for this service if you wish); that you receive source code or can get
|
||||
it if you want it; that you can change the software and use pieces of
|
||||
it in new free programs; and that you are informed that you can do
|
||||
these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
distributors to deny you these rights or to ask you to surrender these
|
||||
rights. These restrictions translate to certain responsibilities for
|
||||
you if you distribute copies of the library or if you modify it.
|
||||
|
||||
For example, if you distribute copies of the library, whether gratis
|
||||
or for a fee, you must give the recipients all the rights that we gave
|
||||
you. You must make sure that they, too, receive or can get the source
|
||||
code. If you link other code with the library, you must provide
|
||||
complete object files to the recipients, so that they can relink them
|
||||
with the library after making changes to the library and recompiling
|
||||
it. And you must show them these terms so they know their rights.
|
||||
|
||||
We protect your rights with a two-step method: (1) we copyright the
|
||||
library, and (2) we offer you this license, which gives you legal
|
||||
permission to copy, distribute and/or modify the library.
|
||||
|
||||
To protect each distributor, we want to make it very clear that
|
||||
there is no warranty for the free library. Also, if the library is
|
||||
modified by someone else and passed on, the recipients should know
|
||||
that what they have is not the original version, so that the original
|
||||
author's reputation will not be affected by problems that might be
|
||||
introduced by others.
|
||||
|
||||
Finally, software patents pose a constant threat to the existence of
|
||||
any free program. We wish to make sure that a company cannot
|
||||
effectively restrict the users of a free program by obtaining a
|
||||
restrictive license from a patent holder. Therefore, we insist that
|
||||
any patent license obtained for a version of the library must be
|
||||
consistent with the full freedom of use specified in this license.
|
||||
|
||||
Most GNU software, including some libraries, is covered by the
|
||||
ordinary GNU General Public License. This license, the GNU Lesser
|
||||
General Public License, applies to certain designated libraries, and
|
||||
is quite different from the ordinary General Public License. We use
|
||||
this license for certain libraries in order to permit linking those
|
||||
libraries into non-free programs.
|
||||
|
||||
When a program is linked with a library, whether statically or using
|
||||
a shared library, the combination of the two is legally speaking a
|
||||
combined work, a derivative of the original library. The ordinary
|
||||
General Public License therefore permits such linking only if the
|
||||
entire combination fits its criteria of freedom. The Lesser General
|
||||
Public License permits more lax criteria for linking other code with
|
||||
the library.
|
||||
|
||||
We call this license the "Lesser" General Public License because it
|
||||
does Less to protect the user's freedom than the ordinary General
|
||||
Public License. It also provides other free software developers Less
|
||||
of an advantage over competing non-free programs. These disadvantages
|
||||
are the reason we use the ordinary General Public License for many
|
||||
libraries. However, the Lesser license provides advantages in certain
|
||||
special circumstances.
|
||||
|
||||
For example, on rare occasions, there may be a special need to
|
||||
encourage the widest possible use of a certain library, so that it becomes
|
||||
a de-facto standard. To achieve this, non-free programs must be
|
||||
allowed to use the library. A more frequent case is that a free
|
||||
library does the same job as widely used non-free libraries. In this
|
||||
case, there is little to gain by limiting the free library to free
|
||||
software only, so we use the Lesser General Public License.
|
||||
|
||||
In other cases, permission to use a particular library in non-free
|
||||
programs enables a greater number of people to use a large body of
|
||||
free software. For example, permission to use the GNU C Library in
|
||||
non-free programs enables many more people to use the whole GNU
|
||||
operating system, as well as its variant, the GNU/Linux operating
|
||||
system.
|
||||
|
||||
Although the Lesser General Public License is Less protective of the
|
||||
users' freedom, it does ensure that the user of a program that is
|
||||
linked with the Library has the freedom and the wherewithal to run
|
||||
that program using a modified version of the Library.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow. Pay close attention to the difference between a
|
||||
"work based on the library" and a "work that uses the library". The
|
||||
former contains code derived from the library, whereas the latter must
|
||||
be combined with the library in order to run.
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License Agreement applies to any software library or other
|
||||
program which contains a notice placed by the copyright holder or
|
||||
other authorized party saying it may be distributed under the terms of
|
||||
this Lesser General Public License (also called "this License").
|
||||
Each licensee is addressed as "you".
|
||||
|
||||
A "library" means a collection of software functions and/or data
|
||||
prepared so as to be conveniently linked with application programs
|
||||
(which use some of those functions and data) to form executables.
|
||||
|
||||
The "Library", below, refers to any such software library or work
|
||||
which has been distributed under these terms. A "work based on the
|
||||
Library" means either the Library or any derivative work under
|
||||
copyright law: that is to say, a work containing the Library or a
|
||||
portion of it, either verbatim or with modifications and/or translated
|
||||
straightforwardly into another language. (Hereinafter, translation is
|
||||
included without limitation in the term "modification".)
|
||||
|
||||
"Source code" for a work means the preferred form of the work for
|
||||
making modifications to it. For a library, complete source code means
|
||||
all the source code for all modules it contains, plus any associated
|
||||
interface definition files, plus the scripts used to control compilation
|
||||
and installation of the library.
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running a program using the Library is not restricted, and output from
|
||||
such a program is covered only if its contents constitute a work based
|
||||
on the Library (independent of the use of the Library in a tool for
|
||||
writing it). Whether that is true depends on what the Library does
|
||||
and what the program that uses the Library does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Library's
|
||||
complete source code as you receive it, in any medium, provided that
|
||||
you conspicuously and appropriately publish on each copy an
|
||||
appropriate copyright notice and disclaimer of warranty; keep intact
|
||||
all the notices that refer to this License and to the absence of any
|
||||
warranty; and distribute a copy of this License along with the
|
||||
Library.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy,
|
||||
and you may at your option offer warranty protection in exchange for a
|
||||
fee.
|
||||
|
||||
2. You may modify your copy or copies of the Library or any portion
|
||||
of it, thus forming a work based on the Library, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) The modified work must itself be a software library.
|
||||
|
||||
b) You must cause the files modified to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
c) You must cause the whole of the work to be licensed at no
|
||||
charge to all third parties under the terms of this License.
|
||||
|
||||
d) If a facility in the modified Library refers to a function or a
|
||||
table of data to be supplied by an application program that uses
|
||||
the facility, other than as an argument passed when the facility
|
||||
is invoked, then you must make a good faith effort to ensure that,
|
||||
in the event an application does not supply such function or
|
||||
table, the facility still operates, and performs whatever part of
|
||||
its purpose remains meaningful.
|
||||
|
||||
(For example, a function in a library to compute square roots has
|
||||
a purpose that is entirely well-defined independent of the
|
||||
application. Therefore, Subsection 2d requires that any
|
||||
application-supplied function or table used by this function must
|
||||
be optional: if the application does not supply it, the square
|
||||
root function must still compute square roots.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Library,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Library, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote
|
||||
it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Library.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Library
|
||||
with the Library (or with a work based on the Library) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may opt to apply the terms of the ordinary GNU General Public
|
||||
License instead of this License to a given copy of the Library. To do
|
||||
this, you must alter all the notices that refer to this License, so
|
||||
that they refer to the ordinary GNU General Public License, version 2,
|
||||
instead of to this License. (If a newer version than version 2 of the
|
||||
ordinary GNU General Public License has appeared, then you can specify
|
||||
that version instead if you wish.) Do not make any other change in
|
||||
these notices.
|
||||
|
||||
Once this change is made in a given copy, it is irreversible for
|
||||
that copy, so the ordinary GNU General Public License applies to all
|
||||
subsequent copies and derivative works made from that copy.
|
||||
|
||||
This option is useful when you wish to copy part of the code of
|
||||
the Library into a program that is not a library.
|
||||
|
||||
4. You may copy and distribute the Library (or a portion or
|
||||
derivative of it, under Section 2) in object code or executable form
|
||||
under the terms of Sections 1 and 2 above provided that you accompany
|
||||
it with the complete corresponding machine-readable source code, which
|
||||
must be distributed under the terms of Sections 1 and 2 above on a
|
||||
medium customarily used for software interchange.
|
||||
|
||||
If distribution of object code is made by offering access to copy
|
||||
from a designated place, then offering equivalent access to copy the
|
||||
source code from the same place satisfies the requirement to
|
||||
distribute the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
5. A program that contains no derivative of any portion of the
|
||||
Library, but is designed to work with the Library by being compiled or
|
||||
linked with it, is called a "work that uses the Library". Such a
|
||||
work, in isolation, is not a derivative work of the Library, and
|
||||
therefore falls outside the scope of this License.
|
||||
|
||||
However, linking a "work that uses the Library" with the Library
|
||||
creates an executable that is a derivative of the Library (because it
|
||||
contains portions of the Library), rather than a "work that uses the
|
||||
library". The executable is therefore covered by this License.
|
||||
Section 6 states terms for distribution of such executables.
|
||||
|
||||
When a "work that uses the Library" uses material from a header file
|
||||
that is part of the Library, the object code for the work may be a
|
||||
derivative work of the Library even though the source code is not.
|
||||
Whether this is true is especially significant if the work can be
|
||||
linked without the Library, or if the work is itself a library. The
|
||||
threshold for this to be true is not precisely defined by law.
|
||||
|
||||
If such an object file uses only numerical parameters, data
|
||||
structure layouts and accessors, and small macros and small inline
|
||||
functions (ten lines or less in length), then the use of the object
|
||||
file is unrestricted, regardless of whether it is legally a derivative
|
||||
work. (Executables containing this object code plus portions of the
|
||||
Library will still fall under Section 6.)
|
||||
|
||||
Otherwise, if the work is a derivative of the Library, you may
|
||||
distribute the object code for the work under the terms of Section 6.
|
||||
Any executables containing that work also fall under Section 6,
|
||||
whether or not they are linked directly with the Library itself.
|
||||
|
||||
6. As an exception to the Sections above, you may also combine or
|
||||
link a "work that uses the Library" with the Library to produce a
|
||||
work containing portions of the Library, and distribute that work
|
||||
under terms of your choice, provided that the terms permit
|
||||
modification of the work for the customer's own use and reverse
|
||||
engineering for debugging such modifications.
|
||||
|
||||
You must give prominent notice with each copy of the work that the
|
||||
Library is used in it and that the Library and its use are covered by
|
||||
this License. You must supply a copy of this License. If the work
|
||||
during execution displays copyright notices, you must include the
|
||||
copyright notice for the Library among them, as well as a reference
|
||||
directing the user to the copy of this License. Also, you must do one
|
||||
of these things:
|
||||
|
||||
a) Accompany the work with the complete corresponding
|
||||
machine-readable source code for the Library including whatever
|
||||
changes were used in the work (which must be distributed under
|
||||
Sections 1 and 2 above); and, if the work is an executable linked
|
||||
with the Library, with the complete machine-readable "work that
|
||||
uses the Library", as object code and/or source code, so that the
|
||||
user can modify the Library and then relink to produce a modified
|
||||
executable containing the modified Library. (It is understood
|
||||
that the user who changes the contents of definitions files in the
|
||||
Library will not necessarily be able to recompile the application
|
||||
to use the modified definitions.)
|
||||
|
||||
b) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (1) uses at run time a
|
||||
copy of the library already present on the user's computer system,
|
||||
rather than copying library functions into the executable, and (2)
|
||||
will operate properly with a modified version of the library, if
|
||||
the user installs one, as long as the modified version is
|
||||
interface-compatible with the version that the work was made with.
|
||||
|
||||
c) Accompany the work with a written offer, valid for at
|
||||
least three years, to give the same user the materials
|
||||
specified in Subsection 6a, above, for a charge no more
|
||||
than the cost of performing this distribution.
|
||||
|
||||
d) If distribution of the work is made by offering access to copy
|
||||
from a designated place, offer equivalent access to copy the above
|
||||
specified materials from the same place.
|
||||
|
||||
e) Verify that the user has already received a copy of these
|
||||
materials or that you have already sent this user a copy.
|
||||
|
||||
For an executable, the required form of the "work that uses the
|
||||
Library" must include any data and utility programs needed for
|
||||
reproducing the executable from it. However, as a special exception,
|
||||
the materials to be distributed need not include anything that is
|
||||
normally distributed (in either source or binary form) with the major
|
||||
components (compiler, kernel, and so on) of the operating system on
|
||||
which the executable runs, unless that component itself accompanies
|
||||
the executable.
|
||||
|
||||
It may happen that this requirement contradicts the license
|
||||
restrictions of other proprietary libraries that do not normally
|
||||
accompany the operating system. Such a contradiction means you cannot
|
||||
use both them and the Library together in an executable that you
|
||||
distribute.
|
||||
|
||||
7. You may place library facilities that are a work based on the
|
||||
Library side-by-side in a single library together with other library
|
||||
facilities not covered by this License, and distribute such a combined
|
||||
library, provided that the separate distribution of the work based on
|
||||
the Library and of the other library facilities is otherwise
|
||||
permitted, and provided that you do these two things:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work
|
||||
based on the Library, uncombined with any other library
|
||||
facilities. This must be distributed under the terms of the
|
||||
Sections above.
|
||||
|
||||
b) Give prominent notice with the combined library of the fact
|
||||
that part of it is a work based on the Library, and explaining
|
||||
where to find the accompanying uncombined form of the same work.
|
||||
|
||||
8. You may not copy, modify, sublicense, link with, or distribute
|
||||
the Library except as expressly provided under this License. Any
|
||||
attempt otherwise to copy, modify, sublicense, link with, or
|
||||
distribute the Library is void, and will automatically terminate your
|
||||
rights under this License. However, parties who have received copies,
|
||||
or rights, from you under this License will not have their licenses
|
||||
terminated so long as such parties remain in full compliance.
|
||||
|
||||
9. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Library or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Library (or any work based on the
|
||||
Library), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Library or works based on it.
|
||||
|
||||
10. Each time you redistribute the Library (or any work based on the
|
||||
Library), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute, link with or modify the Library
|
||||
subject to these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties with
|
||||
this License.
|
||||
|
||||
11. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Library at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Library by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Library.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under any
|
||||
particular circumstance, the balance of the section is intended to apply,
|
||||
and the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
12. If the distribution and/or use of the Library is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Library under this License may add
|
||||
an explicit geographical distribution limitation excluding those countries,
|
||||
so that distribution is permitted only in or among countries not thus
|
||||
excluded. In such case, this License incorporates the limitation as if
|
||||
written in the body of this License.
|
||||
|
||||
13. The Free Software Foundation may publish revised and/or new
|
||||
versions of the Lesser General Public License from time to time.
|
||||
Such new versions will be similar in spirit to the present version,
|
||||
but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Library
|
||||
specifies a version number of this License which applies to it and
|
||||
"any later version", you have the option of following the terms and
|
||||
conditions either of that version or of any later version published by
|
||||
the Free Software Foundation. If the Library does not specify a
|
||||
license version number, you may choose any version ever published by
|
||||
the Free Software Foundation.
|
||||
|
||||
14. If you wish to incorporate parts of the Library into other free
|
||||
programs whose distribution conditions are incompatible with these,
|
||||
write to the author to ask for permission. For software which is
|
||||
copyrighted by the Free Software Foundation, write to the Free
|
||||
Software Foundation; we sometimes make exceptions for this. Our
|
||||
decision will be guided by the two goals of preserving the free status
|
||||
of all derivatives of our free software and of promoting the sharing
|
||||
and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
|
||||
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
|
||||
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
||||
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
|
||||
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
|
||||
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
||||
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
|
||||
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
||||
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Libraries
|
||||
|
||||
If you develop a new library, and you want it to be of the greatest
|
||||
possible use to the public, we recommend making it free software that
|
||||
everyone can redistribute and change. You can do so by permitting
|
||||
redistribution under these terms (or, alternatively, under the terms of the
|
||||
ordinary General Public License).
|
||||
|
||||
To apply these terms, attach the following notices to the library. It is
|
||||
safest to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least the
|
||||
"copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the library's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This library 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.
|
||||
|
||||
This library 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 this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the library, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the
|
||||
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1990
|
||||
Ty Coon, President of Vice
|
||||
|
||||
That's all there is to it!
|
||||
@@ -5,29 +5,20 @@
|
||||
--------------
|
||||
|
||||
|
||||
* [External links](#external-links)
|
||||
* [Overview](#overview)
|
||||
* [System requirements](#system-requirements)
|
||||
* [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)
|
||||
|
||||
|
||||
## External links
|
||||
|
||||
|
||||
* [Wiki](https://github.com/mpv-player/mpv/wiki)
|
||||
* [FAQ](https://github.com/mpv-player/mpv/wiki/FAQ)
|
||||
* [Manual](http://mpv.io/manual/master/)
|
||||
|
||||
* [Man pages](http://mpv.io/manual/master/)
|
||||
* [Contact](#contact)
|
||||
* [License](#license)
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -39,10 +30,10 @@ Releases can be found on the [release list][releases].
|
||||
|
||||
## System requirements
|
||||
|
||||
- A not too ancient Linux, Windows 7 or later, or OSX 10.8 or later.
|
||||
- A somewhat capable CPU. Hardware decoding might help if the CPU is too slow to
|
||||
decode video in realtime, but must be explicitly enabled with the `--hwdec`
|
||||
option.
|
||||
- A not too ancient Linux, or Windows Vista or later, or OSX 10.8 or later.
|
||||
- A somewhat capable CPU. Hardware decoding might sometimes help if the CPU
|
||||
is too slow to decode video realtime, but must be explicitly enabled with
|
||||
the `--hwdec` option.
|
||||
- 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
|
||||
@@ -54,7 +45,7 @@ Releases can be found on the [release list][releases].
|
||||
|
||||
|
||||
For semi-official builds and third-party packages please see
|
||||
[mpv.io/installation](http://mpv.io/installation/).
|
||||
[mpv.io](http://mpv.io/installation/).
|
||||
|
||||
## Changelog
|
||||
|
||||
@@ -76,9 +67,9 @@ Changes to the default key bindings are indicated in
|
||||
Compiling with full features requires development files for several
|
||||
external libraries. Below is a list of some important requirements.
|
||||
|
||||
The mpv build system uses [waf](https://waf.io/), but we don't store it in the
|
||||
repository. The `./bootstrap.py` script will download the latest version
|
||||
of waf that was tested with the build system.
|
||||
The mpv build system uses *waf*, but we don't store it in your source tree. The
|
||||
script './bootstrap.py' will download the latest version of waf that was tested
|
||||
with the build system.
|
||||
|
||||
For a list of the available build options use `./waf configure --help`. If
|
||||
you think you have support for some feature installed but configure fails to
|
||||
@@ -110,6 +101,9 @@ Essential dependencies (incomplete list):
|
||||
- Audio output development headers (libasound/ALSA, pulseaudio)
|
||||
- FFmpeg libraries (libavutil libavcodec libavformat libswscale libavfilter
|
||||
and either libswresample or libavresample)
|
||||
At least FFmpeg 3.2.2 or Libav 12 is required.
|
||||
For hardware decoding with vaapi and vdpau, FFmpeg 3.3 or Libav git is
|
||||
required.
|
||||
- zlib
|
||||
- iconv (normally provided by the system libc)
|
||||
- libass (OSD, OSC, text subtitles)
|
||||
@@ -117,6 +111,7 @@ Essential dependencies (incomplete list):
|
||||
- libjpeg (optional, used for screenshots only)
|
||||
- uchardet (optional, for subtitle charset detection)
|
||||
- vdpau and vaapi libraries for hardware decoding on Linux (optional)
|
||||
(FFmpeg 3.3 or Libav git is also required.)
|
||||
|
||||
Libass dependencies:
|
||||
|
||||
@@ -132,17 +127,14 @@ FFmpeg dependencies:
|
||||
- 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).
|
||||
- For good nvidia support on Linux, make sure nv-codec-headers is installed
|
||||
and can be found by configure.
|
||||
- Libav support is broken. (See section below.)
|
||||
- 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,
|
||||
you may wish to use the separately available build wrapper ([mpv-build][mpv-build])
|
||||
which first compiles FFmpeg libraries and libass, and then compiles the player
|
||||
statically linked against those.
|
||||
Linux distributions. However, FFmpeg is an exception (distro versions may be
|
||||
too old to work well or at all). For that reason you may want to use
|
||||
the separately available build wrapper ([mpv-build][mpv-build]) that first
|
||||
compiles FFmpeg libraries and libass, and then compiles the player statically
|
||||
linked against those.
|
||||
|
||||
If you want to build a Windows binary, you either have to use MSYS2 and MinGW,
|
||||
or cross-compile from Linux with MinGW. See
|
||||
@@ -153,10 +145,24 @@ or cross-compile from Linux with MinGW. See
|
||||
|
||||
|
||||
Generally, mpv should work with the latest release as well as the git version
|
||||
of FFmpeg. Libav support is currently broken, because they did not add certain
|
||||
FFmpeg API changes which mpv relies on.
|
||||
of both FFmpeg and Libav. But FFmpeg is preferred, and some mpv features work
|
||||
with FFmpeg only (subtitle formats in particular).
|
||||
|
||||
|
||||
## Preferred FFmpeg version
|
||||
|
||||
|
||||
Using the latest FFmpeg release (or FFmpeg git master) is strongly recommended.
|
||||
Older versions are unsupported, even if the build system still happens to
|
||||
accept them. The main reason mpv still builds with older FFmpeg versions is to
|
||||
evade arguing with people (users, distros) who insist on using older FFmpeg
|
||||
versions for no rational reason.
|
||||
|
||||
If you want to use a stable FFmpeg release, use the latest release, which has
|
||||
most likely the best maintenance out of all stable releases. Older releases
|
||||
are for distros, and at best receive basic changes, like fixing critical security
|
||||
issues or build fixes, and at worst are completely abandoned.
|
||||
|
||||
## FFmpeg ABI compatibility
|
||||
|
||||
mpv does not support linking against FFmpeg versions it was not built with, even
|
||||
@@ -197,8 +203,6 @@ recommended (see [Contact](#Contact) below).
|
||||
## Contributing
|
||||
|
||||
|
||||
Please read [contribute.md][contribute.md].
|
||||
|
||||
For small changes you can just send us pull requests through GitHub. For bigger
|
||||
changes come and talk to us on IRC before you start working on them. It will
|
||||
make code review easier for both parties later on.
|
||||
@@ -217,12 +221,6 @@ For details see [FAQ entry](https://github.com/mpv-player/mpv/wiki/FAQ#How_is_mp
|
||||
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)
|
||||
|
||||
|
||||
## Contact
|
||||
|
||||
|
||||
@@ -246,4 +244,8 @@ only if discretion is required.
|
||||
[interface-changes]: https://github.com/mpv-player/mpv/blob/master/DOCS/interface-changes.rst
|
||||
[api-changes]: https://github.com/mpv-player/mpv/blob/master/DOCS/client-api-changes.rst
|
||||
[restore-old-bindings]: https://github.com/mpv-player/mpv/blob/master/etc/restore-old-bindings.conf
|
||||
[contribute.md]: https://github.com/mpv-player/mpv/blob/master/DOCS/contribute.md
|
||||
|
||||
## License
|
||||
|
||||
|
||||
Mostly GPLv2 or later. See [details.](https://github.com/mpv-player/mpv/blob/master/Copyright)
|
||||
+2790
-116
File diff suppressed because it is too large.
Load diff
@@ -10,10 +10,7 @@ export PYTHON=/usr/bin/python3
|
||||
"$PYTHON" bootstrap.py
|
||||
"$PYTHON" waf configure \
|
||||
--check-c-compiler=gcc \
|
||||
--disable-egl-angle-lib \
|
||||
--enable-crossc \
|
||||
--enable-d3d-hwaccel \
|
||||
--enable-d3d11 \
|
||||
--disable-cdda \
|
||||
--enable-egl-angle \
|
||||
--enable-jpeg \
|
||||
--enable-lcms2 \
|
||||
@@ -21,7 +18,5 @@ export PYTHON=/usr/bin/python3
|
||||
--enable-libass \
|
||||
--enable-lua \
|
||||
--enable-rubberband \
|
||||
--enable-shaderc \
|
||||
--enable-uchardet \
|
||||
--enable-vulkan
|
||||
--enable-uchardet
|
||||
"$PYTHON" waf build
|
||||
+12
-49
@@ -1,6 +1,15 @@
|
||||
#!/usr/bin/bash
|
||||
set -e
|
||||
|
||||
case $MSYSTEM in
|
||||
MINGW32)
|
||||
export MINGW_PACKAGE_PREFIX=mingw-w64-i686
|
||||
;;
|
||||
MINGW64)
|
||||
export MINGW_PACKAGE_PREFIX=mingw-w64-x86_64
|
||||
;;
|
||||
esac
|
||||
|
||||
# Write an empty fonts.conf to speed up fc-cache
|
||||
export FONTCONFIG_FILE=/dummy-fonts.conf
|
||||
cat >"$FONTCONFIG_FILE" <<EOF
|
||||
@@ -11,62 +20,16 @@ EOF
|
||||
|
||||
# Install build dependencies for mpv
|
||||
pacman -S --noconfirm --needed \
|
||||
$MINGW_PACKAGE_PREFIX-toolchain \
|
||||
$MINGW_PACKAGE_PREFIX-gcc \
|
||||
$MINGW_PACKAGE_PREFIX-angleproject-git \
|
||||
$MINGW_PACKAGE_PREFIX-cmake \
|
||||
$MINGW_PACKAGE_PREFIX-ffmpeg \
|
||||
$MINGW_PACKAGE_PREFIX-lcms2 \
|
||||
$MINGW_PACKAGE_PREFIX-libarchive \
|
||||
$MINGW_PACKAGE_PREFIX-libass \
|
||||
$MINGW_PACKAGE_PREFIX-libjpeg-turbo \
|
||||
$MINGW_PACKAGE_PREFIX-lua51 \
|
||||
$MINGW_PACKAGE_PREFIX-ninja \
|
||||
$MINGW_PACKAGE_PREFIX-rubberband \
|
||||
$MINGW_PACKAGE_PREFIX-uchardet \
|
||||
$MINGW_PACKAGE_PREFIX-vulkan
|
||||
$MINGW_PACKAGE_PREFIX-uchardet
|
||||
|
||||
# Delete unused packages to reduce space used in the Appveyor cache
|
||||
pacman -Sc --noconfirm
|
||||
|
||||
# Compile ffmpeg
|
||||
(
|
||||
git clone --depth=1 https://github.com/FFmpeg/ffmpeg.git && cd ffmpeg
|
||||
|
||||
mkdir build && cd build
|
||||
../configure \
|
||||
--prefix=$MINGW_PREFIX \
|
||||
--target-os=mingw32 \
|
||||
--arch=$MSYSTEM_CARCH \
|
||||
--disable-static \
|
||||
--disable-doc \
|
||||
--disable-asm \
|
||||
--enable-gpl \
|
||||
--enable-version3 \
|
||||
--enable-shared \
|
||||
--enable-pic \
|
||||
--enable-d3d11va \
|
||||
--enable-dxva2 \
|
||||
--enable-schannel
|
||||
make -j4 install
|
||||
)
|
||||
|
||||
# Compile shaderc
|
||||
(
|
||||
git clone --depth=1 https://github.com/google/shaderc && cd shaderc
|
||||
git clone --depth=1 https://github.com/google/glslang.git third_party/glslang
|
||||
git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Tools.git third_party/spirv-tools
|
||||
git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Headers.git third_party/spirv-headers
|
||||
|
||||
mkdir build && cd build
|
||||
cmake -GNinja -DCMAKE_BUILD_TYPE=Release -DSHADERC_SKIP_TESTS=ON \
|
||||
-DCMAKE_INSTALL_PREFIX=$MINGW_PREFIX ..
|
||||
ninja install
|
||||
cp -f libshaderc/libshaderc_shared.dll $MINGW_PREFIX/bin/
|
||||
)
|
||||
|
||||
# Compile crossc
|
||||
(
|
||||
git clone --depth=1 https://github.com/rossy/crossc && cd crossc
|
||||
git submodule update --init
|
||||
|
||||
make -j4 install prefix=$MINGW_PREFIX
|
||||
)
|
||||
@@ -24,8 +24,8 @@ def is_user_lib(objfile, 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()))
|
||||
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])
|
||||
@@ -35,7 +35,7 @@ def install_name_tool_id(name, objfile):
|
||||
|
||||
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:
|
||||
@@ -80,17 +80,15 @@ def process_libraries(libs_dict, binary, processed = []):
|
||||
if p in libs_dict[src]:
|
||||
install_name_tool_change(p, lib_name(p), dst)
|
||||
|
||||
return ls
|
||||
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))
|
||||
libs = libraries(binary)
|
||||
|
||||
libs_processed = process_libraries(libs, binary)
|
||||
while libs_processed is not None:
|
||||
libs_processed = process_libraries(libs, binary, libs_processed)
|
||||
print(libs)
|
||||
process_libraries(libs, binary)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -6,3 +6,8 @@ mp.observe_property("audio-device-list", "native", function(name, val)
|
||||
print(" - '" .. e.name .. "' (" .. e.description .. ")")
|
||||
end
|
||||
end)
|
||||
|
||||
mp.observe_property("audio-out-detected-device", "native", function(name, val)
|
||||
print("Detected audio device changed:")
|
||||
print(" - '" .. val)
|
||||
end)
|
||||
+12
-26
@@ -7,9 +7,7 @@
|
||||
-- Add at most 5000 * 2 files when starting a file (before + after).
|
||||
MAXENTRIES = 5000
|
||||
|
||||
local msg = require 'mp.msg'
|
||||
local options = require 'mp.options'
|
||||
local utils = require 'mp.utils'
|
||||
|
||||
o = {
|
||||
disabled = false
|
||||
@@ -24,9 +22,11 @@ end
|
||||
|
||||
EXTENSIONS = Set {
|
||||
'mkv', 'avi', 'mp4', 'ogv', 'webm', 'rmvb', 'flv', 'wmv', 'mpeg', 'mpg', 'm4v', '3gp',
|
||||
'mp3', 'wav', 'ogm', 'flac', 'm4a', 'wma', 'ogg', 'opus',
|
||||
'mp3', 'wav', 'ogv', 'flac', 'm4a', 'wma',
|
||||
}
|
||||
|
||||
mputils = require 'mp.utils'
|
||||
|
||||
function add_files_at(index, files)
|
||||
index = index - 1
|
||||
local oldcount = mp.get_property_number("playlist-count", 1)
|
||||
@@ -84,35 +84,21 @@ 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
|
||||
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
|
||||
return
|
||||
end
|
||||
@@ -132,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
|
||||
@@ -143,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
|
||||
@@ -157,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
|
||||
@@ -165,11 +151,11 @@ function find_and_add_entries()
|
||||
|
||||
if direction == -1 then
|
||||
if pl_current == 1 then -- never add additional entries in the middle
|
||||
msg.info("Prepending " .. file)
|
||||
mp.msg.info("Prepending " .. file)
|
||||
table.insert(append[-1], 1, filepath)
|
||||
end
|
||||
else
|
||||
msg.info("Adding " .. file)
|
||||
mp.msg.info("Adding " .. file)
|
||||
table.insert(append[1], filepath)
|
||||
end
|
||||
end
|
||||
|
||||
+1
-3
@@ -213,7 +213,6 @@ elements_matroska = (
|
||||
'Tag*, 7373, sub', (
|
||||
'Targets, 63c0, sub', (
|
||||
'TargetTypeValue, 68ca, uint',
|
||||
'TargetType, 63ca, str',
|
||||
'TargetTrackUID, 63c5, uint',
|
||||
'TargetEditionUID, 63c9, uint',
|
||||
'TargetChapterUID, 63c4, uint',
|
||||
@@ -222,8 +221,7 @@ elements_matroska = (
|
||||
'SimpleTag*, 67c8, sub', (
|
||||
'TagName, 45a3, str',
|
||||
'TagLanguage, 447a, str',
|
||||
'TagString, 4487, str',
|
||||
'TagDefault, 4484, uint',
|
||||
'TagString, 4487, str'
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -39,10 +39,6 @@ def apply_plist_template(plist_file, version):
|
||||
for line in fileinput.input(plist_file, inplace=1):
|
||||
print (line.rstrip().replace('${VERSION}', version))
|
||||
|
||||
def create_bundle_symlink(binary_name, symlink_name):
|
||||
os.symlink(os.path.basename(binary_name),
|
||||
os.path.join(target_directory(binary_name), symlink_name))
|
||||
|
||||
def bundle_version():
|
||||
if os.path.exists('VERSION'):
|
||||
x = open('VERSION')
|
||||
@@ -73,8 +69,6 @@ def main():
|
||||
copy_bundle(binary_name)
|
||||
print("> copying binary")
|
||||
copy_binary(binary_name)
|
||||
print("> create bundle symlink")
|
||||
create_bundle_symlink(binary_name, "mpv-bundle")
|
||||
print("> generating Info.plist")
|
||||
apply_plist_template(target_plist(binary_name), version)
|
||||
|
||||
|
||||
@@ -173,7 +173,7 @@
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>mpv-bundle</string>
|
||||
<string>mpv-wrapper.sh</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>icon</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
export MPVBUNDLE="true"
|
||||
|
||||
# set the right args for the user specified standard shell
|
||||
# to load the expected profiles and configs
|
||||
args="-c"
|
||||
case "$SHELL" in
|
||||
*bash) args="-l $args";;
|
||||
*zsh) args="-l -i $args";;
|
||||
esac
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
$SHELL $args "./mpv --player-operation-mode=pseudo-gui"
|
||||
@@ -1 +0,0 @@
|
||||
player-operation-mode=pseudo-gui
|
||||
+6
-2
@@ -91,9 +91,13 @@ class Libav < TravisDepsBuilder
|
||||
:action => :git,
|
||||
:url => "git://git.libav.org/libav.git"
|
||||
},
|
||||
"ffmpeg-stable" => {
|
||||
:action => :stable,
|
||||
:url => 'http://www.ffmpeg.org/releases/ffmpeg-3.2.2.tar.gz'
|
||||
},
|
||||
"ffmpeg-git" => {
|
||||
:action => :git,
|
||||
:url => "https://github.com/FFmpeg/FFmpeg.git"
|
||||
:url => "git://github.com/FFmpeg/FFmpeg.git"
|
||||
}
|
||||
}
|
||||
end
|
||||
@@ -106,7 +110,7 @@ end
|
||||
class LibavOsx < Libav
|
||||
def build_map
|
||||
{
|
||||
"ffmpeg-git" => { :action => :package, :url => 'ffmpeg' },
|
||||
"ffmpeg-stable" => { :action => :package, :url => 'ffmpeg' },
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
+4
-4
@@ -29,11 +29,11 @@ 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 @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 @af = parse_opts('--af=help', '^ ([^\s\:]*)\s*: (.*)');
|
||||
my @vf = parse_opts('--vf=help', '^ ([^\s\:]*)\s*: (.*)');
|
||||
|
||||
my @protos = parse_opts('--list-protocols', '^ ([^\s]*)');
|
||||
|
||||
|
||||
+4
-5
@@ -2,7 +2,6 @@ branches:
|
||||
only:
|
||||
- master
|
||||
- appveyor
|
||||
- ci
|
||||
|
||||
environment:
|
||||
global:
|
||||
@@ -16,13 +15,13 @@ test: off
|
||||
|
||||
install:
|
||||
# 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 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"
|
||||
- C:\msys64\usr\bin\bash -lc "cd \"$APPVEYOR_BUILD_FOLDER\" && exec ./TOOLS/appveyor-install.sh"
|
||||
build_script:
|
||||
- C:\msys64\usr\bin\bash -lc "cd \"$APPVEYOR_BUILD_FOLDER\" && exec ./TOOLS/appveyor-build.sh"
|
||||
on_failure:
|
||||
|
||||
+13
-150
@@ -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);
|
||||
}
|
||||
|
||||
@@ -188,10 +166,10 @@ void mp_aframe_config_copy(struct mp_aframe *dst, struct mp_aframe *src)
|
||||
|
||||
dst->chmap = src->chmap;
|
||||
dst->format = src->format;
|
||||
dst->pts = src->pts;
|
||||
|
||||
mp_aframe_copy_attributes(dst, src);
|
||||
|
||||
dst->av_frame->sample_rate = src->av_frame->sample_rate;
|
||||
if (av_frame_copy_props(dst->av_frame, src->av_frame) < 0)
|
||||
abort();
|
||||
dst->av_frame->format = src->av_frame->format;
|
||||
dst->av_frame->channel_layout = src->av_frame->channel_layout;
|
||||
#if LIBAVUTIL_VERSION_MICRO >= 100
|
||||
@@ -200,21 +178,6 @@ void mp_aframe_config_copy(struct mp_aframe *dst, struct mp_aframe *src)
|
||||
#endif
|
||||
}
|
||||
|
||||
// Copy "soft" attributes from src to dst, excluding things which affect
|
||||
// frame allocation and organization.
|
||||
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;
|
||||
|
||||
if (av_frame_copy_props(dst->av_frame, src->av_frame) < 0)
|
||||
abort();
|
||||
|
||||
dst->av_frame->sample_rate = rate;
|
||||
}
|
||||
|
||||
// Return whether a and b use the same physical audio format. Extra metadata
|
||||
// such as PTS, per-frame signalling, and AVFrame side data is not compared.
|
||||
bool mp_aframe_config_equals(struct mp_aframe *a, struct mp_aframe *b)
|
||||
@@ -320,7 +283,7 @@ bool mp_aframe_set_chmap(struct mp_aframe *frame, struct mp_chmap *in)
|
||||
|
||||
bool mp_aframe_set_rate(struct mp_aframe *frame, int rate)
|
||||
{
|
||||
if (rate < 1 || rate > 10000000)
|
||||
if (rate < 1 && rate > 10000000)
|
||||
return false;
|
||||
frame->av_frame->sample_rate = rate;
|
||||
return true;
|
||||
@@ -339,37 +302,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)
|
||||
{
|
||||
@@ -385,26 +317,6 @@ size_t mp_aframe_get_sstride(struct mp_aframe *frame)
|
||||
(af_fmt_is_planar(format) ? 1 : mp_aframe_get_channels(frame));
|
||||
}
|
||||
|
||||
// Return total number of samples on each plane.
|
||||
int mp_aframe_get_total_plane_samples(struct mp_aframe *frame)
|
||||
{
|
||||
return frame->av_frame->nb_samples *
|
||||
(af_fmt_is_planar(mp_aframe_get_format(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)
|
||||
{
|
||||
@@ -418,38 +330,35 @@ void mp_aframe_skip_samples(struct mp_aframe *f, int samples)
|
||||
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) {
|
||||
@@ -474,52 +383,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;
|
||||
}
|
||||
|
||||
struct mp_aframe_pool {
|
||||
AVBufferPool *avpool;
|
||||
int element_size;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
struct mp_aframe;
|
||||
struct AVFrame;
|
||||
@@ -25,8 +23,6 @@ void mp_aframe_config_copy(struct mp_aframe *dst, struct mp_aframe *src);
|
||||
bool mp_aframe_config_equals(struct mp_aframe *a, struct mp_aframe *b);
|
||||
bool mp_aframe_config_is_valid(struct mp_aframe *frame);
|
||||
|
||||
void mp_aframe_copy_attributes(struct mp_aframe *dst, struct mp_aframe *src);
|
||||
|
||||
uint8_t **mp_aframe_get_data_ro(struct mp_aframe *frame);
|
||||
uint8_t **mp_aframe_get_data_rw(struct mp_aframe *frame);
|
||||
|
||||
@@ -36,32 +32,20 @@ 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);
|
||||
|
||||
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);
|
||||
|
||||
+594
@@ -0,0 +1,594 @@
|
||||
/*
|
||||
* 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 != av_frame_get_channels(avframe))
|
||||
mp_chmap_from_channels(&lavc_chmap, av_frame_get_channels(avframe));
|
||||
#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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
av_frame_set_channels(avframe, 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;
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* 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_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);
|
||||
|
||||
#endif
|
||||
+70
-66
@@ -21,137 +21,141 @@
|
||||
|
||||
#include "common/common.h"
|
||||
|
||||
#include "chmap.h"
|
||||
#include "audio_buffer.h"
|
||||
#include "audio.h"
|
||||
#include "format.h"
|
||||
|
||||
struct mp_audio_buffer {
|
||||
int format;
|
||||
struct mp_chmap channels;
|
||||
int srate;
|
||||
int sstride;
|
||||
int num_planes;
|
||||
uint8_t *data[MP_NUM_CHANNELS];
|
||||
int allocated;
|
||||
int num_samples;
|
||||
struct mp_audio *buffer;
|
||||
};
|
||||
|
||||
struct mp_audio_buffer *mp_audio_buffer_create(void *talloc_ctx)
|
||||
{
|
||||
return talloc_zero(talloc_ctx, struct mp_audio_buffer);
|
||||
struct mp_audio_buffer *ab = talloc(talloc_ctx, struct mp_audio_buffer);
|
||||
*ab = (struct mp_audio_buffer) {
|
||||
.buffer = talloc_zero(ab, struct mp_audio),
|
||||
};
|
||||
return ab;
|
||||
}
|
||||
|
||||
// Reinitialize the buffer, set a new format, drop old data.
|
||||
// The audio data in fmt is not used, only the format.
|
||||
void mp_audio_buffer_reinit(struct mp_audio_buffer *ab, struct mp_audio *fmt)
|
||||
{
|
||||
mp_audio_copy_config(ab->buffer, fmt);
|
||||
mp_audio_realloc(ab->buffer, 1);
|
||||
ab->buffer->samples = 0;
|
||||
}
|
||||
|
||||
void mp_audio_buffer_reinit_fmt(struct mp_audio_buffer *ab, int format,
|
||||
const struct mp_chmap *channels, int srate)
|
||||
{
|
||||
for (int n = 0; n < MP_NUM_CHANNELS; n++)
|
||||
TA_FREEP(&ab->data[n]);
|
||||
ab->format = format;
|
||||
ab->channels = *channels;
|
||||
ab->srate = srate;
|
||||
ab->allocated = 0;
|
||||
ab->num_samples = 0;
|
||||
ab->sstride = af_fmt_to_bytes(ab->format);
|
||||
ab->num_planes = 1;
|
||||
if (af_fmt_is_planar(ab->format)) {
|
||||
ab->num_planes = ab->channels.num;
|
||||
} else {
|
||||
ab->sstride *= ab->channels.num;
|
||||
}
|
||||
struct mp_audio mpa = {0};
|
||||
mp_audio_set_format(&mpa, format);
|
||||
mp_audio_set_channels(&mpa, channels);
|
||||
mpa.rate = srate;
|
||||
mp_audio_buffer_reinit(ab, &mpa);
|
||||
}
|
||||
|
||||
void mp_audio_buffer_get_format(struct mp_audio_buffer *ab,
|
||||
struct mp_audio *out_fmt)
|
||||
{
|
||||
*out_fmt = (struct mp_audio){0};
|
||||
mp_audio_copy_config(out_fmt, ab->buffer);
|
||||
}
|
||||
|
||||
// Make the total size of the internal buffer at least this number of samples.
|
||||
void mp_audio_buffer_preallocate_min(struct mp_audio_buffer *ab, int samples)
|
||||
{
|
||||
if (samples > ab->allocated) {
|
||||
for (int n = 0; n < ab->num_planes; n++) {
|
||||
ab->data[n] = talloc_realloc(ab, ab->data[n], char,
|
||||
ab->sstride * samples);
|
||||
}
|
||||
ab->allocated = samples;
|
||||
}
|
||||
mp_audio_realloc_min(ab->buffer, samples);
|
||||
}
|
||||
|
||||
// Get number of samples that can be written without forcing a resize of the
|
||||
// internal buffer.
|
||||
int mp_audio_buffer_get_write_available(struct mp_audio_buffer *ab)
|
||||
{
|
||||
return ab->allocated - ab->num_samples;
|
||||
return mp_audio_get_allocated_size(ab->buffer) - ab->buffer->samples;
|
||||
}
|
||||
|
||||
// All integer parameters are in samples.
|
||||
// dst and src can overlap.
|
||||
static void copy_planes(struct mp_audio_buffer *ab,
|
||||
uint8_t **dst, int dst_offset,
|
||||
uint8_t **src, int src_offset, int length)
|
||||
// Get a pointer to the end of the buffer (where writing would append). If the
|
||||
// internal buffer is too small for the given number of samples, it's resized.
|
||||
// After writing to the buffer, mp_audio_buffer_finish_write() has to be used
|
||||
// to make the written data part of the readable buffer.
|
||||
void mp_audio_buffer_get_write_buffer(struct mp_audio_buffer *ab, int samples,
|
||||
struct mp_audio *out_buffer)
|
||||
{
|
||||
for (int n = 0; n < ab->num_planes; n++) {
|
||||
memmove((char *)dst[n] + dst_offset * ab->sstride,
|
||||
(char *)src[n] + src_offset * ab->sstride,
|
||||
length * ab->sstride);
|
||||
}
|
||||
assert(samples >= 0);
|
||||
mp_audio_realloc_min(ab->buffer, ab->buffer->samples + samples);
|
||||
*out_buffer = *ab->buffer;
|
||||
out_buffer->samples = ab->buffer->samples + samples;
|
||||
mp_audio_skip_samples(out_buffer, ab->buffer->samples);
|
||||
}
|
||||
|
||||
void mp_audio_buffer_finish_write(struct mp_audio_buffer *ab, int samples)
|
||||
{
|
||||
assert(samples >= 0 && samples <= mp_audio_buffer_get_write_available(ab));
|
||||
ab->buffer->samples += samples;
|
||||
}
|
||||
|
||||
// Append data to the end of the buffer.
|
||||
// If the buffer is not large enough, it is transparently resized.
|
||||
void mp_audio_buffer_append(struct mp_audio_buffer *ab, void **ptr, int samples)
|
||||
// For now always copies the data.
|
||||
void mp_audio_buffer_append(struct mp_audio_buffer *ab, struct mp_audio *mpa)
|
||||
{
|
||||
mp_audio_buffer_preallocate_min(ab, ab->num_samples + samples);
|
||||
copy_planes(ab, ab->data, ab->num_samples, (uint8_t **)ptr, 0, samples);
|
||||
ab->num_samples += samples;
|
||||
int offset = ab->buffer->samples;
|
||||
ab->buffer->samples += mpa->samples;
|
||||
mp_audio_realloc_min(ab->buffer, ab->buffer->samples);
|
||||
mp_audio_copy(ab->buffer, offset, mpa, 0, mpa->samples);
|
||||
}
|
||||
|
||||
// Prepend silence to the start of the buffer.
|
||||
void mp_audio_buffer_prepend_silence(struct mp_audio_buffer *ab, int samples)
|
||||
{
|
||||
assert(samples >= 0);
|
||||
mp_audio_buffer_preallocate_min(ab, ab->num_samples + samples);
|
||||
copy_planes(ab, ab->data, samples, ab->data, 0, ab->num_samples);
|
||||
ab->num_samples += samples;
|
||||
for (int n = 0; n < ab->num_planes; n++)
|
||||
af_fill_silence(ab->data[n], samples * ab->sstride, ab->format);
|
||||
int oldlen = ab->buffer->samples;
|
||||
ab->buffer->samples += samples;
|
||||
mp_audio_realloc_min(ab->buffer, ab->buffer->samples);
|
||||
mp_audio_copy(ab->buffer, samples, ab->buffer, 0, oldlen);
|
||||
mp_audio_fill_silence(ab->buffer, 0, samples);
|
||||
}
|
||||
|
||||
void mp_audio_buffer_duplicate(struct mp_audio_buffer *ab, int samples)
|
||||
{
|
||||
assert(samples >= 0 && samples <= ab->num_samples);
|
||||
mp_audio_buffer_preallocate_min(ab, ab->num_samples + samples);
|
||||
copy_planes(ab, ab->data, ab->num_samples,
|
||||
ab->data, ab->num_samples - samples, samples);
|
||||
ab->num_samples += samples;
|
||||
assert(samples >= 0 && samples <= ab->buffer->samples);
|
||||
int oldlen = ab->buffer->samples;
|
||||
ab->buffer->samples += samples;
|
||||
mp_audio_realloc_min(ab->buffer, ab->buffer->samples);
|
||||
mp_audio_copy(ab->buffer, oldlen, ab->buffer, oldlen - samples, samples);
|
||||
}
|
||||
|
||||
// Get the start of the current readable buffer.
|
||||
void mp_audio_buffer_peek(struct mp_audio_buffer *ab, uint8_t ***ptr,
|
||||
int *samples)
|
||||
void mp_audio_buffer_peek(struct mp_audio_buffer *ab, struct mp_audio *out_mpa)
|
||||
{
|
||||
*ptr = ab->data;
|
||||
*samples = ab->num_samples;
|
||||
*out_mpa = *ab->buffer;
|
||||
}
|
||||
|
||||
// Skip leading samples. (Used with mp_audio_buffer_peek() to read data.)
|
||||
void mp_audio_buffer_skip(struct mp_audio_buffer *ab, int samples)
|
||||
{
|
||||
assert(samples >= 0 && samples <= ab->num_samples);
|
||||
copy_planes(ab, ab->data, 0, ab->data, samples, ab->num_samples - samples);
|
||||
ab->num_samples -= samples;
|
||||
assert(samples >= 0 && samples <= ab->buffer->samples);
|
||||
mp_audio_copy(ab->buffer, 0, ab->buffer, samples,
|
||||
ab->buffer->samples - samples);
|
||||
ab->buffer->samples -= samples;
|
||||
}
|
||||
|
||||
void mp_audio_buffer_clear(struct mp_audio_buffer *ab)
|
||||
{
|
||||
ab->num_samples = 0;
|
||||
ab->buffer->samples = 0;
|
||||
}
|
||||
|
||||
// Return number of buffered audio samples
|
||||
int mp_audio_buffer_samples(struct mp_audio_buffer *ab)
|
||||
{
|
||||
return ab->num_samples;
|
||||
return ab->buffer->samples;
|
||||
}
|
||||
|
||||
// Return amount of buffered audio in seconds.
|
||||
double mp_audio_buffer_seconds(struct mp_audio_buffer *ab)
|
||||
{
|
||||
return ab->num_samples / (double)ab->srate;
|
||||
return ab->buffer->samples / (double)ab->buffer->rate;
|
||||
}
|
||||
@@ -19,18 +19,24 @@
|
||||
#define MP_AUDIO_BUFFER_H
|
||||
|
||||
struct mp_audio_buffer;
|
||||
struct mp_audio;
|
||||
struct mp_chmap;
|
||||
|
||||
struct mp_audio_buffer *mp_audio_buffer_create(void *talloc_ctx);
|
||||
void mp_audio_buffer_reinit(struct mp_audio_buffer *ab, struct mp_audio *fmt);
|
||||
void mp_audio_buffer_reinit_fmt(struct mp_audio_buffer *ab, int format,
|
||||
const struct mp_chmap *channels, int srate);
|
||||
void mp_audio_buffer_get_format(struct mp_audio_buffer *ab,
|
||||
struct mp_audio *out_fmt);
|
||||
void mp_audio_buffer_preallocate_min(struct mp_audio_buffer *ab, int samples);
|
||||
int mp_audio_buffer_get_write_available(struct mp_audio_buffer *ab);
|
||||
void mp_audio_buffer_append(struct mp_audio_buffer *ab, void **ptr, int samples);
|
||||
void mp_audio_buffer_get_write_buffer(struct mp_audio_buffer *ab, int minsamples,
|
||||
struct mp_audio *out_buffer);
|
||||
void mp_audio_buffer_finish_write(struct mp_audio_buffer *ab, int samples);
|
||||
void mp_audio_buffer_append(struct mp_audio_buffer *ab, struct mp_audio *mpa);
|
||||
void mp_audio_buffer_prepend_silence(struct mp_audio_buffer *ab, int samples);
|
||||
void mp_audio_buffer_duplicate(struct mp_audio_buffer *ab, int samples);
|
||||
void mp_audio_buffer_peek(struct mp_audio_buffer *ab, uint8_t ***ptr,
|
||||
int *samples);
|
||||
void mp_audio_buffer_peek(struct mp_audio_buffer *ab, struct mp_audio *out_mpa);
|
||||
void mp_audio_buffer_skip(struct mp_audio_buffer *ab, int samples);
|
||||
void mp_audio_buffer_clear(struct mp_audio_buffer *ab);
|
||||
int mp_audio_buffer_samples(struct mp_audio_buffer *ab);
|
||||
|
||||
@@ -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 */
|
||||
+51
-79
@@ -27,31 +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/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;
|
||||
bool eof_returned;
|
||||
|
||||
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;
|
||||
@@ -76,24 +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 = da->global->opts;
|
||||
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);
|
||||
@@ -122,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);
|
||||
@@ -132,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->eof_returned = false;
|
||||
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 bool 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;
|
||||
@@ -184,7 +190,7 @@ static bool send_packet(struct mp_filter *da, struct demux_packet *mpkt)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool 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;
|
||||
@@ -194,8 +200,7 @@ static bool 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);
|
||||
control(da, ADCTRL_RESET, NULL);
|
||||
return false;
|
||||
} else if (ret < 0 && ret != AVERROR(EAGAIN)) {
|
||||
MP_ERR(da, "Error decoding audio.\n");
|
||||
@@ -215,8 +220,8 @@ static bool receive_frame(struct mp_filter *da, struct mp_frame *out)
|
||||
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,57 +257,24 @@ static bool receive_frame(struct mp_filter *da, struct mp_frame *out)
|
||||
priv->trim_samples -= trim;
|
||||
}
|
||||
|
||||
*out = MAKE_FRAME(MP_FRAME_AUDIO, mpframe);
|
||||
*out = mpframe;
|
||||
|
||||
av_frame_unref(priv->avframe);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void process(struct mp_filter *ad)
|
||||
{
|
||||
struct priv *priv = ad->priv;
|
||||
|
||||
lavc_process(ad, &priv->eof_returned, 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;
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
+76
-85
@@ -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;
|
||||
|
||||
@@ -261,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)
|
||||
@@ -304,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[] = {
|
||||
@@ -378,49 +401,17 @@ struct mp_decoder_list *select_spdif_codec(const char *codec, const char *pref)
|
||||
const char *suffix_name = dts_hd_allowed ? "dts_hd" : codec;
|
||||
char name[80];
|
||||
snprintf(name, sizeof(name), "spdif_%s", suffix_name);
|
||||
mp_add_decoder(list, codec, name,
|
||||
mp_add_decoder(list, "spdif", codec, name,
|
||||
"libavformat/spdifenc audio pass-through decoder");
|
||||
return list;
|
||||
}
|
||||
|
||||
static const struct mp_filter_info ad_spdif_filter = {
|
||||
.name = "ad_spdif",
|
||||
.priv_size = sizeof(struct spdifContext),
|
||||
.process = process,
|
||||
.destroy = destroy,
|
||||
};
|
||||
|
||||
static struct mp_decoder *create(struct mp_filter *parent,
|
||||
struct mp_codec_params *codec,
|
||||
const char *decoder)
|
||||
{
|
||||
struct mp_filter *da = mp_filter_create(parent, &ad_spdif_filter);
|
||||
if (!da)
|
||||
return NULL;
|
||||
|
||||
mp_filter_add_pin(da, MP_PIN_IN, "in");
|
||||
mp_filter_add_pin(da, MP_PIN_OUT, "out");
|
||||
|
||||
da->log = mp_log_new(da, parent->log, NULL);
|
||||
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
spdif_ctx->log = da->log;
|
||||
spdif_ctx->pool = mp_aframe_pool_create(spdif_ctx);
|
||||
spdif_ctx->public.f = da;
|
||||
|
||||
if (strcmp(decoder, "spdif_dts_hd") == 0)
|
||||
spdif_ctx->use_dts_hd = true;
|
||||
|
||||
spdif_ctx->codec_id = mp_codec_to_av_codec_id(codec->codec);
|
||||
|
||||
|
||||
if (spdif_ctx->codec_id == AV_CODEC_ID_NONE) {
|
||||
talloc_free(da);
|
||||
return NULL;
|
||||
}
|
||||
return &spdif_ctx->public;
|
||||
}
|
||||
|
||||
const struct mp_decoder_fns ad_spdif = {
|
||||
.create = create,
|
||||
const struct ad_functions ad_spdif = {
|
||||
.name = "spdif",
|
||||
.add_decoders = NULL,
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.control = control,
|
||||
.send_packet = send_packet,
|
||||
.receive_frame = receive_frame,
|
||||
};
|
||||
@@ -0,0 +1,287 @@
|
||||
/*
|
||||
* 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 "stream/stream.h"
|
||||
#include "demux/demux.h"
|
||||
|
||||
#include "demux/stheader.h"
|
||||
|
||||
#include "dec_audio.h"
|
||||
#include "ad.h"
|
||||
#include "audio/format.h"
|
||||
|
||||
#include "audio/filter/af.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);
|
||||
}
|
||||
|
||||
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 && da->packet->new_segment) {
|
||||
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;
|
||||
|
||||
new_segment->new_segment = false;
|
||||
|
||||
da->packet = new_segment;
|
||||
da->current_state = DATA_AGAIN;
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch an audio frame decoded with audio_work(). Returns one of:
|
||||
// DATA_OK: *out_frame is set to a new image
|
||||
// DATA_WAIT: waiting for demuxer; will receive a wakeup signal
|
||||
// DATA_EOF: end of file, no more frames to be expected
|
||||
// DATA_AGAIN: dropped frame or something similar
|
||||
int audio_get_frame(struct dec_audio *da, struct mp_aframe **out_frame)
|
||||
{
|
||||
*out_frame = NULL;
|
||||
if (da->current_frame) {
|
||||
*out_frame = da->current_frame;
|
||||
da->current_frame = NULL;
|
||||
return DATA_OK;
|
||||
}
|
||||
if (da->current_state == DATA_OK)
|
||||
return DATA_AGAIN;
|
||||
return da->current_state;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MPLAYER_DEC_AUDIO_H
|
||||
#define MPLAYER_DEC_AUDIO_H
|
||||
|
||||
#include "audio/chmap.h"
|
||||
#include "audio/aframe.h"
|
||||
#include "demux/demux.h"
|
||||
#include "demux/stheader.h"
|
||||
|
||||
struct mp_decoder_list;
|
||||
|
||||
struct dec_audio {
|
||||
struct mp_log *log;
|
||||
struct MPOpts *opts;
|
||||
struct mpv_global *global;
|
||||
const struct ad_functions *ad_driver;
|
||||
struct sh_stream *header;
|
||||
struct mp_codec_params *codec;
|
||||
char *decoder_desc;
|
||||
|
||||
bool try_spdif;
|
||||
|
||||
struct mp_recorder_sink *recorder_sink;
|
||||
|
||||
// For free use by the ad_driver
|
||||
void *priv;
|
||||
|
||||
// Strictly internal (dec_audio.c).
|
||||
|
||||
double pts; // endpts of previous frame
|
||||
double start, end;
|
||||
struct demux_packet *packet;
|
||||
struct demux_packet *new_segment;
|
||||
struct mp_aframe *current_frame;
|
||||
int current_state;
|
||||
};
|
||||
|
||||
struct mp_decoder_list *audio_decoder_list(void);
|
||||
int audio_init_best_codec(struct dec_audio *d_audio);
|
||||
void audio_uninit(struct dec_audio *d_audio);
|
||||
|
||||
void audio_work(struct dec_audio *d_audio);
|
||||
int audio_get_frame(struct dec_audio *d_audio, struct mp_aframe **out_frame);
|
||||
|
||||
void audio_reset_decoding(struct dec_audio *d_audio);
|
||||
|
||||
// ad_spdif.c
|
||||
struct mp_decoder_list *select_spdif_codec(const char *codec, const char *pref);
|
||||
|
||||
#endif /* MPLAYER_DEC_AUDIO_H */
|
||||
@@ -0,0 +1,829 @@
|
||||
/*
|
||||
* 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_channels;
|
||||
extern const struct af_info af_info_format;
|
||||
extern const struct af_info af_info_volume;
|
||||
extern const struct af_info af_info_equalizer;
|
||||
extern const struct af_info af_info_pan;
|
||||
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_bs2b;
|
||||
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_channels,
|
||||
&af_info_format,
|
||||
&af_info_volume,
|
||||
&af_info_equalizer,
|
||||
&af_info_pan,
|
||||
&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,
|
||||
.replaygain_data = s->replaygain_data,
|
||||
.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;
|
||||
return s;
|
||||
}
|
||||
|
||||
void af_destroy(struct af_stream *s)
|
||||
{
|
||||
af_uninit(s);
|
||||
talloc_free(s);
|
||||
}
|
||||
|
||||
/* Initialize the stream "s". This function creates a new filter list
|
||||
if necessary according to the values set in input and output. Input
|
||||
and output should contain the format of the current movie and the
|
||||
format of the preferred output respectively. The function is
|
||||
reentrant i.e. if called with an already initialized stream the
|
||||
stream will be reinitialized.
|
||||
If one of the preferred output parameters is 0 the one that needs
|
||||
no conversion is used (i.e. the output format in the last filter).
|
||||
The return value is 0 if success and -1 if failure */
|
||||
int af_init(struct af_stream *s)
|
||||
{
|
||||
// Precaution in case caller is misbehaving
|
||||
mp_audio_set_null_data(&s->input);
|
||||
mp_audio_set_null_data(&s->output);
|
||||
|
||||
// Check if this is the first call
|
||||
if (s->first->next == s->last) {
|
||||
// Add all filters in the list (if there are any)
|
||||
struct m_obj_settings *list = s->opts->af_settings;
|
||||
for (int i = 0; list && list[i].name; i++) {
|
||||
if (!list[i].enabled)
|
||||
continue;
|
||||
struct af_instance *af =
|
||||
af_prepend(s, s->last, list[i].name, list[i].attribs);
|
||||
if (!af) {
|
||||
af_uninit(s);
|
||||
s->initialized = -1;
|
||||
return -1;
|
||||
}
|
||||
af->label = talloc_strdup(af, list[i].label);
|
||||
}
|
||||
}
|
||||
|
||||
if (af_reinit(s) != AF_OK) {
|
||||
// Something is stuffed audio out will not work
|
||||
MP_ERR(s, "Could not create audio filter chain.\n");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Add filter during execution. This function adds the filter "name"
|
||||
to the stream s. The filter will be inserted somewhere nice in the
|
||||
list of filters. The return value is a pointer to the new filter,
|
||||
If the filter couldn't be added the return value is NULL. */
|
||||
struct af_instance *af_add(struct af_stream *s, char *name, char *label,
|
||||
char **args)
|
||||
{
|
||||
assert(label);
|
||||
|
||||
if (af_find_by_label(s, label))
|
||||
return NULL;
|
||||
|
||||
struct af_instance *new = af_prepend(s, s->last, name, args);
|
||||
if (!new)
|
||||
return NULL;
|
||||
new->label = talloc_strdup(new, label);
|
||||
|
||||
// Reinitialize the filter list
|
||||
if (af_reinit(s) != AF_OK) {
|
||||
af_remove_by_label(s, label);
|
||||
return NULL;
|
||||
}
|
||||
return af_find_by_label(s, label);
|
||||
}
|
||||
|
||||
struct af_instance *af_find_by_label(struct af_stream *s, char *label)
|
||||
{
|
||||
for (struct af_instance *af = s->first; af; af = af->next) {
|
||||
if (af->label && strcmp(af->label, label) == 0)
|
||||
return af;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Remove the first filter that matches this name. Return number of filters
|
||||
* removed (0, 1), or a negative error code if reinit after removing failed.
|
||||
*/
|
||||
int af_remove_by_label(struct af_stream *s, char *label)
|
||||
{
|
||||
struct af_instance *af = af_find_by_label(s, label);
|
||||
if (!af)
|
||||
return 0;
|
||||
af_remove(s, af);
|
||||
if (af_reinit(s) != AF_OK) {
|
||||
af_uninit(s);
|
||||
af_init(s);
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Calculate the total delay [seconds of output] caused by the filters */
|
||||
double af_calc_delay(struct af_stream *s)
|
||||
{
|
||||
struct af_instance *af = s->first;
|
||||
double delay = 0.0;
|
||||
while (af) {
|
||||
delay += af->delay;
|
||||
for (int n = 0; n < af->num_out_queued; n++)
|
||||
delay += af->out_queued[n]->samples / (double)af->data->rate;
|
||||
af = af->next;
|
||||
}
|
||||
return delay;
|
||||
}
|
||||
|
||||
/* Send control to all filters, starting with the last until one accepts the
|
||||
* command with AF_OK. Return the accepting filter. */
|
||||
struct af_instance *af_control_any_rev(struct af_stream *s, int cmd, void *arg)
|
||||
{
|
||||
int res = AF_UNKNOWN;
|
||||
struct af_instance *filt = s->last;
|
||||
while (filt) {
|
||||
res = filt->control(filt, cmd, arg);
|
||||
if (res == AF_OK)
|
||||
return filt;
|
||||
filt = filt->prev;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Send control to all filters. Never stop, even if a filter returns AF_OK. */
|
||||
void af_control_all(struct af_stream *s, int cmd, void *arg)
|
||||
{
|
||||
for (struct af_instance *af = s->first; af; af = af->next)
|
||||
af->control(af, cmd, arg);
|
||||
}
|
||||
|
||||
int af_control_by_label(struct af_stream *s, int cmd, void *arg, bstr label)
|
||||
{
|
||||
char *label_str = bstrdup0(NULL, label);
|
||||
struct af_instance *cur = af_find_by_label(s, label_str);
|
||||
talloc_free(label_str);
|
||||
if (cur) {
|
||||
return cur->control ? cur->control(cur, cmd, arg) : CONTROL_NA;
|
||||
} else {
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
int af_send_command(struct af_stream *s, char *label, char *cmd, char *arg)
|
||||
{
|
||||
char *args[2] = {cmd, arg};
|
||||
if (strcmp(label, "all") == 0) {
|
||||
af_control_all(s, AF_CONTROL_COMMAND, args);
|
||||
return 0;
|
||||
} else {
|
||||
return af_control_by_label(s, AF_CONTROL_COMMAND, args, bstr0(label));
|
||||
}
|
||||
}
|
||||
|
||||
// Used by filters to add a filtered frame to the output queue.
|
||||
// Ownership of frame is transferred from caller to the filter chain.
|
||||
void af_add_output_frame(struct af_instance *af, struct mp_audio *frame)
|
||||
{
|
||||
if (frame) {
|
||||
assert(mp_audio_config_equals(&af->fmt_out, frame));
|
||||
MP_TARRAY_APPEND(af, af->out_queued, af->num_out_queued, frame);
|
||||
}
|
||||
}
|
||||
|
||||
static bool af_has_output_frame(struct af_instance *af)
|
||||
{
|
||||
if (!af->num_out_queued && af->filter_out) {
|
||||
if (af->filter_out(af) < 0)
|
||||
MP_ERR(af, "Error filtering frame.\n");
|
||||
}
|
||||
return af->num_out_queued > 0;
|
||||
}
|
||||
|
||||
static struct mp_audio *af_dequeue_output_frame(struct af_instance *af)
|
||||
{
|
||||
struct mp_audio *res = NULL;
|
||||
if (af_has_output_frame(af)) {
|
||||
res = af->out_queued[0];
|
||||
MP_TARRAY_REMOVE_AT(af->out_queued, af->num_out_queued, 0);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static void read_remaining(struct af_instance *af)
|
||||
{
|
||||
int num_frames;
|
||||
do {
|
||||
num_frames = af->num_out_queued;
|
||||
if (!af->filter_out || af->filter_out(af) < 0)
|
||||
break;
|
||||
} while (num_frames != af->num_out_queued);
|
||||
}
|
||||
|
||||
static int af_do_filter(struct af_instance *af, struct mp_audio *frame)
|
||||
{
|
||||
if (frame)
|
||||
assert(mp_audio_config_equals(&af->fmt_in, frame));
|
||||
int r = af->filter_frame(af, frame);
|
||||
if (r < 0)
|
||||
MP_ERR(af, "Error filtering frame.\n");
|
||||
return r;
|
||||
}
|
||||
|
||||
// Input a frame into the filter chain. Ownership of frame is transferred.
|
||||
// Return >= 0 on success, < 0 on failure (even if output frames were produced)
|
||||
int af_filter_frame(struct af_stream *s, struct mp_audio *frame)
|
||||
{
|
||||
assert(frame);
|
||||
if (s->initialized < 1) {
|
||||
talloc_free(frame);
|
||||
return -1;
|
||||
}
|
||||
return af_do_filter(s->first, frame);
|
||||
}
|
||||
|
||||
// Output the next queued frame (if any) from the full filter chain.
|
||||
// The frame can be retrieved with af_read_output_frame().
|
||||
// eof: if set, assume there's no more input i.e. af_filter_frame() will
|
||||
// not be called (until reset) - flush all internally delayed frames
|
||||
// returns: -1: error, 0: no output, 1: output available
|
||||
int af_output_frame(struct af_stream *s, bool eof)
|
||||
{
|
||||
if (s->last->num_out_queued)
|
||||
return 1;
|
||||
if (s->initialized < 1)
|
||||
return -1;
|
||||
while (1) {
|
||||
struct af_instance *last = NULL;
|
||||
for (struct af_instance * cur = s->first; cur; cur = cur->next) {
|
||||
// Flush remaining frames on EOF, but do that only if the previous
|
||||
// filters have been flushed (i.e. they have no more output).
|
||||
if (eof && !last) {
|
||||
read_remaining(cur);
|
||||
int r = af_do_filter(cur, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
if (af_has_output_frame(cur))
|
||||
last = cur;
|
||||
}
|
||||
if (!last)
|
||||
return 0;
|
||||
if (!last->next)
|
||||
return 1;
|
||||
int r = af_do_filter(last->next, af_dequeue_output_frame(last));
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
struct mp_audio *af_read_output_frame(struct af_stream *s)
|
||||
{
|
||||
if (!s->last->num_out_queued)
|
||||
af_output_frame(s, false);
|
||||
return af_dequeue_output_frame(s->last);
|
||||
}
|
||||
|
||||
void af_unread_output_frame(struct af_stream *s, struct mp_audio *frame)
|
||||
{
|
||||
struct af_instance *af = s->last;
|
||||
MP_TARRAY_INSERT_AT(af, af->out_queued, af->num_out_queued, 0, frame);
|
||||
}
|
||||
|
||||
// Make sure the caller can change data referenced by the frame.
|
||||
// Return negative error code on failure (i.e. you can't write).
|
||||
int af_make_writeable(struct af_instance *af, struct mp_audio *frame)
|
||||
{
|
||||
return mp_audio_pool_make_writeable(af->out_pool, frame);
|
||||
}
|
||||
|
||||
void af_seek_reset(struct af_stream *s)
|
||||
{
|
||||
af_control_all(s, AF_CONTROL_RESET, NULL);
|
||||
af_chain_forget_frames(s);
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MPLAYER_AF_H
|
||||
#define MPLAYER_AF_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "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 replaygain_data *replaygain_data;
|
||||
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 replaygain_data *replaygain_data;
|
||||
};
|
||||
|
||||
// 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_VOLUME,
|
||||
AF_CONTROL_SET_PAN_LEVEL,
|
||||
AF_CONTROL_SET_PAN_NOUT,
|
||||
AF_CONTROL_SET_PAN_BALANCE,
|
||||
AF_CONTROL_GET_PAN_BALANCE,
|
||||
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 */
|
||||
@@ -0,0 +1,255 @@
|
||||
/*
|
||||
* Audio filter that adds and removes channels, according to the
|
||||
* command line parameter channels. It is stupid and can only add
|
||||
* silence or copy channels, not mix or filter.
|
||||
*
|
||||
* Original author: Anders
|
||||
*
|
||||
* 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 "common/common.h"
|
||||
#include "af.h"
|
||||
|
||||
#define FR 0
|
||||
#define TO 1
|
||||
|
||||
typedef struct af_channels_s{
|
||||
int route[AF_NCH][2];
|
||||
int nch, nr;
|
||||
int router;
|
||||
char *routes;
|
||||
}af_channels_t;
|
||||
|
||||
// Local function for copying data
|
||||
static void copy(struct af_instance *af, void* in, void* out,
|
||||
int ins, int inos,int outs, int outos, int len, int bps)
|
||||
{
|
||||
switch(bps){
|
||||
case 1:{
|
||||
int8_t* tin = (int8_t*)in;
|
||||
int8_t* tout = (int8_t*)out;
|
||||
tin += inos;
|
||||
tout += outos;
|
||||
len = len/ins;
|
||||
while(len--){
|
||||
*tout=*tin;
|
||||
tin +=ins;
|
||||
tout+=outs;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 2:{
|
||||
int16_t* tin = (int16_t*)in;
|
||||
int16_t* tout = (int16_t*)out;
|
||||
tin += inos;
|
||||
tout += outos;
|
||||
len = len/(2*ins);
|
||||
while(len--){
|
||||
*tout=*tin;
|
||||
tin +=ins;
|
||||
tout+=outs;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 3:{
|
||||
int8_t* tin = (int8_t*)in;
|
||||
int8_t* tout = (int8_t*)out;
|
||||
tin += 3 * inos;
|
||||
tout += 3 * outos;
|
||||
len = len / ( 3 * ins);
|
||||
while (len--) {
|
||||
tout[0] = tin[0];
|
||||
tout[1] = tin[1];
|
||||
tout[2] = tin[2];
|
||||
tin += 3 * ins;
|
||||
tout += 3 * outs;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 4:{
|
||||
int32_t* tin = (int32_t*)in;
|
||||
int32_t* tout = (int32_t*)out;
|
||||
tin += inos;
|
||||
tout += outos;
|
||||
len = len/(4*ins);
|
||||
while(len--){
|
||||
*tout=*tin;
|
||||
tin +=ins;
|
||||
tout+=outs;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 8:{
|
||||
int64_t* tin = (int64_t*)in;
|
||||
int64_t* tout = (int64_t*)out;
|
||||
tin += inos;
|
||||
tout += outos;
|
||||
len = len/(8*ins);
|
||||
while(len--){
|
||||
*tout=*tin;
|
||||
tin +=ins;
|
||||
tout+=outs;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
MP_ERR(af, "Unsupported number of bytes/sample: %i"
|
||||
" please report this error on the MPlayer mailing list. \n",bps);
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the routes are sane
|
||||
static int check_routes(struct af_instance *af, int nin, int nout)
|
||||
{
|
||||
af_channels_t* s = af->priv;
|
||||
int i;
|
||||
if((s->nr < 1) || (s->nr > AF_NCH)){
|
||||
MP_ERR(af, "The number of routing pairs must be"
|
||||
" between 1 and %i. Current value is %i\n",AF_NCH,s->nr);
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
for(i=0;i<s->nr;i++){
|
||||
if((s->route[i][FR] >= nin) || (s->route[i][TO] >= nout)){
|
||||
MP_ERR(af, "Invalid routing in pair nr. %i.\n", i);
|
||||
return AF_ERROR;
|
||||
}
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
// Initialization and runtime control
|
||||
static int control(struct af_instance* af, int cmd, void* arg)
|
||||
{
|
||||
af_channels_t* s = af->priv;
|
||||
switch(cmd){
|
||||
case AF_CONTROL_REINIT: ;
|
||||
|
||||
struct mp_chmap chmap;
|
||||
mp_chmap_set_unknown(&chmap, s->nch);
|
||||
mp_audio_set_channels(af->data, &chmap);
|
||||
|
||||
// Set default channel assignment
|
||||
if(!s->router){
|
||||
int i;
|
||||
// Make sure this filter isn't redundant
|
||||
if(af->data->nch == ((struct mp_audio*)arg)->nch)
|
||||
return AF_DETACH;
|
||||
|
||||
// If mono: fake stereo
|
||||
if(((struct mp_audio*)arg)->nch == 1){
|
||||
s->nr = MPMIN(af->data->nch,2);
|
||||
for(i=0;i<s->nr;i++){
|
||||
s->route[i][FR] = 0;
|
||||
s->route[i][TO] = i;
|
||||
}
|
||||
}
|
||||
else{
|
||||
s->nr = MPMIN(af->data->nch, ((struct mp_audio*)arg)->nch);
|
||||
for(i=0;i<s->nr;i++){
|
||||
s->route[i][FR] = i;
|
||||
s->route[i][TO] = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
af->data->rate = ((struct mp_audio*)arg)->rate;
|
||||
mp_audio_force_interleaved_format((struct mp_audio*)arg);
|
||||
mp_audio_set_format(af->data, ((struct mp_audio*)arg)->format);
|
||||
return check_routes(af,((struct mp_audio*)arg)->nch,af->data->nch);
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static int filter_frame(struct af_instance *af, struct mp_audio *c)
|
||||
{
|
||||
af_channels_t* s = af->priv;
|
||||
int i;
|
||||
|
||||
if (!c)
|
||||
return 0;
|
||||
|
||||
struct mp_audio *l = mp_audio_pool_get(af->out_pool, &af->fmt_out, c->samples);
|
||||
if (!l) {
|
||||
talloc_free(c);
|
||||
return -1;
|
||||
}
|
||||
mp_audio_copy_attributes(l, c);
|
||||
|
||||
// Reset unused channels
|
||||
memset(l->planes[0],0,mp_audio_psize(c) / c->nch * l->nch);
|
||||
|
||||
if(AF_OK == check_routes(af,c->nch,l->nch))
|
||||
for(i=0;i<s->nr;i++)
|
||||
copy(af, c->planes[0],l->planes[0],c->nch,s->route[i][FR],
|
||||
l->nch,s->route[i][TO],mp_audio_psize(c),c->bps);
|
||||
|
||||
talloc_free(c);
|
||||
af_add_output_frame(af, l);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Allocate memory and set function pointers
|
||||
static int af_open(struct af_instance* af){
|
||||
af->control=control;
|
||||
af->filter_frame = filter_frame;
|
||||
af_channels_t *s = af->priv;
|
||||
|
||||
MP_WARN(af, "This filter is deprecated (no replacement).\n");
|
||||
|
||||
// If router scan commandline for routing pairs
|
||||
if(s->routes && s->routes[0]){
|
||||
char* cp = s->routes;
|
||||
int ch = 0;
|
||||
// Scan for pairs on commandline
|
||||
do {
|
||||
int n = 0;
|
||||
if (ch >= AF_NCH) {
|
||||
MP_FATAL(af, "Can't have more than %d routes.\n", AF_NCH);
|
||||
return AF_ERROR;
|
||||
}
|
||||
sscanf(cp, "%i-%i%n" ,&s->route[ch][FR], &s->route[ch][TO], &n);
|
||||
MP_VERBOSE(af, "Routing from channel %i to"
|
||||
" channel %i\n",s->route[ch][FR],s->route[ch][TO]);
|
||||
cp = &cp[n];
|
||||
ch++;
|
||||
} while(*cp == ',' && *(cp++));
|
||||
s->nr = ch;
|
||||
if (s->nr > 0)
|
||||
s->router = 1;
|
||||
}
|
||||
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT af_channels_t
|
||||
const struct af_info af_info_channels = {
|
||||
.info = "Insert or remove channels",
|
||||
.name = "channels",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(af_channels_t),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_INTRANGE("nch", nch, 0, 1, AF_NCH, OPTDEF_INT(2)),
|
||||
OPT_STRING("routes", routes, 0),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* Equalizer filter, implementation of a 10 band time domain graphic
|
||||
* equalizer using IIR filters. The IIR filters are implemented using a
|
||||
* Direct Form II approach, but has been modified (b1 == 0 always) to
|
||||
* save computation.
|
||||
*
|
||||
* Copyright (C) 2001 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/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "af.h"
|
||||
|
||||
#define L 2 // Storage for filter taps
|
||||
#define KM 10 // Max number of bands
|
||||
|
||||
#define Q 1.2247449 /* Q value for band-pass filters 1.2247=(3/2)^(1/2)
|
||||
gives 4dB suppression @ Fc*2 and Fc/2 */
|
||||
|
||||
/* Center frequencies for band-pass filters
|
||||
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
|
||||
*/
|
||||
#define CF {31.25,62.5,125,250,500,1000,2000,4000,8000,16000}
|
||||
|
||||
// Maximum and minimum gain for the bands
|
||||
#define G_MAX +12.0
|
||||
#define G_MIN -12.0
|
||||
|
||||
// Data for specific instances of this filter
|
||||
typedef struct af_equalizer_s
|
||||
{
|
||||
float a[KM][L]; // A weights
|
||||
float b[KM][L]; // B weights
|
||||
float wq[AF_NCH][KM][L]; // Circular buffer for W data
|
||||
float g[AF_NCH][KM]; // Gain factor for each channel and band
|
||||
int K; // Number of used eq bands
|
||||
int channels; // Number of channels
|
||||
float gain_factor; // applied at output to avoid clipping
|
||||
double p[KM];
|
||||
} af_equalizer_t;
|
||||
|
||||
// 2nd order Band-pass Filter design
|
||||
static void bp2(float* a, float* b, float fc, float q){
|
||||
double th= 2.0 * M_PI * fc;
|
||||
double C = (1.0 - tan(th*q/2.0))/(1.0 + tan(th*q/2.0));
|
||||
|
||||
a[0] = (1.0 + C) * cos(th);
|
||||
a[1] = -1 * C;
|
||||
|
||||
b[0] = (1.0 - C)/2.0;
|
||||
b[1] = -1.0050;
|
||||
}
|
||||
|
||||
// Initialization and runtime control
|
||||
static int control(struct af_instance* af, int cmd, void* arg)
|
||||
{
|
||||
af_equalizer_t* s = (af_equalizer_t*)af->priv;
|
||||
|
||||
switch(cmd){
|
||||
case AF_CONTROL_REINIT:{
|
||||
int k =0, i =0;
|
||||
float F[KM] = CF;
|
||||
|
||||
s->gain_factor=0.0;
|
||||
|
||||
// Sanity check
|
||||
if(!arg) return AF_ERROR;
|
||||
|
||||
mp_audio_copy_config(af->data, (struct mp_audio*)arg);
|
||||
mp_audio_set_format(af->data, AF_FORMAT_FLOAT);
|
||||
|
||||
// Calculate number of active filters
|
||||
s->K=KM;
|
||||
while(F[s->K-1] > (float)af->data->rate/2.2)
|
||||
s->K--;
|
||||
|
||||
if(s->K != KM)
|
||||
MP_INFO(af, "Limiting the number of filters to"
|
||||
" %i due to low sample rate.\n",s->K);
|
||||
|
||||
// Generate filter taps
|
||||
for(k=0;k<s->K;k++)
|
||||
bp2(s->a[k],s->b[k],F[k]/((float)af->data->rate),Q);
|
||||
|
||||
// Calculate how much this plugin adds to the overall time delay
|
||||
af->delay = 2.0 / (double)af->data->rate;
|
||||
|
||||
// Calculate gain factor to prevent clipping at output
|
||||
for(k=0;k<AF_NCH;k++)
|
||||
{
|
||||
for(i=0;i<KM;i++)
|
||||
{
|
||||
if(s->gain_factor < s->g[k][i]) s->gain_factor=s->g[k][i];
|
||||
}
|
||||
}
|
||||
|
||||
s->gain_factor=log10(s->gain_factor + 1.0) * 20.0;
|
||||
|
||||
if(s->gain_factor > 0.0)
|
||||
{
|
||||
s->gain_factor=0.1+(s->gain_factor/12.0);
|
||||
}else{
|
||||
s->gain_factor=1;
|
||||
}
|
||||
|
||||
return af_test_output(af,arg);
|
||||
}
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static int filter(struct af_instance* af, struct mp_audio* data)
|
||||
{
|
||||
struct mp_audio* c = data; // Current working data
|
||||
if (!c)
|
||||
return 0;
|
||||
af_equalizer_t* s = (af_equalizer_t*)af->priv; // Setup
|
||||
uint32_t ci = af->data->nch; // Index for channels
|
||||
uint32_t nch = af->data->nch; // Number of channels
|
||||
|
||||
if (af_make_writeable(af, data) < 0) {
|
||||
talloc_free(data);
|
||||
return -1;
|
||||
}
|
||||
|
||||
while(ci--){
|
||||
float* g = s->g[ci]; // Gain factor
|
||||
float* in = ((float*)c->planes[0])+ci;
|
||||
float* out = ((float*)c->planes[0])+ci;
|
||||
float* end = in + c->samples*c->nch; // Block loop end
|
||||
|
||||
while(in < end){
|
||||
register int k = 0; // Frequency band index
|
||||
register float yt = *in; // Current input sample
|
||||
in+=nch;
|
||||
|
||||
// Run the filters
|
||||
for(;k<s->K;k++){
|
||||
// Pointer to circular buffer wq
|
||||
register float* wq = s->wq[ci][k];
|
||||
// Calculate output from AR part of current filter
|
||||
register float w=yt*s->b[k][0] + wq[0]*s->a[k][0] + wq[1]*s->a[k][1];
|
||||
// Calculate output form MA part of current filter
|
||||
yt+=(w + wq[1]*s->b[k][1])*g[k];
|
||||
// Update circular buffer
|
||||
wq[1] = wq[0];
|
||||
wq[0] = w;
|
||||
}
|
||||
// Calculate output
|
||||
*out=yt*s->gain_factor;
|
||||
out+=nch;
|
||||
}
|
||||
}
|
||||
af_add_output_frame(af, data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Allocate memory and set function pointers
|
||||
static int af_open(struct af_instance* af){
|
||||
MP_WARN(af, "This filter is deprecated. Use 'anequalizer' or 'firequalizer' instead.\n");
|
||||
af->control=control;
|
||||
af->filter_frame = filter;
|
||||
af_equalizer_t *priv = af->priv;
|
||||
for(int i=0;i<AF_NCH;i++){
|
||||
for(int j=0;j<KM;j++){
|
||||
priv->g[i][j] = pow(10.0,MPCLAMP(priv->p[j],G_MIN,G_MAX)/20.0)-1.0;
|
||||
}
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT af_equalizer_t
|
||||
const struct af_info af_info_equalizer = {
|
||||
.info = "Equalizer audio filter",
|
||||
.name = "equalizer",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(af_equalizer_t),
|
||||
.options = (const struct m_option[]) {
|
||||
#define BAND(n) OPT_DOUBLE("e" #n, p[n], 0)
|
||||
BAND(0), BAND(1), BAND(2), BAND(3), BAND(4),
|
||||
BAND(5), BAND(6), BAND(7), BAND(8), BAND(9),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
+87
-94
@@ -15,14 +15,18 @@
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "audio/aframe.h"
|
||||
#include "audio/format.h"
|
||||
#include "filters/f_autoconvert.h"
|
||||
#include "filters/filter_internal.h"
|
||||
#include "filters/user_filters.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <libavutil/common.h>
|
||||
|
||||
#include "options/m_option.h"
|
||||
|
||||
struct f_opts {
|
||||
#include "audio/format.h"
|
||||
#include "af.h"
|
||||
|
||||
struct priv {
|
||||
struct m_config *config;
|
||||
|
||||
int in_format;
|
||||
int in_srate;
|
||||
struct m_channels in_channels;
|
||||
@@ -33,109 +37,98 @@ struct f_opts {
|
||||
int fail;
|
||||
};
|
||||
|
||||
struct priv {
|
||||
struct f_opts *opts;
|
||||
struct mp_pin *in_pin;
|
||||
};
|
||||
|
||||
static void process(struct mp_filter *f)
|
||||
static void force_in_params(struct af_instance *af, struct mp_audio *in)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
struct priv *priv = af->priv;
|
||||
|
||||
if (!mp_pin_can_transfer_data(f->ppins[1], p->in_pin))
|
||||
return;
|
||||
if (priv->in_format != AF_FORMAT_UNKNOWN)
|
||||
mp_audio_set_format(in, priv->in_format);
|
||||
|
||||
struct mp_frame frame = mp_pin_out_read(p->in_pin);
|
||||
if (priv->in_channels.num_chmaps > 0)
|
||||
mp_audio_set_channels(in, &priv->in_channels.chmaps[0]);
|
||||
|
||||
if (p->opts->fail) {
|
||||
MP_ERR(f, "Failing on purpose.\n");
|
||||
goto error;
|
||||
}
|
||||
if (priv->in_srate)
|
||||
in->rate = priv->in_srate;
|
||||
}
|
||||
|
||||
if (frame.type == MP_FRAME_EOF) {
|
||||
mp_pin_in_write(f->ppins[1], frame);
|
||||
return;
|
||||
}
|
||||
static void force_out_params(struct af_instance *af, struct mp_audio *out)
|
||||
{
|
||||
struct priv *priv = af->priv;
|
||||
|
||||
if (frame.type != MP_FRAME_AUDIO) {
|
||||
MP_ERR(f, "audio frame expected\n");
|
||||
goto error;
|
||||
}
|
||||
if (priv->out_format != AF_FORMAT_UNKNOWN)
|
||||
mp_audio_set_format(out, priv->out_format);
|
||||
|
||||
struct mp_aframe *in = frame.data;
|
||||
if (priv->out_channels.num_chmaps > 0)
|
||||
mp_audio_set_channels(out, &priv->out_channels.chmaps[0]);
|
||||
|
||||
if (p->opts->out_channels.num_chmaps > 0) {
|
||||
if (!mp_aframe_set_chmap(in, &p->opts->out_channels.chmaps[0])) {
|
||||
MP_ERR(f, "could not force output channels\n");
|
||||
goto error;
|
||||
if (priv->out_srate)
|
||||
out->rate = priv->out_srate;
|
||||
}
|
||||
|
||||
static int control(struct af_instance *af, int cmd, void *arg)
|
||||
{
|
||||
struct priv *priv = af->priv;
|
||||
|
||||
switch (cmd) {
|
||||
case AF_CONTROL_REINIT: {
|
||||
struct mp_audio *in = arg;
|
||||
struct mp_audio orig_in = *in;
|
||||
struct mp_audio *out = af->data;
|
||||
|
||||
force_in_params(af, in);
|
||||
mp_audio_copy_config(out, in);
|
||||
force_out_params(af, out);
|
||||
|
||||
if (in->nch != out->nch || in->bps != out->bps) {
|
||||
MP_ERR(af, "Forced input/output formats are incompatible.\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
if (priv->fail) {
|
||||
MP_ERR(af, "Failing on purpose.\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
return mp_audio_config_equals(in, &orig_in) ? AF_OK : AF_FALSE;
|
||||
}
|
||||
|
||||
if (p->opts->out_srate)
|
||||
mp_aframe_set_rate(in, p->opts->out_srate);
|
||||
|
||||
mp_pin_in_write(f->ppins[1], frame);
|
||||
return;
|
||||
|
||||
error:
|
||||
mp_frame_unref(&frame);
|
||||
mp_filter_internal_mark_failed(f);
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static const struct mp_filter_info af_format_filter = {
|
||||
.name = "format",
|
||||
.priv_size = sizeof(struct priv),
|
||||
.process = process,
|
||||
};
|
||||
|
||||
static struct mp_filter *af_format_create(struct mp_filter *parent,
|
||||
void *options)
|
||||
static int filter(struct af_instance *af, struct mp_audio *data)
|
||||
{
|
||||
struct mp_filter *f = mp_filter_create(parent, &af_format_filter);
|
||||
if (!f) {
|
||||
talloc_free(options);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct priv *p = f->priv;
|
||||
p->opts = talloc_steal(p, options);
|
||||
|
||||
mp_filter_add_pin(f, MP_PIN_IN, "in");
|
||||
mp_filter_add_pin(f, MP_PIN_OUT, "out");
|
||||
|
||||
struct mp_autoconvert *conv = mp_autoconvert_create(f);
|
||||
if (!conv)
|
||||
abort();
|
||||
|
||||
if (p->opts->in_format)
|
||||
mp_autoconvert_add_afmt(conv, p->opts->in_format);
|
||||
if (p->opts->in_srate)
|
||||
mp_autoconvert_add_srate(conv, p->opts->in_srate);
|
||||
if (p->opts->in_channels.num_chmaps > 0)
|
||||
mp_autoconvert_add_chmap(conv, &p->opts->in_channels.chmaps[0]);
|
||||
|
||||
mp_pin_connect(conv->f->pins[0], f->ppins[0]);
|
||||
p->in_pin = conv->f->pins[1];
|
||||
|
||||
return f;
|
||||
if (data)
|
||||
mp_audio_copy_config(data, af->data);
|
||||
af_add_output_frame(af, data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct f_opts
|
||||
static int af_open(struct af_instance *af)
|
||||
{
|
||||
af->control = control;
|
||||
af->filter_frame = filter;
|
||||
|
||||
const struct mp_user_filter_entry af_format = {
|
||||
.desc = {
|
||||
.name = "format",
|
||||
.description = "Force audio format",
|
||||
.priv_size = sizeof(struct f_opts),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_AUDIOFORMAT("format", in_format, 0),
|
||||
OPT_INTRANGE("srate", in_srate, 0, 1000, 8*48000),
|
||||
OPT_CHANNELS("channels", in_channels, 0, .min = 1),
|
||||
OPT_INTRANGE("out-srate", out_srate, 0, 1000, 8*48000),
|
||||
OPT_CHANNELS("out-channels", out_channels, 0, .min = 1),
|
||||
OPT_FLAG("fail", fail, 0),
|
||||
{0}
|
||||
},
|
||||
force_in_params(af, af->data);
|
||||
force_out_params(af, af->data);
|
||||
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
const struct af_info af_info_format = {
|
||||
.info = "Force audio format",
|
||||
.name = "format",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_AUDIOFORMAT("format", in_format, 0),
|
||||
OPT_INTRANGE("srate", in_srate, 0, 1000, 8*48000),
|
||||
OPT_CHANNELS("channels", in_channels, 0, .min = 1),
|
||||
OPT_AUDIOFORMAT("out-format", out_format, 0),
|
||||
OPT_INTRANGE("out-srate", out_srate, 0, 1000, 8*48000),
|
||||
OPT_CHANNELS("out-channels", out_channels, 0, .min = 1),
|
||||
OPT_FLAG("fail", fail, 0),
|
||||
{0}
|
||||
},
|
||||
.create = af_format_create,
|
||||
};
|
||||
+280
-234
@@ -31,17 +31,14 @@
|
||||
#include <libavutil/bswap.h>
|
||||
#include <libavutil/mem.h>
|
||||
|
||||
#include "audio/aframe.h"
|
||||
#include "audio/chmap_sel.h"
|
||||
#include "audio/fmt-conversion.h"
|
||||
#include "audio/format.h"
|
||||
#include "config.h"
|
||||
|
||||
#include "common/av_common.h"
|
||||
#include "common/common.h"
|
||||
#include "filters/f_autoconvert.h"
|
||||
#include "filters/f_utils.h"
|
||||
#include "filters/filter_internal.h"
|
||||
#include "filters/user_filters.h"
|
||||
#include "options/m_option.h"
|
||||
#include "af.h"
|
||||
#include "audio/audio_buffer.h"
|
||||
#include "audio/chmap_sel.h"
|
||||
#include "audio/fmt-conversion.h"
|
||||
|
||||
|
||||
#define AC3_MAX_CHANNELS 6
|
||||
@@ -52,89 +49,173 @@ const uint16_t ac3_bitrate_tab[19] = {
|
||||
160, 192, 224, 256, 320, 384, 448, 512, 576, 640
|
||||
};
|
||||
|
||||
struct f_opts {
|
||||
int add_iec61937_header;
|
||||
int bit_rate;
|
||||
int min_channel_num;
|
||||
char *encoder;
|
||||
char **avopts;
|
||||
};
|
||||
|
||||
struct priv {
|
||||
struct f_opts *opts;
|
||||
|
||||
struct mp_pin *in_pin;
|
||||
struct mp_aframe *cur_format;
|
||||
struct mp_aframe *in_frame;
|
||||
struct mp_aframe_pool *out_pool;
|
||||
|
||||
// Data for specific instances of this filter
|
||||
typedef struct af_ac3enc_s {
|
||||
struct AVCodec *lavc_acodec;
|
||||
struct AVCodecContext *lavc_actx;
|
||||
int bit_rate;
|
||||
struct mp_audio *input; // frame passed to libavcodec
|
||||
struct mp_audio *pending; // unconsumed input data
|
||||
int in_samples; // samples of input per AC3 frame
|
||||
int out_samples; // upper bound on encoded output per AC3 frame
|
||||
};
|
||||
int64_t encoder_buffered;
|
||||
|
||||
static bool reinit(struct mp_filter *f)
|
||||
int cfg_add_iec61937_header;
|
||||
int cfg_bit_rate;
|
||||
int cfg_min_channel_num;
|
||||
char *cfg_encoder;
|
||||
char **cfg_avopts;
|
||||
} af_ac3enc_t;
|
||||
|
||||
// fmt carries the input format. Change it to the best next-possible format
|
||||
// the encoder likely accepts.
|
||||
static void select_encode_format(AVCodecContext *c, struct mp_audio *fmt)
|
||||
{
|
||||
struct priv *s = f->priv;
|
||||
int formats[AF_FORMAT_COUNT];
|
||||
af_get_best_sample_formats(fmt->format, formats);
|
||||
|
||||
mp_aframe_reset(s->cur_format);
|
||||
for (int n = 0; formats[n]; n++) {
|
||||
const enum AVSampleFormat *lf = c->codec->sample_fmts;
|
||||
for (int i = 0; lf && lf[i] != AV_SAMPLE_FMT_NONE; i++) {
|
||||
int mpfmt = af_from_avformat(lf[i]);
|
||||
if (mpfmt && mpfmt == formats[n]) {
|
||||
mp_audio_set_format(fmt, mpfmt);
|
||||
goto done_fmt;
|
||||
}
|
||||
}
|
||||
}
|
||||
done_fmt: ;
|
||||
|
||||
int rate =
|
||||
af_select_best_samplerate(fmt->rate, c->codec->supported_samplerates);
|
||||
if (rate > 0)
|
||||
fmt->rate = rate;
|
||||
|
||||
struct mp_chmap_sel sel = {0};
|
||||
const uint64_t *lch = c->codec->channel_layouts;
|
||||
for (int n = 0; lch && lch[n]; n++) {
|
||||
struct mp_chmap chmap = {0};
|
||||
mp_chmap_from_lavc(&chmap, lch[n]);
|
||||
mp_chmap_sel_add_map(&sel, &chmap);
|
||||
}
|
||||
struct mp_chmap res = fmt->channels;
|
||||
mp_chmap_sel_adjust(&sel, &res);
|
||||
if (!mp_chmap_is_empty(&res))
|
||||
mp_audio_set_channels(fmt, &res);
|
||||
}
|
||||
|
||||
// Initialization and runtime control
|
||||
static int control(struct af_instance *af, int cmd, void *arg)
|
||||
{
|
||||
af_ac3enc_t *s = af->priv;
|
||||
static const int default_bit_rate[AC3_MAX_CHANNELS+1] = \
|
||||
{0, 96000, 192000, 256000, 384000, 448000, 448000};
|
||||
|
||||
if (s->opts->add_iec61937_header) {
|
||||
s->out_samples = AC3_FRAME_SIZE;
|
||||
} else {
|
||||
s->out_samples = AC3_MAX_CODED_FRAME_SIZE /
|
||||
mp_aframe_get_sstride(s->in_frame);
|
||||
switch (cmd){
|
||||
case AF_CONTROL_REINIT: {
|
||||
struct mp_audio *in = arg;
|
||||
struct mp_audio orig_in = *in;
|
||||
|
||||
if (!af_fmt_is_pcm(in->format) || in->nch < s->cfg_min_channel_num)
|
||||
return AF_DETACH;
|
||||
|
||||
// At least currently, the AC3 encoder doesn't export sample rates.
|
||||
in->rate = 48000;
|
||||
select_encode_format(s->lavc_actx, in);
|
||||
|
||||
af->data->rate = in->rate;
|
||||
mp_audio_set_format(af->data, AF_FORMAT_S_AC3);
|
||||
mp_audio_set_num_channels(af->data, 2);
|
||||
|
||||
if (!mp_audio_config_equals(in, &orig_in))
|
||||
return AF_FALSE;
|
||||
|
||||
if (s->cfg_add_iec61937_header) {
|
||||
s->out_samples = AC3_FRAME_SIZE;
|
||||
} else {
|
||||
s->out_samples = AC3_MAX_CODED_FRAME_SIZE / af->data->sstride;
|
||||
}
|
||||
|
||||
mp_audio_copy_config(s->input, in);
|
||||
|
||||
talloc_free(s->pending);
|
||||
s->pending = NULL;
|
||||
|
||||
MP_DBG(af, "reinit: %d, %d, %d.\n", in->nch, in->rate, s->in_samples);
|
||||
|
||||
int bit_rate = s->bit_rate ? s->bit_rate : default_bit_rate[in->nch];
|
||||
|
||||
if (s->lavc_actx->channels != in->nch ||
|
||||
s->lavc_actx->sample_rate != in->rate ||
|
||||
s->lavc_actx->bit_rate != bit_rate)
|
||||
{
|
||||
avcodec_close(s->lavc_actx);
|
||||
|
||||
// Put sample parameters
|
||||
s->lavc_actx->sample_fmt = af_to_avformat(in->format);
|
||||
s->lavc_actx->channels = in->nch;
|
||||
s->lavc_actx->channel_layout = mp_chmap_to_lavc(&in->channels);
|
||||
s->lavc_actx->sample_rate = in->rate;
|
||||
s->lavc_actx->bit_rate = bit_rate;
|
||||
|
||||
if (avcodec_open2(s->lavc_actx, s->lavc_acodec, NULL) < 0) {
|
||||
MP_ERR(af, "Couldn't open codec %s, br=%d.\n", "ac3", bit_rate);
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
if (s->lavc_actx->frame_size < 1) {
|
||||
MP_ERR(af, "encoder didn't specify input frame size\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
}
|
||||
s->in_samples = s->lavc_actx->frame_size;
|
||||
mp_audio_realloc(s->input, s->in_samples);
|
||||
s->input->samples = 0;
|
||||
s->encoder_buffered = 0;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
int format = mp_aframe_get_format(s->in_frame);
|
||||
int rate = mp_aframe_get_rate(s->in_frame);
|
||||
struct mp_chmap chmap = {0};
|
||||
mp_aframe_get_chmap(s->in_frame, &chmap);
|
||||
|
||||
int bit_rate = s->bit_rate;
|
||||
if (!bit_rate && chmap.num < AC3_MAX_CHANNELS + 1)
|
||||
bit_rate = default_bit_rate[chmap.num];
|
||||
|
||||
avcodec_close(s->lavc_actx);
|
||||
|
||||
// Put sample parameters
|
||||
s->lavc_actx->sample_fmt = af_to_avformat(format);
|
||||
s->lavc_actx->channels = chmap.num;
|
||||
s->lavc_actx->channel_layout = mp_chmap_to_lavc(&chmap);
|
||||
s->lavc_actx->sample_rate = rate;
|
||||
s->lavc_actx->bit_rate = bit_rate;
|
||||
|
||||
if (avcodec_open2(s->lavc_actx, s->lavc_acodec, NULL) < 0) {
|
||||
MP_ERR(f, "Couldn't open codec %s, br=%d.\n", "ac3", bit_rate);
|
||||
return false;
|
||||
case AF_CONTROL_RESET:
|
||||
if (avcodec_is_open(s->lavc_actx))
|
||||
avcodec_flush_buffers(s->lavc_actx);
|
||||
talloc_free(s->pending);
|
||||
s->pending = NULL;
|
||||
s->input->samples = 0;
|
||||
s->encoder_buffered = 0;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
if (s->lavc_actx->frame_size < 1) {
|
||||
MP_ERR(f, "encoder didn't specify input frame size\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
mp_aframe_config_copy(s->cur_format, s->in_frame);
|
||||
return true;
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static void reset(struct mp_filter *f)
|
||||
// Deallocate memory
|
||||
static void uninit(struct af_instance* af)
|
||||
{
|
||||
struct priv *s = f->priv;
|
||||
af_ac3enc_t *s = af->priv;
|
||||
|
||||
TA_FREEP(&s->in_frame);
|
||||
if (s) {
|
||||
avcodec_free_context(&s->lavc_actx);
|
||||
talloc_free(s->pending);
|
||||
}
|
||||
}
|
||||
|
||||
static void destroy(struct mp_filter *f)
|
||||
static void update_delay(struct af_instance *af)
|
||||
{
|
||||
struct priv *s = f->priv;
|
||||
af_ac3enc_t *s = af->priv;
|
||||
af->delay = ((s->pending ? s->pending->samples : 0) + s->input->samples +
|
||||
s->encoder_buffered) / (double)s->input->rate;
|
||||
}
|
||||
|
||||
reset(f);
|
||||
avcodec_free_context(&s->lavc_actx);
|
||||
static int filter_frame(struct af_instance *af, struct mp_audio *audio)
|
||||
{
|
||||
af_ac3enc_t *s = af->priv;
|
||||
|
||||
// filter_output must have been called until no output was produced.
|
||||
if (s->pending && s->pending->samples)
|
||||
MP_ERR(af, "broken data flow\n");
|
||||
|
||||
talloc_free(s->pending);
|
||||
s->pending = audio;
|
||||
update_delay(af);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void swap_16(uint16_t *ptr, size_t size)
|
||||
@@ -143,84 +224,105 @@ static void swap_16(uint16_t *ptr, size_t size)
|
||||
ptr[n] = av_bswap16(ptr[n]);
|
||||
}
|
||||
|
||||
static void process(struct mp_filter *f)
|
||||
// Copy data from input frame to encode frame (because libavcodec wants a full
|
||||
// AC3 frame for encoding, while filter input frames can be smaller or larger).
|
||||
// Return true if the frame is complete.
|
||||
static bool fill_buffer(struct af_instance *af)
|
||||
{
|
||||
struct priv *s = f->priv;
|
||||
af_ac3enc_t *s = af->priv;
|
||||
|
||||
if (!mp_pin_in_needs_data(f->ppins[1]))
|
||||
return;
|
||||
af->delay = 0;
|
||||
|
||||
if (s->pending) {
|
||||
if (!mp_audio_is_writeable(s->input))
|
||||
assert(s->input->samples == 0); // we can't have sent a partial frame
|
||||
mp_audio_realloc_min(s->input, s->in_samples);
|
||||
int copy = MPMIN(s->in_samples - s->input->samples, s->pending->samples);
|
||||
s->input->samples += copy;
|
||||
mp_audio_copy(s->input, s->input->samples - copy, s->pending, 0, copy);
|
||||
mp_audio_skip_samples(s->pending, copy);
|
||||
}
|
||||
update_delay(af);
|
||||
return s->input->samples >= s->in_samples;
|
||||
}
|
||||
|
||||
// Return <0 on error, 0 on need more input, 1 on success (and *frame set).
|
||||
// To actually advance the read pointer, set s->input->samples=0 afterwards.
|
||||
static int read_input_frame(struct af_instance *af, AVFrame *frame)
|
||||
{
|
||||
af_ac3enc_t *s = af->priv;
|
||||
if (!fill_buffer(af))
|
||||
return 0; // need more input
|
||||
|
||||
if (mp_audio_to_avframe(s->input, frame) < 0)
|
||||
return -1;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int filter_out(struct af_instance *af)
|
||||
{
|
||||
af_ac3enc_t *s = af->priv;
|
||||
|
||||
if (!s->pending)
|
||||
return 0;
|
||||
|
||||
AVFrame *frame = av_frame_alloc();
|
||||
if (!frame) {
|
||||
MP_FATAL(af, "Could not allocate memory \n");
|
||||
return -1;
|
||||
}
|
||||
int err = -1;
|
||||
|
||||
bool err = true;
|
||||
struct mp_aframe *out = NULL;
|
||||
AVPacket pkt = {0};
|
||||
av_init_packet(&pkt);
|
||||
|
||||
// Send input as long as it wants.
|
||||
while (1) {
|
||||
if (avcodec_is_open(s->lavc_actx)) {
|
||||
int lavc_ret = avcodec_receive_packet(s->lavc_actx, &pkt);
|
||||
if (lavc_ret >= 0)
|
||||
break;
|
||||
if (lavc_ret < 0 && lavc_ret != AVERROR(EAGAIN)) {
|
||||
MP_FATAL(f, "Encode failed (receive).\n");
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
AVFrame *frame = NULL;
|
||||
struct mp_frame input = mp_pin_out_read(s->in_pin);
|
||||
// The following code assumes no sample data buffering in the encoder.
|
||||
if (input.type == MP_FRAME_EOF) {
|
||||
mp_pin_in_write(f->ppins[1], input);
|
||||
return;
|
||||
} else if (input.type == MP_FRAME_AUDIO) {
|
||||
TA_FREEP(&s->in_frame);
|
||||
s->in_frame = input.data;
|
||||
frame = mp_frame_to_av(input, NULL);
|
||||
if (!frame)
|
||||
goto done;
|
||||
if (mp_aframe_get_channels(s->in_frame) < s->opts->min_channel_num) {
|
||||
// Just pass it through.
|
||||
s->in_frame = NULL;
|
||||
mp_pin_in_write(f->ppins[1], input);
|
||||
return;
|
||||
}
|
||||
if (!mp_aframe_config_equals(s->in_frame, s->cur_format)) {
|
||||
if (!reinit(f))
|
||||
goto done;
|
||||
}
|
||||
} else if (input.type) {
|
||||
err = read_input_frame(af, frame);
|
||||
if (err < 0)
|
||||
goto done;
|
||||
} else {
|
||||
return; // no data yet
|
||||
}
|
||||
if (err == 0)
|
||||
break;
|
||||
err = -1;
|
||||
int lavc_ret = avcodec_send_frame(s->lavc_actx, frame);
|
||||
av_frame_free(&frame);
|
||||
if (lavc_ret < 0 && lavc_ret != AVERROR(EAGAIN)) {
|
||||
MP_FATAL(f, "Encode failed (send).\n");
|
||||
// On EAGAIN, we're supposed to read remaining output.
|
||||
if (lavc_ret == AVERROR(EAGAIN))
|
||||
break;
|
||||
if (lavc_ret < 0) {
|
||||
MP_FATAL(af, "Encode failed.\n");
|
||||
goto done;
|
||||
}
|
||||
s->encoder_buffered += s->input->samples;
|
||||
s->input->samples = 0;
|
||||
}
|
||||
int lavc_ret = avcodec_receive_packet(s->lavc_actx, &pkt);
|
||||
if (lavc_ret == AVERROR(EAGAIN)) {
|
||||
// Need to buffer more input.
|
||||
err = 0;
|
||||
goto done;
|
||||
}
|
||||
if (lavc_ret < 0) {
|
||||
MP_FATAL(af, "Encode failed.\n");
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (!s->in_frame)
|
||||
MP_DBG(af, "avcodec_encode_audio got %d, pending %d.\n",
|
||||
pkt.size, s->pending->samples + s->input->samples);
|
||||
|
||||
s->encoder_buffered -= AC3_FRAME_SIZE;
|
||||
|
||||
struct mp_audio *out =
|
||||
mp_audio_pool_get(af->out_pool, af->data, s->out_samples);
|
||||
if (!out)
|
||||
goto done;
|
||||
|
||||
out = mp_aframe_create();
|
||||
mp_aframe_set_format(out, AF_FORMAT_S_AC3);
|
||||
mp_aframe_set_chmap(out, &(struct mp_chmap)MP_CHMAP_INIT_STEREO);
|
||||
mp_aframe_set_rate(out, 48000);
|
||||
|
||||
if (mp_aframe_pool_allocate(s->out_pool, out, s->out_samples) < 0)
|
||||
goto done;
|
||||
|
||||
int sstride = mp_aframe_get_sstride(out);
|
||||
|
||||
mp_aframe_copy_attributes(out, s->in_frame);
|
||||
mp_audio_copy_attributes(out, s->pending);
|
||||
|
||||
int frame_size = pkt.size;
|
||||
int header_len = 0;
|
||||
char hdr[8];
|
||||
|
||||
if (s->opts->add_iec61937_header && pkt.size > 5) {
|
||||
if (s->cfg_add_iec61937_header && pkt.size > 5) {
|
||||
int bsmod = pkt.data[5] & 0x7;
|
||||
int len = frame_size;
|
||||
|
||||
@@ -234,69 +336,48 @@ static void process(struct mp_filter *f)
|
||||
AV_WL16(hdr + 6, len << 3); // number of bits in payload
|
||||
}
|
||||
|
||||
if (frame_size > s->out_samples * sstride)
|
||||
if (frame_size > out->samples * out->sstride)
|
||||
abort();
|
||||
|
||||
uint8_t **planes = mp_aframe_get_data_rw(out);
|
||||
if (!planes)
|
||||
goto done;
|
||||
char *buf = planes[0];
|
||||
char *buf = (char *)out->planes[0];
|
||||
memcpy(buf, hdr, header_len);
|
||||
memcpy(buf + header_len, pkt.data, pkt.size);
|
||||
memset(buf + header_len + pkt.size, 0,
|
||||
frame_size - (header_len + pkt.size));
|
||||
swap_16((uint16_t *)(buf + header_len), pkt.size / 2);
|
||||
mp_aframe_set_size(out, frame_size / sstride);
|
||||
mp_pin_in_write(f->ppins[1], MAKE_FRAME(MP_FRAME_AUDIO, out));
|
||||
out = NULL;
|
||||
out->samples = frame_size / out->sstride;
|
||||
af_add_output_frame(af, out);
|
||||
|
||||
err = 0;
|
||||
done:
|
||||
av_packet_unref(&pkt);
|
||||
talloc_free(out);
|
||||
if (err)
|
||||
mp_filter_internal_mark_failed(f);
|
||||
av_frame_free(&frame);
|
||||
update_delay(af);
|
||||
return err;
|
||||
}
|
||||
|
||||
static const struct mp_filter_info af_lavcac3enc_filter = {
|
||||
.name = "lavcac3enc",
|
||||
.priv_size = sizeof(struct priv),
|
||||
.process = process,
|
||||
.reset = reset,
|
||||
.destroy = destroy,
|
||||
};
|
||||
static int af_open(struct af_instance* af){
|
||||
|
||||
static struct mp_filter *af_lavcac3enc_create(struct mp_filter *parent,
|
||||
void *options)
|
||||
{
|
||||
struct mp_filter *f = mp_filter_create(parent, &af_lavcac3enc_filter);
|
||||
if (!f) {
|
||||
talloc_free(options);
|
||||
return NULL;
|
||||
}
|
||||
af_ac3enc_t *s = af->priv;
|
||||
af->control=control;
|
||||
af->uninit=uninit;
|
||||
af->filter_frame = filter_frame;
|
||||
af->filter_out = filter_out;
|
||||
|
||||
mp_filter_add_pin(f, MP_PIN_IN, "in");
|
||||
mp_filter_add_pin(f, MP_PIN_OUT, "out");
|
||||
|
||||
struct priv *s = f->priv;
|
||||
s->opts = talloc_steal(s, options);
|
||||
s->cur_format = talloc_steal(s, mp_aframe_create());
|
||||
s->out_pool = mp_aframe_pool_create(s);
|
||||
|
||||
s->lavc_acodec = avcodec_find_encoder_by_name(s->opts->encoder);
|
||||
s->lavc_acodec = avcodec_find_encoder_by_name(s->cfg_encoder);
|
||||
if (!s->lavc_acodec) {
|
||||
MP_ERR(f, "Couldn't find encoder %s.\n", s->opts->encoder);
|
||||
goto error;
|
||||
MP_ERR(af, "Couldn't find encoder %s.\n", s->cfg_encoder);
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
s->lavc_actx = avcodec_alloc_context3(s->lavc_acodec);
|
||||
if (!s->lavc_actx) {
|
||||
MP_ERR(f, "Audio LAVC, couldn't allocate context!\n");
|
||||
goto error;
|
||||
MP_ERR(af, "Audio LAVC, couldn't allocate context!\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
if (mp_set_avopts(f->log, s->lavc_actx, s->opts->avopts) < 0)
|
||||
goto error;
|
||||
if (mp_set_avopts(af->log, s->lavc_actx, s->cfg_avopts) < 0)
|
||||
return AF_ERROR;
|
||||
|
||||
// For this one, we require the decoder to expert lists of all supported
|
||||
// parameters. (Not all decoders do that, but the ones we're interested
|
||||
@@ -304,85 +385,50 @@ static struct mp_filter *af_lavcac3enc_create(struct mp_filter *parent,
|
||||
if (!s->lavc_acodec->sample_fmts ||
|
||||
!s->lavc_acodec->channel_layouts)
|
||||
{
|
||||
MP_ERR(f, "Audio encoder doesn't list supported parameters.\n");
|
||||
goto error;
|
||||
MP_ERR(af, "Audio encoder doesn't list supported parameters.\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
if (s->opts->bit_rate) {
|
||||
s->input = talloc_zero(s, struct mp_audio);
|
||||
|
||||
if (s->cfg_bit_rate) {
|
||||
int i;
|
||||
for (i = 0; i < 19; i++) {
|
||||
if (ac3_bitrate_tab[i] == s->opts->bit_rate) {
|
||||
if (ac3_bitrate_tab[i] == s->cfg_bit_rate) {
|
||||
s->bit_rate = ac3_bitrate_tab[i] * 1000;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i >= 19) {
|
||||
MP_WARN(f, "unable set unsupported bitrate %d, using default "
|
||||
MP_WARN(af, "unable set unsupported bitrate %d, use default "
|
||||
"bitrate (check manpage to see supported bitrates).\n",
|
||||
s->opts->bit_rate);
|
||||
s->cfg_bit_rate);
|
||||
}
|
||||
}
|
||||
|
||||
struct mp_autoconvert *conv = mp_autoconvert_create(f);
|
||||
if (!conv)
|
||||
abort();
|
||||
|
||||
const enum AVSampleFormat *lf = s->lavc_acodec->sample_fmts;
|
||||
for (int i = 0; lf && lf[i] != AV_SAMPLE_FMT_NONE; i++) {
|
||||
int mpfmt = af_from_avformat(lf[i]);
|
||||
if (mpfmt)
|
||||
mp_autoconvert_add_afmt(conv, mpfmt);
|
||||
}
|
||||
|
||||
const uint64_t *lch = s->lavc_acodec->channel_layouts;
|
||||
for (int n = 0; lch && lch[n]; n++) {
|
||||
struct mp_chmap chmap = {0};
|
||||
mp_chmap_from_lavc(&chmap, lch[n]);
|
||||
if (mp_chmap_is_valid(&chmap))
|
||||
mp_autoconvert_add_chmap(conv, &chmap);
|
||||
}
|
||||
|
||||
// At least currently, the AC3 encoder doesn't export sample rates.
|
||||
mp_autoconvert_add_srate(conv, 48000);
|
||||
|
||||
mp_pin_connect(conv->f->pins[0], f->ppins[0]);
|
||||
|
||||
struct mp_filter *fs = mp_fixed_aframe_size_create(f, AC3_FRAME_SIZE, true);
|
||||
if (!fs)
|
||||
abort();
|
||||
|
||||
mp_pin_connect(fs->pins[0], conv->f->pins[1]);
|
||||
s->in_pin = fs->pins[1];
|
||||
|
||||
return f;
|
||||
|
||||
error:
|
||||
talloc_free(f);
|
||||
return NULL;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct f_opts
|
||||
#define OPT_BASE_STRUCT struct af_ac3enc_s
|
||||
|
||||
const struct mp_user_filter_entry af_lavcac3enc = {
|
||||
.desc = {
|
||||
.description = "runtime encode to ac3 using libavcodec",
|
||||
.name = "lavcac3enc",
|
||||
.priv_size = sizeof(OPT_BASE_STRUCT),
|
||||
.priv_defaults = &(const OPT_BASE_STRUCT) {
|
||||
.add_iec61937_header = 1,
|
||||
.bit_rate = 640,
|
||||
.min_channel_num = 3,
|
||||
.encoder = "ac3",
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_FLAG("tospdif", add_iec61937_header, 0),
|
||||
OPT_CHOICE_OR_INT("bitrate", bit_rate, 0, 32, 640,
|
||||
({"auto", 0}, {"default", 0})),
|
||||
OPT_INTRANGE("minch", min_channel_num, 0, 2, 6),
|
||||
OPT_STRING("encoder", encoder, 0),
|
||||
OPT_KEYVALUELIST("o", avopts, 0),
|
||||
{0}
|
||||
},
|
||||
const struct af_info af_info_lavcac3enc = {
|
||||
.info = "runtime encode to ac3 using libavcodec",
|
||||
.name = "lavcac3enc",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(struct af_ac3enc_s),
|
||||
.priv_defaults = &(const struct af_ac3enc_s){
|
||||
.cfg_add_iec61937_header = 1,
|
||||
.cfg_bit_rate = 640,
|
||||
.cfg_min_channel_num = 3,
|
||||
.cfg_encoder = "ac3",
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_FLAG("tospdif", cfg_add_iec61937_header, 0),
|
||||
OPT_CHOICE_OR_INT("bitrate", cfg_bit_rate, 0, 32, 640,
|
||||
({"auto", 0}, {"default", 0})),
|
||||
OPT_INTRANGE("minch", cfg_min_channel_num, 0, 2, 6),
|
||||
OPT_STRING("encoder", cfg_encoder, 0),
|
||||
OPT_KEYVALUELIST("o", cfg_avopts, 0),
|
||||
{0}
|
||||
},
|
||||
.create = af_lavcac3enc_create,
|
||||
};
|
||||
@@ -0,0 +1,409 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
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);
|
||||
|
||||
talloc_free(tmp);
|
||||
return true;
|
||||
|
||||
error:
|
||||
MP_FATAL(af, "Can't configure libavfilter graph.\n");
|
||||
avfilter_graph_free(&graph);
|
||||
talloc_free(tmp);
|
||||
return false;
|
||||
}
|
||||
|
||||
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, av_frame_get_metadata(frame));
|
||||
#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}
|
||||
},
|
||||
};
|
||||
+543
-58
@@ -27,86 +27,571 @@
|
||||
#include <math.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <libavutil/opt.h>
|
||||
#include <libavutil/common.h>
|
||||
#include <libavutil/samplefmt.h>
|
||||
#include <libavutil/channel_layout.h>
|
||||
#include <libavutil/mathematics.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "config.h"
|
||||
|
||||
#define HAVE_LIBSWRESAMPLE HAVE_IS_FFMPEG
|
||||
#define HAVE_LIBAVRESAMPLE HAVE_IS_LIBAV
|
||||
|
||||
#if HAVE_LIBAVRESAMPLE
|
||||
#include <libavresample/avresample.h>
|
||||
#elif HAVE_LIBSWRESAMPLE
|
||||
#include <libswresample/swresample.h>
|
||||
#define AVAudioResampleContext SwrContext
|
||||
#define avresample_alloc_context swr_alloc
|
||||
#define avresample_open swr_init
|
||||
#define avresample_close(x) do { } while(0)
|
||||
#define avresample_free swr_free
|
||||
#define avresample_available(x) 0
|
||||
#define avresample_convert(ctx, out, out_planesize, out_samples, in, in_planesize, in_samples) \
|
||||
swr_convert(ctx, out, out_samples, (const uint8_t**)(in), in_samples)
|
||||
#define avresample_set_channel_mapping swr_set_channel_mapping
|
||||
#define avresample_set_compensation swr_set_compensation
|
||||
#else
|
||||
#error "config.h broken or no resampler found"
|
||||
#endif
|
||||
|
||||
#include "common/av_common.h"
|
||||
#include "common/msg.h"
|
||||
#include "filters/f_swresample.h"
|
||||
#include "filters/filter_internal.h"
|
||||
#include "filters/user_filters.h"
|
||||
#include "options/m_config.h"
|
||||
#include "options/m_option.h"
|
||||
#include "options/options.h"
|
||||
#include "audio/filter/af.h"
|
||||
#include "audio/fmt-conversion.h"
|
||||
#include "osdep/endian.h"
|
||||
|
||||
struct af_resample_opts {
|
||||
int filter_size;
|
||||
int phase_shift;
|
||||
int linear;
|
||||
double cutoff;
|
||||
int normalize;
|
||||
};
|
||||
|
||||
struct af_resample {
|
||||
int allow_detach;
|
||||
struct mp_resample_opts opts;
|
||||
int global_normalize;
|
||||
char **avopts;
|
||||
double playback_speed;
|
||||
bool is_resampling;
|
||||
struct AVAudioResampleContext *avrctx;
|
||||
struct mp_audio avrctx_fmt; // output format of avrctx
|
||||
struct mp_audio pool_fmt; // format used to allocate frames for avrctx output
|
||||
struct mp_audio pre_out_fmt; // format before final conversion (S24)
|
||||
struct AVAudioResampleContext *avrctx_out; // for output channel reordering
|
||||
struct af_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];
|
||||
struct mp_audio_pool *reorder_buffer;
|
||||
|
||||
int in_rate_af; // filter input sample rate
|
||||
int in_rate; // actual rate (used by lavr), adjusted for playback speed
|
||||
int in_format;
|
||||
struct mp_chmap in_channels;
|
||||
int out_rate;
|
||||
int out_format;
|
||||
struct mp_chmap out_channels;
|
||||
};
|
||||
|
||||
static void set_defaults(struct mpv_global *global, void *p)
|
||||
#if HAVE_LIBAVRESAMPLE
|
||||
static double get_delay(struct af_resample *s)
|
||||
{
|
||||
struct af_resample *s = p;
|
||||
return avresample_get_delay(s->avrctx) / (double)s->in_rate +
|
||||
avresample_available(s->avrctx) / (double)s->out_rate;
|
||||
}
|
||||
static int get_out_samples(struct af_resample *s, int in_samples)
|
||||
{
|
||||
return avresample_get_out_samples(s->avrctx, in_samples);
|
||||
}
|
||||
#else
|
||||
static double get_delay(struct af_resample *s)
|
||||
{
|
||||
int64_t base = s->in_rate * (int64_t)s->out_rate;
|
||||
return swr_get_delay(s->avrctx, base) / (double)base;
|
||||
}
|
||||
static int get_out_samples(struct af_resample *s, int in_samples)
|
||||
{
|
||||
return swr_get_out_samples(s->avrctx, in_samples);
|
||||
}
|
||||
#endif
|
||||
|
||||
struct mp_resample_opts *opts = &s->opts;
|
||||
static void close_lavrr(struct af_instance *af)
|
||||
{
|
||||
struct af_resample *s = af->priv;
|
||||
|
||||
struct mp_resample_opts *src_opts =
|
||||
mp_get_config_group(s, global, &resample_conf);
|
||||
if (s->avrctx)
|
||||
avresample_close(s->avrctx);
|
||||
avresample_free(&s->avrctx);
|
||||
if (s->avrctx_out)
|
||||
avresample_close(s->avrctx_out);
|
||||
avresample_free(&s->avrctx_out);
|
||||
}
|
||||
|
||||
s->global_normalize = src_opts->normalize;
|
||||
static int resample_frame(struct AVAudioResampleContext *r,
|
||||
struct mp_audio *out, struct mp_audio *in)
|
||||
{
|
||||
return avresample_convert(r,
|
||||
out ? (uint8_t **)out->planes : NULL,
|
||||
out ? mp_audio_get_allocated_size(out) : 0,
|
||||
out ? out->samples : 0,
|
||||
in ? (uint8_t **)in->planes : NULL,
|
||||
in ? mp_audio_get_allocated_size(in) : 0,
|
||||
in ? in->samples : 0);
|
||||
}
|
||||
|
||||
assert(!opts->avopts); // we don't set a default value, so it must be NULL
|
||||
static double af_resample_default_cutoff(int filter_size)
|
||||
{
|
||||
return FFMAX(1.0 - 6.5 / (filter_size + 8), 0.80);
|
||||
}
|
||||
|
||||
*opts = *src_opts;
|
||||
static int rate_from_speed(int rate, double speed)
|
||||
{
|
||||
return lrint(rate * speed);
|
||||
}
|
||||
|
||||
opts->avopts = NULL;
|
||||
struct m_option dummy = {.type = &m_option_type_keyvalue_list};
|
||||
m_option_copy(&dummy, &opts->avopts, &src_opts->avopts);
|
||||
static struct mp_chmap fudge_pairs[][2] = {
|
||||
{MP_CHMAP2(BL, BR), MP_CHMAP2(SL, SR)},
|
||||
{MP_CHMAP2(SL, SR), MP_CHMAP2(BL, BR)},
|
||||
{MP_CHMAP2(SDL, SDR), MP_CHMAP2(SL, SR)},
|
||||
{MP_CHMAP2(SL, SR), MP_CHMAP2(SDL, SDR)},
|
||||
};
|
||||
|
||||
// Modify out_layout and return the new value. The intention is reducing the
|
||||
// loss libswresample's rematrixing will cause by exchanging similar, but
|
||||
// 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 af_instance *af,
|
||||
uint64_t in, uint64_t out)
|
||||
{
|
||||
for (int n = 0; n < MP_ARRAY_SIZE(fudge_pairs); n++) {
|
||||
uint64_t a = mp_chmap_to_lavc(&fudge_pairs[n][0]);
|
||||
uint64_t b = mp_chmap_to_lavc(&fudge_pairs[n][1]);
|
||||
if ((in & a) == a && (in & b) == 0 &&
|
||||
(out & a) == 0 && (out & b) == b)
|
||||
{
|
||||
out = (out & ~b) | a;
|
||||
|
||||
MP_VERBOSE(af, "Fudge: %s -> %s\n",
|
||||
mp_chmap_to_str(&fudge_pairs[n][0]),
|
||||
mp_chmap_to_str(&fudge_pairs[n][1]));
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// mp_chmap_get_reorder() performs:
|
||||
// to->speaker[n] = from->speaker[src[n]]
|
||||
// but libavresample does:
|
||||
// to->speaker[dst[n]] = from->speaker[n]
|
||||
static void transpose_order(int *map, int num)
|
||||
{
|
||||
int nmap[MP_NUM_CHANNELS] = {0};
|
||||
for (int n = 0; n < num; n++) {
|
||||
for (int i = 0; i < num; i++) {
|
||||
if (map[n] == i)
|
||||
nmap[i] = n;
|
||||
}
|
||||
}
|
||||
memcpy(map, nmap, sizeof(nmap));
|
||||
}
|
||||
|
||||
static int configure_lavrr(struct af_instance *af, struct mp_audio *in,
|
||||
struct mp_audio *out, bool verbose)
|
||||
{
|
||||
struct af_resample *s = af->priv;
|
||||
|
||||
close_lavrr(af);
|
||||
|
||||
s->avrctx = avresample_alloc_context();
|
||||
s->avrctx_out = avresample_alloc_context();
|
||||
if (!s->avrctx || !s->avrctx_out)
|
||||
goto error;
|
||||
|
||||
enum AVSampleFormat in_samplefmt = af_to_avformat(in->format);
|
||||
enum AVSampleFormat out_samplefmt = af_to_avformat(out->format);
|
||||
enum AVSampleFormat out_samplefmtp = av_get_planar_sample_fmt(out_samplefmt);
|
||||
|
||||
if (in_samplefmt == AV_SAMPLE_FMT_NONE ||
|
||||
out_samplefmt == AV_SAMPLE_FMT_NONE ||
|
||||
out_samplefmtp == AV_SAMPLE_FMT_NONE)
|
||||
goto error;
|
||||
|
||||
s->out_rate = out->rate;
|
||||
s->in_rate_af = in->rate;
|
||||
s->in_rate = rate_from_speed(in->rate, s->playback_speed);
|
||||
s->out_format = out->format;
|
||||
s->in_format = in->format;
|
||||
s->out_channels= out->channels;
|
||||
s->in_channels = in->channels;
|
||||
|
||||
av_opt_set_int(s->avrctx, "filter_size", s->opts.filter_size, 0);
|
||||
av_opt_set_int(s->avrctx, "phase_shift", s->opts.phase_shift, 0);
|
||||
av_opt_set_int(s->avrctx, "linear_interp", s->opts.linear, 0);
|
||||
|
||||
av_opt_set_double(s->avrctx, "cutoff", s->opts.cutoff, 0);
|
||||
|
||||
int normalize = s->opts.normalize;
|
||||
if (normalize < 0)
|
||||
normalize = af->opts->audio_normalize;
|
||||
#if HAVE_LIBSWRESAMPLE
|
||||
av_opt_set_double(s->avrctx, "rematrix_maxval", normalize ? 1 : 1000, 0);
|
||||
#else
|
||||
av_opt_set_int(s->avrctx, "normalize_mix_level", !!normalize, 0);
|
||||
#endif
|
||||
|
||||
if (mp_set_avopts(af->log, s->avrctx, s->avopts) < 0)
|
||||
goto error;
|
||||
|
||||
struct mp_chmap map_in = in->channels;
|
||||
struct mp_chmap map_out = out->channels;
|
||||
|
||||
// Try not to do any remixing if at least one is "unknown".
|
||||
if (mp_chmap_is_unknown(&map_in) || mp_chmap_is_unknown(&map_out)) {
|
||||
mp_chmap_set_unknown(&map_in, map_in.num);
|
||||
mp_chmap_set_unknown(&map_out, map_out.num);
|
||||
}
|
||||
|
||||
// unchecked: don't take any channel reordering into account
|
||||
uint64_t in_ch_layout = mp_chmap_to_lavc_unchecked(&map_in);
|
||||
uint64_t out_ch_layout = mp_chmap_to_lavc_unchecked(&map_out);
|
||||
|
||||
struct mp_chmap in_lavc, out_lavc;
|
||||
mp_chmap_from_lavc(&in_lavc, in_ch_layout);
|
||||
mp_chmap_from_lavc(&out_lavc, out_ch_layout);
|
||||
|
||||
if (verbose && !mp_chmap_equals(&in_lavc, &out_lavc)) {
|
||||
MP_VERBOSE(af, "Remix: %s -> %s\n", mp_chmap_to_str(&in_lavc),
|
||||
mp_chmap_to_str(&out_lavc));
|
||||
}
|
||||
|
||||
if (in_lavc.num != map_in.num) {
|
||||
// For handling NA channels, we would have to add a planarization step.
|
||||
MP_FATAL(af, "Unsupported channel remapping.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
mp_chmap_get_reorder(s->reorder_in, &map_in, &in_lavc);
|
||||
transpose_order(s->reorder_in, map_in.num);
|
||||
|
||||
if (mp_chmap_equals(&out_lavc, &map_out)) {
|
||||
// No intermediate step required - output new format directly.
|
||||
out_samplefmtp = out_samplefmt;
|
||||
} else {
|
||||
// Verify that we really just reorder and/or insert NA channels.
|
||||
struct mp_chmap withna = out_lavc;
|
||||
mp_chmap_fill_na(&withna, map_out.num);
|
||||
if (withna.num != map_out.num)
|
||||
goto error;
|
||||
}
|
||||
mp_chmap_get_reorder(s->reorder_out, &out_lavc, &map_out);
|
||||
|
||||
s->avrctx_fmt = *out;
|
||||
mp_audio_set_channels(&s->avrctx_fmt, &out_lavc);
|
||||
mp_audio_set_format(&s->avrctx_fmt, af_from_avformat(out_samplefmtp));
|
||||
|
||||
s->pre_out_fmt = *out;
|
||||
|
||||
// If there are NA channels, the final output will have more channels than
|
||||
// the avrctx output. Also, avrctx will output planar (out_samplefmtp was
|
||||
// not overwritten). Allocate the output frame with more channels, so the
|
||||
// NA channels can be trivially added.
|
||||
s->pool_fmt = s->avrctx_fmt;
|
||||
if (map_out.num > out_lavc.num)
|
||||
mp_audio_set_channels(&s->pool_fmt, &map_out);
|
||||
|
||||
out_ch_layout = fudge_layout_conversion(af, in_ch_layout, out_ch_layout);
|
||||
|
||||
// Real conversion; output is input to avrctx_out.
|
||||
av_opt_set_int(s->avrctx, "in_channel_layout", in_ch_layout, 0);
|
||||
av_opt_set_int(s->avrctx, "out_channel_layout", out_ch_layout, 0);
|
||||
av_opt_set_int(s->avrctx, "in_sample_rate", s->in_rate, 0);
|
||||
av_opt_set_int(s->avrctx, "out_sample_rate", s->out_rate, 0);
|
||||
av_opt_set_int(s->avrctx, "in_sample_fmt", in_samplefmt, 0);
|
||||
av_opt_set_int(s->avrctx, "out_sample_fmt", out_samplefmtp, 0);
|
||||
|
||||
// Just needs the correct number of channels for deplanarization.
|
||||
struct mp_chmap fake_chmap;
|
||||
mp_chmap_set_unknown(&fake_chmap, map_out.num);
|
||||
uint64_t fake_out_ch_layout = mp_chmap_to_lavc_unchecked(&fake_chmap);
|
||||
if (!fake_out_ch_layout)
|
||||
goto error;
|
||||
av_opt_set_int(s->avrctx_out, "in_channel_layout", fake_out_ch_layout, 0);
|
||||
av_opt_set_int(s->avrctx_out, "out_channel_layout", fake_out_ch_layout, 0);
|
||||
|
||||
av_opt_set_int(s->avrctx_out, "in_sample_fmt", out_samplefmtp, 0);
|
||||
av_opt_set_int(s->avrctx_out, "out_sample_fmt", out_samplefmt, 0);
|
||||
av_opt_set_int(s->avrctx_out, "in_sample_rate", s->out_rate, 0);
|
||||
av_opt_set_int(s->avrctx_out, "out_sample_rate", s->out_rate, 0);
|
||||
|
||||
// API has weird requirements, quoting avresample.h:
|
||||
// * This function can only be called when the allocated context is not open.
|
||||
// * Also, the input channel layout must have already been set.
|
||||
avresample_set_channel_mapping(s->avrctx, s->reorder_in);
|
||||
|
||||
s->is_resampling = false;
|
||||
|
||||
if (avresample_open(s->avrctx) < 0 || avresample_open(s->avrctx_out) < 0) {
|
||||
MP_ERR(af, "Cannot open Libavresample Context. \n");
|
||||
goto error;
|
||||
}
|
||||
return AF_OK;
|
||||
|
||||
error:
|
||||
close_lavrr(af);
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
r = configure_lavrr(af, in, out, true);
|
||||
return r;
|
||||
}
|
||||
case AF_CONTROL_SET_PLAYBACK_SPEED_RESAMPLE: {
|
||||
s->playback_speed = *(double *)arg;
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_RESET:
|
||||
if (s->avrctx) {
|
||||
#if HAVE_LIBSWRESAMPLE
|
||||
swr_close(s->avrctx);
|
||||
if (swr_init(s->avrctx) < 0) {
|
||||
close_lavrr(af);
|
||||
return AF_ERROR;
|
||||
}
|
||||
#else
|
||||
while (avresample_read(s->avrctx, NULL, 1000) > 0) {}
|
||||
#endif
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static void uninit(struct af_instance *af)
|
||||
{
|
||||
close_lavrr(af);
|
||||
}
|
||||
|
||||
// The LSB is always ignored.
|
||||
#if BYTE_ORDER == BIG_ENDIAN
|
||||
#define SHIFT24(x) ((3-(x))*8)
|
||||
#else
|
||||
#define SHIFT24(x) (((x)+1)*8)
|
||||
#endif
|
||||
|
||||
static void extra_output_conversion(struct af_instance *af, struct mp_audio *mpa)
|
||||
{
|
||||
for (int p = 0; p < mpa->num_planes; p++) {
|
||||
void *ptr = mpa->planes[p];
|
||||
int total = mpa->samples * mpa->spf;
|
||||
if (af_fmt_from_planar(mpa->format) == AF_FORMAT_FLOAT) {
|
||||
for (int s = 0; s < total; s++)
|
||||
((float *)ptr)[s] = av_clipf(((float *)ptr)[s], -1.0f, 1.0f);
|
||||
} else if (af_fmt_from_planar(mpa->format) == AF_FORMAT_DOUBLE) {
|
||||
for (int s = 0; s < total; s++)
|
||||
((double *)ptr)[s] = MPCLAMP(((double *)ptr)[s], -1.0, 1.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This relies on the tricky way mpa was allocated.
|
||||
static void reorder_planes(struct mp_audio *mpa, int *reorder,
|
||||
struct mp_chmap *newmap)
|
||||
{
|
||||
struct mp_audio prev = *mpa;
|
||||
mp_audio_set_channels(mpa, newmap);
|
||||
|
||||
// The trailing planes were never written by avrctx, they're the NA channels.
|
||||
int next_na = prev.num_planes;
|
||||
|
||||
for (int n = 0; n < mpa->num_planes; n++) {
|
||||
int src = reorder[n];
|
||||
assert(src >= -1 && src < prev.num_planes);
|
||||
if (src >= 0) {
|
||||
mpa->planes[n] = prev.planes[src];
|
||||
} else {
|
||||
assert(next_na < mpa->num_planes);
|
||||
mpa->planes[n] = prev.planes[next_na++];
|
||||
af_fill_silence(mpa->planes[n], mpa->sstride * mpa->samples,
|
||||
mpa->format);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int filter_resample(struct af_instance *af, struct mp_audio *in)
|
||||
{
|
||||
struct af_resample *s = af->priv;
|
||||
struct mp_audio *out = NULL;
|
||||
|
||||
if (!s->avrctx)
|
||||
goto error;
|
||||
|
||||
int samples = get_out_samples(s, in ? in->samples : 0);
|
||||
|
||||
struct mp_audio out_format = s->pool_fmt;
|
||||
out = mp_audio_pool_get(af->out_pool, &out_format, samples);
|
||||
if (!out)
|
||||
goto error;
|
||||
if (in)
|
||||
mp_audio_copy_attributes(out, in);
|
||||
|
||||
if (out->samples) {
|
||||
out->samples = resample_frame(s->avrctx, out, in);
|
||||
if (out->samples < 0)
|
||||
goto error;
|
||||
}
|
||||
|
||||
struct mp_audio real_out = *out;
|
||||
mp_audio_copy_config(out, &s->avrctx_fmt);
|
||||
|
||||
if (out->samples && !mp_audio_config_equals(out, &s->pre_out_fmt)) {
|
||||
assert(af_fmt_is_planar(out->format) && out->format == real_out.format);
|
||||
reorder_planes(out, s->reorder_out, &s->pool_fmt.channels);
|
||||
if (!mp_audio_config_equals(out, &s->pre_out_fmt)) {
|
||||
struct mp_audio *new = mp_audio_pool_get(s->reorder_buffer,
|
||||
&s->pre_out_fmt,
|
||||
out->samples);
|
||||
if (!new)
|
||||
goto error;
|
||||
mp_audio_copy_attributes(new, out);
|
||||
int out_samples = resample_frame(s->avrctx_out, new, out);
|
||||
talloc_free(out);
|
||||
out = new;
|
||||
if (out_samples != new->samples)
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
extra_output_conversion(af, out);
|
||||
|
||||
talloc_free(in);
|
||||
if (out->samples) {
|
||||
af_add_output_frame(af, out);
|
||||
} else {
|
||||
talloc_free(out);
|
||||
}
|
||||
|
||||
af->delay = get_delay(s);
|
||||
|
||||
return 0;
|
||||
error:
|
||||
talloc_free(in);
|
||||
talloc_free(out);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int filter(struct af_instance *af, struct mp_audio *in)
|
||||
{
|
||||
struct af_resample *s = af->priv;
|
||||
|
||||
int new_rate = rate_from_speed(s->in_rate_af, s->playback_speed);
|
||||
if (s->avrctx && !(!s->is_resampling && new_rate == s->in_rate)) {
|
||||
AVRational r = av_d2q(s->playback_speed * s->in_rate_af / s->in_rate,
|
||||
INT_MAX / 2);
|
||||
// Essentially, swr/avresample_set_compensation() does 2 things:
|
||||
// - adjust output sample rate by sample_delta/compensation_distance
|
||||
// - reset the adjustment after compensation_distance output samples
|
||||
// Increase the compensation_distance to avoid undesired reset
|
||||
// semantics - we want to keep the ratio for the whole frame we're
|
||||
// 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 (avresample_set_compensation(s->avrctx, r.den - r.num, r.den) >= 0) {
|
||||
new_rate = s->in_rate;
|
||||
s->is_resampling = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool need_reinit = fabs(new_rate / (double)s->in_rate - 1) > 0.01;
|
||||
if (need_reinit && new_rate != s->in_rate) {
|
||||
// Before reconfiguring, drain the audio that is still buffered
|
||||
// in the resampler.
|
||||
filter_resample(af, NULL);
|
||||
// Reinitialize resampler.
|
||||
configure_lavrr(af, &af->fmt_in, &af->fmt_out, false);
|
||||
}
|
||||
|
||||
return filter_resample(af, in);
|
||||
}
|
||||
|
||||
static int af_open(struct af_instance *af)
|
||||
{
|
||||
struct af_resample *s = af->priv;
|
||||
|
||||
af->control = control;
|
||||
af->uninit = uninit;
|
||||
af->filter_frame = filter;
|
||||
|
||||
if (s->opts.cutoff <= 0.0)
|
||||
s->opts.cutoff = af_resample_default_cutoff(s->opts.filter_size);
|
||||
|
||||
s->reorder_buffer = mp_audio_pool_create(s);
|
||||
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct af_resample
|
||||
|
||||
static struct mp_filter *af_lavrresample_create(struct mp_filter *parent,
|
||||
void *options)
|
||||
{
|
||||
struct af_resample *s = options;
|
||||
|
||||
if (s->opts.normalize < 0)
|
||||
s->opts.normalize = s->global_normalize;
|
||||
|
||||
struct mp_swresample *swr = mp_swresample_create(parent, &s->opts);
|
||||
if (!swr)
|
||||
abort();
|
||||
|
||||
MP_WARN(swr->f, "This filter is deprecated! Use the --audio-resample- options"
|
||||
" to customize resampling, or the --af=aresample filter.\n");
|
||||
|
||||
talloc_free(s);
|
||||
return swr->f;
|
||||
}
|
||||
|
||||
const struct mp_user_filter_entry af_lavrresample = {
|
||||
.desc = {
|
||||
.description = "Sample frequency conversion using libavresample",
|
||||
.name = "lavrresample",
|
||||
.priv_size = sizeof(struct af_resample),
|
||||
.priv_defaults = &(const struct af_resample) {
|
||||
.opts = MP_RESAMPLE_OPTS_DEF,
|
||||
.allow_detach = 1,
|
||||
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 = {
|
||||
.filter_size = 16,
|
||||
.cutoff = 0.0,
|
||||
.phase_shift = 10,
|
||||
.normalize = -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), // does nothing
|
||||
OPT_CHOICE("normalize", opts.normalize, 0,
|
||||
({"no", 0}, {"yes", 1}, {"auto", -1})),
|
||||
OPT_KEYVALUELIST("o", opts.avopts, 0),
|
||||
{0}
|
||||
},
|
||||
.set_defaults = set_defaults,
|
||||
.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", avopts, 0),
|
||||
{0}
|
||||
},
|
||||
.create = af_lavrresample_create,
|
||||
};
|
||||
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <math.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "af.h"
|
||||
|
||||
// Data for specific instances of this filter
|
||||
typedef struct af_pan_s {
|
||||
int nch; // Number of output channels; zero means same as input
|
||||
float level[AF_NCH][AF_NCH]; // Gain level for each channel
|
||||
char *matrixstr;
|
||||
} af_pan_t;
|
||||
|
||||
static void set_channels(struct mp_audio *mpa, int num)
|
||||
{
|
||||
struct mp_chmap map;
|
||||
// "unknown" channel layouts make it easier to pass through audio data,
|
||||
// without triggering remixing.
|
||||
mp_chmap_set_unknown(&map, num);
|
||||
mp_audio_set_channels(mpa, &map);
|
||||
}
|
||||
|
||||
static void parse_matrix(struct af_instance *af, const char *cp)
|
||||
{
|
||||
af_pan_t *s = af->priv;
|
||||
int j = 0, k = 0, n;
|
||||
while (*cp && k < AF_NCH) {
|
||||
sscanf(cp, "%f%n" , &s->level[j][k], &n);
|
||||
MP_VERBOSE(af, "Pan level from channel %i to"
|
||||
" channel %i = %f\n", k, j, s->level[j][k]);
|
||||
cp = &cp[n];
|
||||
j++;
|
||||
if (j >= s->nch) {
|
||||
j = 0;
|
||||
k++;
|
||||
}
|
||||
if (*cp != ',')
|
||||
break;
|
||||
cp++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Initialization and runtime control
|
||||
static int control(struct af_instance *af, int cmd, void *arg)
|
||||
{
|
||||
af_pan_t* s = af->priv;
|
||||
|
||||
switch(cmd){
|
||||
case AF_CONTROL_REINIT:
|
||||
// Sanity check
|
||||
if (!arg)
|
||||
return AF_ERROR;
|
||||
|
||||
af->data->rate = ((struct mp_audio*)arg)->rate;
|
||||
mp_audio_set_format(af->data, AF_FORMAT_FLOAT);
|
||||
set_channels(af->data, s->nch ? s->nch : ((struct mp_audio*)arg)->nch);
|
||||
|
||||
if ((af->data->format != ((struct mp_audio*)arg)->format) ||
|
||||
(af->data->bps != ((struct mp_audio*)arg)->bps)) {
|
||||
mp_audio_set_format((struct mp_audio*)arg, af->data->format);
|
||||
return AF_FALSE;
|
||||
}
|
||||
return AF_OK;
|
||||
case AF_CONTROL_SET_PAN_LEVEL: {
|
||||
int i;
|
||||
int ch = ((af_control_ext_t*)arg)->ch;
|
||||
float *level = ((af_control_ext_t*)arg)->arg;
|
||||
if (ch >= AF_NCH)
|
||||
return AF_FALSE;
|
||||
for (i = 0; i < AF_NCH; i++)
|
||||
s->level[ch][i] = level[i];
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_SET_PAN_NOUT:
|
||||
// Reinit must be called after this function has been called
|
||||
// Sanity check
|
||||
if (((int*)arg)[0] <= 0 || ((int*)arg)[0] > AF_NCH) {
|
||||
MP_ERR(af, "The number of output channels must be"
|
||||
" between 1 and %i. Current value is %i\n",
|
||||
AF_NCH, ((int*)arg)[0]);
|
||||
return AF_ERROR;
|
||||
}
|
||||
s->nch = ((int*)arg)[0];
|
||||
return AF_OK;
|
||||
case AF_CONTROL_SET_PAN_BALANCE: {
|
||||
float val = *(float*)arg;
|
||||
if (s->nch)
|
||||
return AF_ERROR;
|
||||
if (af->data->nch >= 2) {
|
||||
s->level[0][0] = MPMIN(1.f, 1.f - val);
|
||||
s->level[0][1] = MPMAX(0.f, val);
|
||||
s->level[1][0] = MPMAX(0.f, -val);
|
||||
s->level[1][1] = MPMIN(1.f, 1.f + val);
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_GET_PAN_BALANCE:
|
||||
if (s->nch)
|
||||
return AF_ERROR;
|
||||
*(float*)arg = s->level[0][1] - s->level[1][0];
|
||||
return AF_OK;
|
||||
case AF_CONTROL_COMMAND: {
|
||||
char **args = arg;
|
||||
if (!strcmp(args[0], "set-matrix")) {
|
||||
parse_matrix(af, args[1]);
|
||||
return CONTROL_OK;
|
||||
} else {
|
||||
return CONTROL_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static int filter_frame(struct af_instance *af, struct mp_audio *c)
|
||||
{
|
||||
if (!c)
|
||||
return 0;
|
||||
struct mp_audio *l = mp_audio_pool_get(af->out_pool, &af->fmt_out, c->samples);
|
||||
if (!l) {
|
||||
talloc_free(c);
|
||||
return -1;
|
||||
}
|
||||
mp_audio_copy_attributes(l, c);
|
||||
|
||||
af_pan_t* s = af->priv; // Setup for this instance
|
||||
float *in = c->planes[0]; // Input audio data
|
||||
float *out = NULL; // Output audio data
|
||||
float *end = in+c->samples * c->nch; // End of loop
|
||||
int nchi = c->nch; // Number of input channels
|
||||
int ncho = l->nch; // Number of output channels
|
||||
register int j, k;
|
||||
|
||||
out = l->planes[0];
|
||||
// Execute panning
|
||||
// FIXME: Too slow
|
||||
while (in < end) {
|
||||
for (j = 0; j < ncho; j++) {
|
||||
register float x = 0.0;
|
||||
register float *tin = in;
|
||||
for (k = 0; k < nchi; k++)
|
||||
x += tin[k] * s->level[j][k];
|
||||
out[j] = x;
|
||||
}
|
||||
out += ncho;
|
||||
in += nchi;
|
||||
}
|
||||
|
||||
talloc_free(c);
|
||||
af_add_output_frame(af, l);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Allocate memory and set function pointers
|
||||
static int af_open(struct af_instance *af)
|
||||
{
|
||||
af->control = control;
|
||||
af->filter_frame = filter_frame;
|
||||
MP_WARN(af, "This filter is deprecated. Use lavfi pan instead.\n");
|
||||
af_pan_t *s = af->priv;
|
||||
int nch = s->nch;
|
||||
if (nch && AF_OK != control(af, AF_CONTROL_SET_PAN_NOUT, &nch))
|
||||
return AF_ERROR;
|
||||
|
||||
// Read pan values
|
||||
if (s->matrixstr)
|
||||
parse_matrix(af, s->matrixstr);
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT af_pan_t
|
||||
const struct af_info af_info_pan = {
|
||||
.info = "Panning audio filter",
|
||||
.name = "pan",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(af_pan_t),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_INTRANGE("channels", nch, 0, 0, AF_NCH),
|
||||
OPT_STRING("matrix", matrixstr, 0),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
+169
-287
@@ -20,350 +20,232 @@
|
||||
|
||||
#include <rubberband/rubberband-c.h>
|
||||
|
||||
#include "audio/aframe.h"
|
||||
#include "audio/format.h"
|
||||
#include "common/common.h"
|
||||
#include "filters/f_autoconvert.h"
|
||||
#include "filters/filter_internal.h"
|
||||
#include "filters/user_filters.h"
|
||||
#include "options/m_option.h"
|
||||
|
||||
// command line options
|
||||
struct f_opts {
|
||||
int transients, detector, phase, window,
|
||||
smoothing, formant, pitch, channels;
|
||||
double scale;
|
||||
};
|
||||
#include "af.h"
|
||||
|
||||
struct priv {
|
||||
struct f_opts *opts;
|
||||
|
||||
struct mp_pin *in_pin;
|
||||
struct mp_aframe *cur_format;
|
||||
struct mp_aframe_pool *out_pool;
|
||||
bool sent_final;
|
||||
RubberBandState rubber;
|
||||
double speed;
|
||||
double pitch;
|
||||
struct mp_aframe *pending;
|
||||
struct mp_audio *pending;
|
||||
bool needs_reset;
|
||||
// Estimate how much librubberband has buffered internally.
|
||||
// I could not find a way to do this with the librubberband API.
|
||||
double rubber_delay;
|
||||
// command line options
|
||||
int opt_transients, opt_detector, opt_phase, opt_window,
|
||||
opt_smoothing, opt_formant, opt_pitch, opt_channels;
|
||||
};
|
||||
|
||||
static void update_speed(struct priv *p, double new_speed)
|
||||
static void update_speed(struct af_instance *af, double new_speed)
|
||||
{
|
||||
struct priv *p = af->priv;
|
||||
|
||||
p->speed = new_speed;
|
||||
if (p->rubber)
|
||||
rubberband_set_time_ratio(p->rubber, 1.0 / p->speed);
|
||||
rubberband_set_time_ratio(p->rubber, 1.0 / p->speed);
|
||||
}
|
||||
|
||||
static bool update_pitch(struct priv *p, double new_pitch)
|
||||
static void update_pitch(struct af_instance *af, double new_pitch)
|
||||
{
|
||||
if (new_pitch < 0.01 || new_pitch > 100.0)
|
||||
return false;
|
||||
struct priv *p = af->priv;
|
||||
|
||||
p->pitch = new_pitch;
|
||||
if (p->rubber)
|
||||
rubberband_set_pitch_scale(p->rubber, p->pitch);
|
||||
return true;
|
||||
rubberband_set_pitch_scale(p->rubber, p->pitch);
|
||||
}
|
||||
|
||||
static bool init_rubberband(struct mp_filter *f)
|
||||
static int control(struct af_instance *af, int cmd, void *arg)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
struct priv *p = af->priv;
|
||||
|
||||
assert(!p->rubber);
|
||||
assert(p->pending);
|
||||
switch (cmd) {
|
||||
case AF_CONTROL_REINIT: {
|
||||
struct mp_audio *in = arg;
|
||||
struct mp_audio orig_in = *in;
|
||||
struct mp_audio *out = af->data;
|
||||
|
||||
int opts = p->opts->transients | p->opts->detector | p->opts->phase |
|
||||
p->opts->window | p->opts->smoothing | p->opts->formant |
|
||||
p->opts->pitch | p-> opts->channels |
|
||||
RubberBandOptionProcessRealTime;
|
||||
in->format = AF_FORMAT_FLOATP;
|
||||
mp_audio_copy_config(out, in);
|
||||
|
||||
int rate = mp_aframe_get_rate(p->pending);
|
||||
int channels = mp_aframe_get_channels(p->pending);
|
||||
if (mp_aframe_get_format(p->pending) != AF_FORMAT_FLOATP)
|
||||
return false;
|
||||
if (p->rubber)
|
||||
rubberband_delete(p->rubber);
|
||||
|
||||
p->rubber = rubberband_new(rate, channels, opts, 1.0, 1.0);
|
||||
if (!p->rubber) {
|
||||
MP_FATAL(f, "librubberband initialization failed.\n");
|
||||
return false;
|
||||
int opts = p->opt_transients | p->opt_detector | p->opt_phase |
|
||||
p->opt_window | p->opt_smoothing | p->opt_formant |
|
||||
p->opt_pitch | p-> opt_channels |
|
||||
RubberBandOptionProcessRealTime;
|
||||
|
||||
p->rubber = rubberband_new(in->rate, in->channels.num, opts, 1.0, 1.0);
|
||||
if (!p->rubber) {
|
||||
MP_FATAL(af, "librubberband initialization failed.\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
update_speed(af, p->speed);
|
||||
update_pitch(af, p->pitch);
|
||||
control(af, AF_CONTROL_RESET, NULL);
|
||||
|
||||
return mp_audio_config_equals(in, &orig_in) ? AF_OK : AF_FALSE;
|
||||
}
|
||||
|
||||
mp_aframe_config_copy(p->cur_format, p->pending);
|
||||
|
||||
update_speed(p, p->speed);
|
||||
update_pitch(p, p->pitch);
|
||||
|
||||
return true;
|
||||
case AF_CONTROL_SET_PLAYBACK_SPEED: {
|
||||
update_speed(af, *(double *)arg);
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_RESET:
|
||||
if (p->rubber)
|
||||
rubberband_reset(p->rubber);
|
||||
talloc_free(p->pending);
|
||||
p->pending = NULL;
|
||||
p->rubber_delay = 0;
|
||||
return AF_OK;
|
||||
case AF_CONTROL_COMMAND: {
|
||||
char **args = arg;
|
||||
if (!strcmp(args[0], "set-pitch")) {
|
||||
char *endptr;
|
||||
double pitch = strtod(args[1], &endptr);
|
||||
if (*endptr || pitch < 0.01 || pitch > 100.0)
|
||||
return CONTROL_ERROR;
|
||||
update_pitch(af, pitch);
|
||||
return CONTROL_OK;
|
||||
} else {
|
||||
return CONTROL_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static void process(struct mp_filter *f)
|
||||
static int filter_frame(struct af_instance *af, struct mp_audio *data)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
struct priv *p = af->priv;
|
||||
|
||||
if (!mp_pin_in_needs_data(f->ppins[1]))
|
||||
return;
|
||||
talloc_free(p->pending);
|
||||
p->pending = data;
|
||||
|
||||
while (!p->rubber || !p->pending || rubberband_available(p->rubber) <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int filter_out(struct af_instance *af)
|
||||
{
|
||||
struct priv *p = af->priv;
|
||||
|
||||
while (rubberband_available(p->rubber) <= 0) {
|
||||
const float *dummy[MP_NUM_CHANNELS] = {0};
|
||||
const float **in_data = dummy;
|
||||
size_t in_samples = 0;
|
||||
if (p->pending) {
|
||||
if (!p->pending->samples)
|
||||
break;
|
||||
|
||||
bool eof = false;
|
||||
if (!p->pending || !mp_aframe_get_size(p->pending)) {
|
||||
struct mp_frame frame = mp_pin_out_read(p->in_pin);
|
||||
if (frame.type == MP_FRAME_AUDIO) {
|
||||
TA_FREEP(&p->pending);
|
||||
p->pending = frame.data;
|
||||
} else if (frame.type == MP_FRAME_EOF) {
|
||||
eof = true;
|
||||
} else if (frame.type) {
|
||||
MP_ERR(f, "unexpected frame type\n");
|
||||
goto error;
|
||||
} else {
|
||||
return; // no new data yet
|
||||
}
|
||||
}
|
||||
assert(p->pending || eof);
|
||||
|
||||
if (!p->rubber) {
|
||||
if (!p->pending) {
|
||||
mp_pin_in_write(f->ppins[1], MP_EOF_FRAME);
|
||||
return;
|
||||
}
|
||||
if (!init_rubberband(f))
|
||||
goto error;
|
||||
}
|
||||
|
||||
bool format_change =
|
||||
p->pending && !mp_aframe_config_equals(p->pending, p->cur_format);
|
||||
|
||||
if (p->pending && !format_change) {
|
||||
size_t needs = rubberband_get_samples_required(p->rubber);
|
||||
uint8_t **planes = mp_aframe_get_data_ro(p->pending);
|
||||
int num_planes = mp_aframe_get_planes(p->pending);
|
||||
for (int n = 0; n < num_planes; n++)
|
||||
in_data[n] = (void *)planes[n];
|
||||
in_samples = MPMIN(mp_aframe_get_size(p->pending), needs);
|
||||
}
|
||||
|
||||
bool final = format_change || eof;
|
||||
if (!p->sent_final)
|
||||
rubberband_process(p->rubber, in_data, in_samples, final);
|
||||
p->sent_final |= final;
|
||||
|
||||
p->rubber_delay += in_samples;
|
||||
|
||||
if (p->pending && !format_change)
|
||||
mp_aframe_skip_samples(p->pending, in_samples);
|
||||
|
||||
if (rubberband_available(p->rubber) > 0) {
|
||||
if (eof)
|
||||
mp_pin_out_repeat_eof(p->in_pin); // drain more next time
|
||||
} else {
|
||||
if (eof) {
|
||||
mp_pin_in_write(f->ppins[1], MP_EOF_FRAME);
|
||||
// recover from previous EOF
|
||||
if (p->needs_reset) {
|
||||
rubberband_reset(p->rubber);
|
||||
p->rubber_delay = 0;
|
||||
TA_FREEP(&p->pending);
|
||||
p->sent_final = false;
|
||||
return;
|
||||
} else if (format_change) {
|
||||
// go on with proper reinit on the next iteration
|
||||
rubberband_delete(p->rubber);
|
||||
p->sent_final = false;
|
||||
p->rubber = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
p->needs_reset = false;
|
||||
|
||||
assert(p->pending);
|
||||
size_t needs = rubberband_get_samples_required(p->rubber);
|
||||
in_data = (void *)&p->pending->planes;
|
||||
in_samples = MPMIN(p->pending->samples, needs);
|
||||
}
|
||||
|
||||
if (p->needs_reset)
|
||||
break; // previous EOF
|
||||
p->needs_reset = !p->pending; // EOF
|
||||
|
||||
rubberband_process(p->rubber, in_data, in_samples, p->needs_reset);
|
||||
p->rubber_delay += in_samples;
|
||||
|
||||
if (!p->pending)
|
||||
break;
|
||||
mp_audio_skip_samples(p->pending, in_samples);
|
||||
}
|
||||
|
||||
int out_samples = rubberband_available(p->rubber);
|
||||
if (out_samples > 0) {
|
||||
struct mp_aframe *out = mp_aframe_new_ref(p->cur_format);
|
||||
if (mp_aframe_pool_allocate(p->out_pool, out, out_samples) < 0) {
|
||||
talloc_free(out);
|
||||
goto error;
|
||||
}
|
||||
struct mp_audio *out =
|
||||
mp_audio_pool_get(af->out_pool, af->data, out_samples);
|
||||
if (!out)
|
||||
return -1;
|
||||
if (p->pending)
|
||||
mp_audio_copy_config(out, p->pending);
|
||||
|
||||
mp_aframe_copy_attributes(out, p->pending);
|
||||
float **out_data = (void *)&out->planes;
|
||||
out->samples = rubberband_retrieve(p->rubber, out_data, out->samples);
|
||||
p->rubber_delay -= out->samples * p->speed;
|
||||
|
||||
float *out_data[MP_NUM_CHANNELS] = {0};
|
||||
uint8_t **planes = mp_aframe_get_data_rw(out);
|
||||
assert(planes);
|
||||
int num_planes = mp_aframe_get_planes(out);
|
||||
for (int n = 0; n < num_planes; n++)
|
||||
out_data[n] = (void *)planes[n];
|
||||
|
||||
out_samples = rubberband_retrieve(p->rubber, out_data, out_samples);
|
||||
|
||||
if (!out_samples) {
|
||||
mp_filter_internal_mark_progress(f); // unexpected, just try again
|
||||
talloc_free(out);
|
||||
return;
|
||||
}
|
||||
|
||||
mp_aframe_set_size(out, out_samples);
|
||||
|
||||
p->rubber_delay -= out_samples * p->speed;
|
||||
|
||||
double pts = mp_aframe_get_pts(p->pending);
|
||||
if (pts != MP_NOPTS_VALUE) {
|
||||
// Note: rubberband_get_latency() does not do what you'd expect.
|
||||
double delay = p->rubber_delay / mp_aframe_get_effective_rate(out);
|
||||
mp_aframe_set_pts(out, pts - delay);
|
||||
}
|
||||
|
||||
mp_aframe_mul_speed(out, p->speed);
|
||||
|
||||
mp_pin_in_write(f->ppins[1], MAKE_FRAME(MP_FRAME_AUDIO, out));
|
||||
af_add_output_frame(af, out);
|
||||
}
|
||||
|
||||
return;
|
||||
error:
|
||||
mp_filter_internal_mark_failed(f);
|
||||
int delay_samples = p->rubber_delay;
|
||||
if (p->pending)
|
||||
delay_samples += p->pending->samples;
|
||||
af->delay = delay_samples / (af->data->rate * p->speed);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool command(struct mp_filter *f, struct mp_filter_command *cmd)
|
||||
static void uninit(struct af_instance *af)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
|
||||
switch (cmd->type) {
|
||||
case MP_FILTER_COMMAND_TEXT: {
|
||||
char *endptr = NULL;
|
||||
double pitch = p->pitch;
|
||||
if (!strcmp(cmd->cmd, "set-pitch")) {
|
||||
pitch = strtod(cmd->arg, &endptr);
|
||||
if (*endptr)
|
||||
return false;
|
||||
return update_pitch(p, pitch);
|
||||
} else if (!strcmp(cmd->cmd, "multiply-pitch")) {
|
||||
double mult = strtod(cmd->arg, &endptr);
|
||||
if (*endptr || mult <= 0)
|
||||
return false;
|
||||
pitch *= mult;
|
||||
return update_pitch(p, pitch);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case MP_FILTER_COMMAND_SET_SPEED:
|
||||
update_speed(p, cmd->speed);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void reset(struct mp_filter *f)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
|
||||
if (p->rubber)
|
||||
rubberband_reset(p->rubber);
|
||||
p->rubber_delay = 0;
|
||||
p->sent_final = false;
|
||||
TA_FREEP(&p->pending);
|
||||
}
|
||||
|
||||
static void destroy(struct mp_filter *f)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
struct priv *p = af->priv;
|
||||
|
||||
if (p->rubber)
|
||||
rubberband_delete(p->rubber);
|
||||
talloc_free(p->pending);
|
||||
}
|
||||
|
||||
static const struct mp_filter_info af_rubberband_filter = {
|
||||
.name = "rubberband",
|
||||
.priv_size = sizeof(struct priv),
|
||||
.process = process,
|
||||
.command = command,
|
||||
.reset = reset,
|
||||
.destroy = destroy,
|
||||
};
|
||||
|
||||
static struct mp_filter *af_rubberband_create(struct mp_filter *parent,
|
||||
void *options)
|
||||
static int af_open(struct af_instance *af)
|
||||
{
|
||||
struct mp_filter *f = mp_filter_create(parent, &af_rubberband_filter);
|
||||
if (!f) {
|
||||
talloc_free(options);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
mp_filter_add_pin(f, MP_PIN_IN, "in");
|
||||
mp_filter_add_pin(f, MP_PIN_OUT, "out");
|
||||
|
||||
struct priv *p = f->priv;
|
||||
p->opts = talloc_steal(p, options);
|
||||
p->speed = 1.0;
|
||||
p->pitch = p->opts->scale;
|
||||
p->cur_format = talloc_steal(p, mp_aframe_create());
|
||||
p->out_pool = mp_aframe_pool_create(p);
|
||||
|
||||
struct mp_autoconvert *conv = mp_autoconvert_create(f);
|
||||
if (!conv)
|
||||
abort();
|
||||
|
||||
mp_autoconvert_add_afmt(conv, AF_FORMAT_FLOATP);
|
||||
|
||||
mp_pin_connect(conv->f->pins[0], f->ppins[0]);
|
||||
p->in_pin = conv->f->pins[1];
|
||||
|
||||
return f;
|
||||
af->control = control;
|
||||
af->filter_frame = filter_frame;
|
||||
af->filter_out = filter_out;
|
||||
af->uninit = uninit;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct f_opts
|
||||
|
||||
const struct mp_user_filter_entry af_rubberband = {
|
||||
.desc = {
|
||||
.description = "Pitch conversion with librubberband",
|
||||
.name = "rubberband",
|
||||
.priv_size = sizeof(OPT_BASE_STRUCT),
|
||||
.priv_defaults = &(const OPT_BASE_STRUCT) {
|
||||
.scale = 1.0,
|
||||
.pitch = RubberBandOptionPitchHighConsistency,
|
||||
.transients = RubberBandOptionTransientsMixed,
|
||||
.formant = RubberBandOptionFormantPreserved,
|
||||
.channels = RubberBandOptionChannelsTogether,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_CHOICE("transients", transients, 0,
|
||||
({"crisp", RubberBandOptionTransientsCrisp},
|
||||
{"mixed", RubberBandOptionTransientsMixed},
|
||||
{"smooth", RubberBandOptionTransientsSmooth})),
|
||||
OPT_CHOICE("detector", detector, 0,
|
||||
({"compound", RubberBandOptionDetectorCompound},
|
||||
{"percussive", RubberBandOptionDetectorPercussive},
|
||||
{"soft", RubberBandOptionDetectorSoft})),
|
||||
OPT_CHOICE("phase", phase, 0,
|
||||
({"laminar", RubberBandOptionPhaseLaminar},
|
||||
{"independent", RubberBandOptionPhaseIndependent})),
|
||||
OPT_CHOICE("window", window, 0,
|
||||
({"standard", RubberBandOptionWindowStandard},
|
||||
{"short", RubberBandOptionWindowShort},
|
||||
{"long", RubberBandOptionWindowLong})),
|
||||
OPT_CHOICE("smoothing", smoothing, 0,
|
||||
({"off", RubberBandOptionSmoothingOff},
|
||||
{"on", RubberBandOptionSmoothingOn})),
|
||||
OPT_CHOICE("formant", formant, 0,
|
||||
({"shifted", RubberBandOptionFormantShifted},
|
||||
{"preserved", RubberBandOptionFormantPreserved})),
|
||||
OPT_CHOICE("pitch", pitch, 0,
|
||||
({"quality", RubberBandOptionPitchHighQuality},
|
||||
{"speed", RubberBandOptionPitchHighSpeed},
|
||||
{"consistency", RubberBandOptionPitchHighConsistency})),
|
||||
OPT_CHOICE("channels", channels, 0,
|
||||
({"apart", RubberBandOptionChannelsApart},
|
||||
{"together", RubberBandOptionChannelsTogether})),
|
||||
OPT_DOUBLE("pitch-scale", scale, M_OPT_RANGE, .min = 0.01, .max = 100),
|
||||
{0}
|
||||
},
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
const struct af_info af_info_rubberband = {
|
||||
.info = "Pitch conversion with librubberband",
|
||||
.name = "rubberband",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.priv_defaults = &(const struct priv) {
|
||||
.speed = 1.0,
|
||||
.pitch = 1.0,
|
||||
.opt_pitch = RubberBandOptionPitchHighConsistency,
|
||||
.opt_transients = RubberBandOptionTransientsMixed,
|
||||
.opt_formant = RubberBandOptionFormantPreserved,
|
||||
.opt_channels = RubberBandOptionChannelsTogether,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_CHOICE("transients", opt_transients, 0,
|
||||
({"crisp", RubberBandOptionTransientsCrisp},
|
||||
{"mixed", RubberBandOptionTransientsMixed},
|
||||
{"smooth", RubberBandOptionTransientsSmooth})),
|
||||
OPT_CHOICE("detector", opt_detector, 0,
|
||||
({"compound", RubberBandOptionDetectorCompound},
|
||||
{"percussive", RubberBandOptionDetectorPercussive},
|
||||
{"soft", RubberBandOptionDetectorSoft})),
|
||||
OPT_CHOICE("phase", opt_phase, 0,
|
||||
({"laminar", RubberBandOptionPhaseLaminar},
|
||||
{"independent", RubberBandOptionPhaseIndependent})),
|
||||
OPT_CHOICE("window", opt_window, 0,
|
||||
({"standard", RubberBandOptionWindowStandard},
|
||||
{"short", RubberBandOptionWindowShort},
|
||||
{"long", RubberBandOptionWindowLong})),
|
||||
OPT_CHOICE("smoothing", opt_smoothing, 0,
|
||||
({"off", RubberBandOptionSmoothingOff},
|
||||
{"on", RubberBandOptionSmoothingOn})),
|
||||
OPT_CHOICE("formant", opt_formant, 0,
|
||||
({"shifted", RubberBandOptionFormantShifted},
|
||||
{"preserved", RubberBandOptionFormantPreserved})),
|
||||
OPT_CHOICE("pitch", opt_pitch, 0,
|
||||
({"quality", RubberBandOptionPitchHighQuality},
|
||||
{"speed", RubberBandOptionPitchHighSpeed},
|
||||
{"consistency", RubberBandOptionPitchHighConsistency})),
|
||||
OPT_CHOICE("channels", opt_channels, 0,
|
||||
({"apart", RubberBandOptionChannelsApart},
|
||||
{"together", RubberBandOptionChannelsTogether})),
|
||||
OPT_DOUBLE("pitch-scale", pitch, M_OPT_RANGE, .min = 0.01, .max = 100),
|
||||
{0}
|
||||
},
|
||||
.create = af_rubberband_create,
|
||||
};
|
||||
+248
-378
@@ -35,33 +35,14 @@
|
||||
#include <limits.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "audio/aframe.h"
|
||||
#include "audio/format.h"
|
||||
#include "common/common.h"
|
||||
#include "filters/f_autoconvert.h"
|
||||
#include "filters/filter_internal.h"
|
||||
#include "filters/user_filters.h"
|
||||
|
||||
#include "af.h"
|
||||
#include "options/m_option.h"
|
||||
|
||||
struct f_opts {
|
||||
float scale_nominal;
|
||||
float ms_stride;
|
||||
float ms_search;
|
||||
float percent_overlap;
|
||||
#define SCALE_TEMPO 1
|
||||
#define SCALE_PITCH 2
|
||||
int speed_opt;
|
||||
};
|
||||
|
||||
struct priv {
|
||||
struct f_opts *opts;
|
||||
|
||||
struct mp_pin *in_pin;
|
||||
struct mp_aframe *cur_format;
|
||||
struct mp_aframe_pool *out_pool;
|
||||
double current_pts;
|
||||
struct mp_aframe *in;
|
||||
|
||||
// Data for specific instances of this filter
|
||||
typedef struct af_scaletempo_s
|
||||
{
|
||||
// stride
|
||||
float scale;
|
||||
float speed;
|
||||
@@ -81,23 +62,29 @@ struct priv {
|
||||
int bytes_standing;
|
||||
void *buf_overlap;
|
||||
void *table_blend;
|
||||
void (*output_overlap)(struct priv *s, void *out_buf,
|
||||
void (*output_overlap)(struct af_scaletempo_s *s, void *out_buf,
|
||||
int bytes_off);
|
||||
// best overlap
|
||||
int frames_search;
|
||||
int num_channels;
|
||||
void *buf_pre_corr;
|
||||
void *table_window;
|
||||
int (*best_overlap_offset)(struct priv *s);
|
||||
};
|
||||
int (*best_overlap_offset)(struct af_scaletempo_s *s);
|
||||
// command line
|
||||
float scale_nominal;
|
||||
float ms_stride;
|
||||
float percent_overlap;
|
||||
float ms_search;
|
||||
#define SCALE_TEMPO 1
|
||||
#define SCALE_PITCH 2
|
||||
int speed_opt;
|
||||
} af_scaletempo_t;
|
||||
|
||||
static bool reinit(struct mp_filter *f);
|
||||
|
||||
// Return whether it got enough data for filtering.
|
||||
static bool fill_queue(struct priv *s)
|
||||
static int fill_queue(struct af_instance *af, struct mp_audio *data, int offset)
|
||||
{
|
||||
int bytes_in = s->in ? mp_aframe_get_size(s->in) * s->bytes_per_frame : 0;
|
||||
int offset = 0;
|
||||
af_scaletempo_t *s = af->priv;
|
||||
int bytes_in = (data ? mp_audio_psize(data) : 0) - offset;
|
||||
int offset_unchanged = offset;
|
||||
|
||||
if (s->bytes_to_slide > 0) {
|
||||
if (s->bytes_to_slide < s->bytes_queued) {
|
||||
@@ -116,27 +103,21 @@ static bool fill_queue(struct priv *s)
|
||||
}
|
||||
}
|
||||
|
||||
int bytes_needed = s->bytes_queue - s->bytes_queued;
|
||||
assert(bytes_needed >= 0);
|
||||
|
||||
int bytes_copy = MPMIN(bytes_needed, bytes_in);
|
||||
if (bytes_copy > 0) {
|
||||
uint8_t **planes = mp_aframe_get_data_ro(s->in);
|
||||
memcpy(s->buf_queue + s->bytes_queued, planes[0] + offset, bytes_copy);
|
||||
if (bytes_in > 0) {
|
||||
int bytes_copy = MPMIN(s->bytes_queue - s->bytes_queued, bytes_in);
|
||||
assert(bytes_copy >= 0);
|
||||
memcpy(s->buf_queue + s->bytes_queued,
|
||||
(int8_t *)data->planes[0] + offset, bytes_copy);
|
||||
s->bytes_queued += bytes_copy;
|
||||
offset += bytes_copy;
|
||||
bytes_needed -= bytes_copy;
|
||||
}
|
||||
|
||||
if (s->in)
|
||||
mp_aframe_skip_samples(s->in, offset / s->bytes_per_frame);
|
||||
|
||||
return bytes_needed == 0;
|
||||
return offset - offset_unchanged;
|
||||
}
|
||||
|
||||
#define UNROLL_PADDING (4 * 4)
|
||||
|
||||
static int best_overlap_offset_float(struct priv *s)
|
||||
static int best_overlap_offset_float(af_scaletempo_t *s)
|
||||
{
|
||||
float best_corr = INT_MIN;
|
||||
int best_off = 0;
|
||||
@@ -165,7 +146,7 @@ static int best_overlap_offset_float(struct priv *s)
|
||||
return best_off * 4 * s->num_channels;
|
||||
}
|
||||
|
||||
static int best_overlap_offset_s16(struct priv *s)
|
||||
static int best_overlap_offset_s16(af_scaletempo_t *s)
|
||||
{
|
||||
int64_t best_corr = INT64_MIN;
|
||||
int best_off = 0;
|
||||
@@ -202,7 +183,7 @@ static int best_overlap_offset_s16(struct priv *s)
|
||||
return best_off * 2 * s->num_channels;
|
||||
}
|
||||
|
||||
static void output_overlap_float(struct priv *s, void *buf_out,
|
||||
static void output_overlap_float(af_scaletempo_t *s, void *buf_out,
|
||||
int bytes_off)
|
||||
{
|
||||
float *pout = buf_out;
|
||||
@@ -215,7 +196,7 @@ static void output_overlap_float(struct priv *s, void *buf_out,
|
||||
}
|
||||
}
|
||||
|
||||
static void output_overlap_s16(struct priv *s, void *buf_out,
|
||||
static void output_overlap_s16(af_scaletempo_t *s, void *buf_out,
|
||||
int bytes_off)
|
||||
{
|
||||
int16_t *pout = buf_out;
|
||||
@@ -228,77 +209,29 @@ static void output_overlap_s16(struct priv *s, void *buf_out,
|
||||
}
|
||||
}
|
||||
|
||||
static void process(struct mp_filter *f)
|
||||
static int filter(struct af_instance *af, struct mp_audio *data)
|
||||
{
|
||||
struct priv *s = f->priv;
|
||||
af_scaletempo_t *s = af->priv;
|
||||
|
||||
if (!mp_pin_in_needs_data(f->ppins[1]))
|
||||
return;
|
||||
|
||||
struct mp_aframe *out = NULL;
|
||||
|
||||
bool drain = false;
|
||||
bool is_eof = false;
|
||||
if (!s->in) {
|
||||
struct mp_frame frame = mp_pin_out_read(s->in_pin);
|
||||
if (!frame.type)
|
||||
return; // no input yet
|
||||
if (frame.type != MP_FRAME_AUDIO && frame.type != MP_FRAME_EOF) {
|
||||
MP_ERR(f, "unexpected frame type\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
s->in = frame.type == MP_FRAME_AUDIO ? frame.data : NULL;
|
||||
is_eof = drain = !s->in;
|
||||
|
||||
// EOF before it was even initialized once.
|
||||
if (is_eof && !mp_aframe_config_is_valid(s->cur_format)) {
|
||||
mp_pin_in_write(f->ppins[1], MP_EOF_FRAME);
|
||||
return;
|
||||
}
|
||||
|
||||
if (s->in && !mp_aframe_config_equals(s->in, s->cur_format)) {
|
||||
if (s->bytes_queued) {
|
||||
// Drain remaining data before executing the format change.
|
||||
MP_VERBOSE(f, "draining\n");
|
||||
mp_pin_out_unread(s->in_pin, frame);
|
||||
s->in = NULL;
|
||||
drain = true;
|
||||
} else {
|
||||
if (!reinit(f)) {
|
||||
MP_ERR(f, "initialization failed\n");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (s->in)
|
||||
s->current_pts = mp_aframe_end_pts(s->in);
|
||||
if (s->scale == 1.0) {
|
||||
af->delay = 0;
|
||||
af_add_output_frame(af, data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!fill_queue(s) && !drain) {
|
||||
TA_FREEP(&s->in);
|
||||
mp_pin_out_request_data_next(s->in_pin);
|
||||
return;
|
||||
int in_samples = data ? data->samples : 0;
|
||||
struct mp_audio *out = mp_audio_pool_get(af->out_pool, af->data,
|
||||
((int)(in_samples / s->frames_stride_scaled) + 1) * s->frames_stride);
|
||||
if (!out) {
|
||||
talloc_free(data);
|
||||
return -1;
|
||||
}
|
||||
if (data)
|
||||
mp_audio_copy_attributes(out, data);
|
||||
|
||||
int max_out_samples = s->bytes_stride / s->bytes_per_frame;
|
||||
if (drain)
|
||||
max_out_samples += s->bytes_queued;
|
||||
|
||||
out = mp_aframe_new_ref(s->cur_format);
|
||||
if (mp_aframe_pool_allocate(s->out_pool, out, max_out_samples) < 0)
|
||||
goto error;
|
||||
|
||||
if (s->in)
|
||||
mp_aframe_copy_attributes(out, s->in);
|
||||
|
||||
uint8_t **out_planes = mp_aframe_get_data_rw(out);
|
||||
if (!out_planes)
|
||||
goto error;
|
||||
int8_t *pout = out_planes[0];
|
||||
int out_offset = 0;
|
||||
if (s->bytes_queued >= s->bytes_queue) {
|
||||
int offset_in = fill_queue(af, data, 0);
|
||||
int8_t *pout = out->planes[0];
|
||||
while (s->bytes_queued >= s->bytes_queue) {
|
||||
int ti;
|
||||
float tf;
|
||||
int bytes_off = 0;
|
||||
@@ -307,12 +240,12 @@ static void process(struct mp_filter *f)
|
||||
if (s->output_overlap) {
|
||||
if (s->best_overlap_offset)
|
||||
bytes_off = s->best_overlap_offset(s);
|
||||
s->output_overlap(s, pout + out_offset, bytes_off);
|
||||
s->output_overlap(s, pout, bytes_off);
|
||||
}
|
||||
memcpy(pout + out_offset + s->bytes_overlap,
|
||||
memcpy(pout + s->bytes_overlap,
|
||||
s->buf_queue + bytes_off + s->bytes_overlap,
|
||||
s->bytes_standing);
|
||||
out_offset += s->bytes_stride;
|
||||
pout += s->bytes_stride;
|
||||
|
||||
// input stride
|
||||
memcpy(s->buf_overlap,
|
||||
@@ -322,303 +255,240 @@ static void process(struct mp_filter *f)
|
||||
ti = (int)tf;
|
||||
s->frames_stride_error = tf - ti;
|
||||
s->bytes_to_slide = ti * s->bytes_per_frame;
|
||||
|
||||
offset_in += fill_queue(af, data, offset_in);
|
||||
}
|
||||
// Drain remaining buffered data.
|
||||
if (drain && s->bytes_queued) {
|
||||
memcpy(pout + out_offset, s->buf_queue, s->bytes_queued);
|
||||
out_offset += s->bytes_queued;
|
||||
s->bytes_queued = 0;
|
||||
}
|
||||
mp_aframe_set_size(out, out_offset / s->bytes_per_frame);
|
||||
|
||||
// This filter can have a negative delay when scale > 1:
|
||||
// output corresponding to some length of input can be decided and written
|
||||
// after receiving only a part of that input.
|
||||
float delay = (out_offset * s->speed + s->bytes_queued - s->bytes_to_slide) /
|
||||
s->bytes_per_frame / mp_aframe_get_effective_rate(out)
|
||||
+ (s->in ? mp_aframe_duration(s->in) : 0);
|
||||
af->delay = (s->bytes_queued - s->bytes_to_slide) / s->scale
|
||||
/ out->sstride / out->rate;
|
||||
|
||||
if (s->current_pts != MP_NOPTS_VALUE)
|
||||
mp_aframe_set_pts(out, s->current_pts - delay);
|
||||
|
||||
mp_aframe_mul_speed(out, s->speed);
|
||||
|
||||
if (!mp_aframe_get_size(out))
|
||||
TA_FREEP(&out);
|
||||
|
||||
if (is_eof && out) {
|
||||
mp_pin_out_repeat_eof(s->in_pin);
|
||||
} else if (is_eof && !out) {
|
||||
mp_pin_in_write(f->ppins[1], MP_EOF_FRAME);
|
||||
} else if (!is_eof && !out) {
|
||||
mp_pin_out_request_data_next(s->in_pin);
|
||||
out->samples = (pout - (int8_t *)out->planes[0]) / out->sstride;
|
||||
talloc_free(data);
|
||||
if (out->samples) {
|
||||
af_add_output_frame(af, out);
|
||||
} else {
|
||||
talloc_free(out);
|
||||
}
|
||||
|
||||
if (out)
|
||||
mp_pin_in_write(f->ppins[1], MAKE_FRAME(MP_FRAME_AUDIO, out));
|
||||
|
||||
return;
|
||||
|
||||
error:
|
||||
TA_FREEP(&s->in);
|
||||
talloc_free(out);
|
||||
mp_filter_internal_mark_failed(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void update_speed(struct priv *s, float speed)
|
||||
static void update_speed(struct af_instance *af, float speed)
|
||||
{
|
||||
af_scaletempo_t *s = af->priv;
|
||||
|
||||
s->speed = speed;
|
||||
|
||||
double factor = (s->opts->speed_opt & SCALE_PITCH) ? 1.0 / s->speed : s->speed;
|
||||
s->scale = factor * s->opts->scale_nominal;
|
||||
double factor = (s->speed_opt & SCALE_PITCH) ? 1.0 / s->speed : s->speed;
|
||||
s->scale = factor * s->scale_nominal;
|
||||
|
||||
s->frames_stride_scaled = s->scale * s->frames_stride;
|
||||
s->frames_stride_error = MPMIN(s->frames_stride_error, s->frames_stride_scaled);
|
||||
}
|
||||
|
||||
static bool reinit(struct mp_filter *f)
|
||||
// Initialization and runtime control
|
||||
static int control(struct af_instance *af, int cmd, void *arg)
|
||||
{
|
||||
struct priv *s = f->priv;
|
||||
af_scaletempo_t *s = af->priv;
|
||||
switch (cmd) {
|
||||
case AF_CONTROL_REINIT: {
|
||||
struct mp_audio *data = (struct mp_audio *)arg;
|
||||
float srate = data->rate / 1000.0;
|
||||
int nch = data->nch;
|
||||
int use_int = 0;
|
||||
|
||||
mp_aframe_reset(s->cur_format);
|
||||
mp_audio_force_interleaved_format(data);
|
||||
mp_audio_copy_config(af->data, data);
|
||||
|
||||
float srate = mp_aframe_get_rate(s->in) / 1000.0;
|
||||
int nch = mp_aframe_get_channels(s->in);
|
||||
int format = mp_aframe_get_format(s->in);
|
||||
|
||||
int use_int = 0;
|
||||
if (format == AF_FORMAT_S16) {
|
||||
use_int = 1;
|
||||
} else if (format != AF_FORMAT_FLOAT) {
|
||||
return false;
|
||||
}
|
||||
int bps = use_int ? 2 : 4;
|
||||
|
||||
s->frames_stride = srate * s->opts->ms_stride;
|
||||
s->bytes_stride = s->frames_stride * bps * nch;
|
||||
|
||||
update_speed(s, s->speed);
|
||||
|
||||
int frames_overlap = s->frames_stride * s->opts->percent_overlap;
|
||||
if (frames_overlap <= 0) {
|
||||
s->bytes_standing = s->bytes_stride;
|
||||
s->samples_standing = s->bytes_standing / bps;
|
||||
s->output_overlap = NULL;
|
||||
s->bytes_overlap = 0;
|
||||
} else {
|
||||
s->samples_overlap = frames_overlap * nch;
|
||||
s->bytes_overlap = frames_overlap * nch * bps;
|
||||
s->bytes_standing = s->bytes_stride - s->bytes_overlap;
|
||||
s->samples_standing = s->bytes_standing / bps;
|
||||
s->buf_overlap = realloc(s->buf_overlap, s->bytes_overlap);
|
||||
s->table_blend = realloc(s->table_blend, s->bytes_overlap * 4);
|
||||
if (!s->buf_overlap || !s->table_blend) {
|
||||
MP_FATAL(f, "Out of memory\n");
|
||||
return false;
|
||||
if (data->format == AF_FORMAT_S16) {
|
||||
use_int = 1;
|
||||
} else {
|
||||
mp_audio_set_format(af->data, AF_FORMAT_FLOAT);
|
||||
}
|
||||
int bps = af->data->bps;
|
||||
|
||||
s->frames_stride = srate * s->ms_stride;
|
||||
s->bytes_stride = s->frames_stride * bps * nch;
|
||||
af->delay = 0;
|
||||
|
||||
update_speed(af, s->speed);
|
||||
|
||||
int frames_overlap = s->frames_stride * s->percent_overlap;
|
||||
if (frames_overlap <= 0) {
|
||||
s->bytes_standing = s->bytes_stride;
|
||||
s->samples_standing = s->bytes_standing / bps;
|
||||
s->output_overlap = NULL;
|
||||
s->bytes_overlap = 0;
|
||||
} else {
|
||||
s->samples_overlap = frames_overlap * nch;
|
||||
s->bytes_overlap = frames_overlap * nch * bps;
|
||||
s->bytes_standing = s->bytes_stride - s->bytes_overlap;
|
||||
s->samples_standing = s->bytes_standing / bps;
|
||||
s->buf_overlap = realloc(s->buf_overlap, s->bytes_overlap);
|
||||
s->table_blend = realloc(s->table_blend, s->bytes_overlap * 4);
|
||||
if (!s->buf_overlap || !s->table_blend) {
|
||||
MP_FATAL(af, "Out of memory\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
memset(s->buf_overlap, 0, s->bytes_overlap);
|
||||
if (use_int) {
|
||||
int32_t *pb = s->table_blend;
|
||||
int64_t blend = 0;
|
||||
for (int i = 0; i < frames_overlap; i++) {
|
||||
int32_t v = blend / frames_overlap;
|
||||
for (int j = 0; j < nch; j++)
|
||||
*pb++ = v;
|
||||
blend += 65536; // 2^16
|
||||
}
|
||||
s->output_overlap = output_overlap_s16;
|
||||
} else {
|
||||
float *pb = s->table_blend;
|
||||
for (int i = 0; i < frames_overlap; i++) {
|
||||
float v = i / (float)frames_overlap;
|
||||
for (int j = 0; j < nch; j++)
|
||||
*pb++ = v;
|
||||
}
|
||||
s->output_overlap = output_overlap_float;
|
||||
}
|
||||
}
|
||||
|
||||
s->frames_search = (frames_overlap > 1) ? srate * s->ms_search : 0;
|
||||
if (s->frames_search <= 0)
|
||||
s->best_overlap_offset = NULL;
|
||||
else {
|
||||
if (use_int) {
|
||||
int64_t t = frames_overlap;
|
||||
int32_t n = 8589934588LL / (t * t); // 4 * (2^31 - 1) / t^2
|
||||
s->buf_pre_corr = realloc(s->buf_pre_corr,
|
||||
s->bytes_overlap * 2 + UNROLL_PADDING);
|
||||
s->table_window = realloc(s->table_window,
|
||||
s->bytes_overlap * 2 - nch * bps * 2);
|
||||
if (!s->buf_pre_corr || !s->table_window) {
|
||||
MP_FATAL(af, "Out of memory\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
memset((char *)s->buf_pre_corr + s->bytes_overlap * 2, 0,
|
||||
UNROLL_PADDING);
|
||||
int32_t *pw = s->table_window;
|
||||
for (int i = 1; i < frames_overlap; i++) {
|
||||
int32_t v = (i * (t - i) * n) >> 15;
|
||||
for (int j = 0; j < nch; j++)
|
||||
*pw++ = v;
|
||||
}
|
||||
s->best_overlap_offset = best_overlap_offset_s16;
|
||||
} else {
|
||||
s->buf_pre_corr = realloc(s->buf_pre_corr, s->bytes_overlap);
|
||||
s->table_window = realloc(s->table_window,
|
||||
s->bytes_overlap - nch * bps);
|
||||
if (!s->buf_pre_corr || !s->table_window) {
|
||||
MP_FATAL(af, "Out of memory\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
float *pw = s->table_window;
|
||||
for (int i = 1; i < frames_overlap; i++) {
|
||||
float v = i * (frames_overlap - i);
|
||||
for (int j = 0; j < nch; j++)
|
||||
*pw++ = v;
|
||||
}
|
||||
s->best_overlap_offset = best_overlap_offset_float;
|
||||
}
|
||||
}
|
||||
|
||||
s->bytes_per_frame = bps * nch;
|
||||
s->num_channels = nch;
|
||||
|
||||
s->bytes_queue = (s->frames_search + s->frames_stride + frames_overlap)
|
||||
* bps * nch;
|
||||
s->buf_queue = realloc(s->buf_queue, s->bytes_queue + UNROLL_PADDING);
|
||||
if (!s->buf_queue) {
|
||||
MP_FATAL(af, "Out of memory\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
s->bytes_queued = 0;
|
||||
s->bytes_to_slide = 0;
|
||||
|
||||
MP_DBG(af, ""
|
||||
"%.2f stride_in, %i stride_out, %i standing, "
|
||||
"%i overlap, %i search, %i queue, %s mode\n",
|
||||
s->frames_stride_scaled,
|
||||
(int)(s->bytes_stride / nch / bps),
|
||||
(int)(s->bytes_standing / nch / bps),
|
||||
(int)(s->bytes_overlap / nch / bps),
|
||||
s->frames_search,
|
||||
(int)(s->bytes_queue / nch / bps),
|
||||
(use_int ? "s16" : "float"));
|
||||
|
||||
return af_test_output(af, (struct mp_audio *)arg);
|
||||
}
|
||||
case AF_CONTROL_SET_PLAYBACK_SPEED: {
|
||||
double speed = *(double *)arg;
|
||||
if (s->speed_opt & SCALE_TEMPO) {
|
||||
if (s->speed_opt & SCALE_PITCH)
|
||||
break;
|
||||
update_speed(af, speed);
|
||||
} else if (s->speed_opt & SCALE_PITCH) {
|
||||
update_speed(af, speed);
|
||||
break; // do not signal OK
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_RESET:
|
||||
s->bytes_queued = 0;
|
||||
s->bytes_to_slide = 0;
|
||||
s->frames_stride_error = 0;
|
||||
memset(s->buf_overlap, 0, s->bytes_overlap);
|
||||
if (use_int) {
|
||||
int32_t *pb = s->table_blend;
|
||||
int64_t blend = 0;
|
||||
for (int i = 0; i < frames_overlap; i++) {
|
||||
int32_t v = blend / frames_overlap;
|
||||
for (int j = 0; j < nch; j++)
|
||||
*pb++ = v;
|
||||
blend += 65536; // 2^16
|
||||
}
|
||||
s->output_overlap = output_overlap_s16;
|
||||
} else {
|
||||
float *pb = s->table_blend;
|
||||
for (int i = 0; i < frames_overlap; i++) {
|
||||
float v = i / (float)frames_overlap;
|
||||
for (int j = 0; j < nch; j++)
|
||||
*pb++ = v;
|
||||
}
|
||||
s->output_overlap = output_overlap_float;
|
||||
}
|
||||
}
|
||||
|
||||
s->frames_search = (frames_overlap > 1) ? srate * s->opts->ms_search : 0;
|
||||
if (s->frames_search <= 0)
|
||||
s->best_overlap_offset = NULL;
|
||||
else {
|
||||
if (use_int) {
|
||||
int64_t t = frames_overlap;
|
||||
int32_t n = 8589934588LL / (t * t); // 4 * (2^31 - 1) / t^2
|
||||
s->buf_pre_corr = realloc(s->buf_pre_corr,
|
||||
s->bytes_overlap * 2 + UNROLL_PADDING);
|
||||
s->table_window = realloc(s->table_window,
|
||||
s->bytes_overlap * 2 - nch * bps * 2);
|
||||
if (!s->buf_pre_corr || !s->table_window) {
|
||||
MP_FATAL(f, "Out of memory\n");
|
||||
return false;
|
||||
}
|
||||
memset((char *)s->buf_pre_corr + s->bytes_overlap * 2, 0,
|
||||
UNROLL_PADDING);
|
||||
int32_t *pw = s->table_window;
|
||||
for (int i = 1; i < frames_overlap; i++) {
|
||||
int32_t v = (i * (t - i) * n) >> 15;
|
||||
for (int j = 0; j < nch; j++)
|
||||
*pw++ = v;
|
||||
}
|
||||
s->best_overlap_offset = best_overlap_offset_s16;
|
||||
} else {
|
||||
s->buf_pre_corr = realloc(s->buf_pre_corr, s->bytes_overlap);
|
||||
s->table_window = realloc(s->table_window,
|
||||
s->bytes_overlap - nch * bps);
|
||||
if (!s->buf_pre_corr || !s->table_window) {
|
||||
MP_FATAL(f, "Out of memory\n");
|
||||
return false;
|
||||
}
|
||||
float *pw = s->table_window;
|
||||
for (int i = 1; i < frames_overlap; i++) {
|
||||
float v = i * (frames_overlap - i);
|
||||
for (int j = 0; j < nch; j++)
|
||||
*pw++ = v;
|
||||
}
|
||||
s->best_overlap_offset = best_overlap_offset_float;
|
||||
}
|
||||
}
|
||||
|
||||
s->bytes_per_frame = bps * nch;
|
||||
s->num_channels = nch;
|
||||
|
||||
s->bytes_queue = (s->frames_search + s->frames_stride + frames_overlap)
|
||||
* bps * nch;
|
||||
s->buf_queue = realloc(s->buf_queue, s->bytes_queue + UNROLL_PADDING);
|
||||
if (!s->buf_queue) {
|
||||
MP_FATAL(f, "Out of memory\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
s->bytes_queued = 0;
|
||||
s->bytes_to_slide = 0;
|
||||
|
||||
MP_DBG(f, ""
|
||||
"%.2f stride_in, %i stride_out, %i standing, "
|
||||
"%i overlap, %i search, %i queue, %s mode\n",
|
||||
s->frames_stride_scaled,
|
||||
(int)(s->bytes_stride / nch / bps),
|
||||
(int)(s->bytes_standing / nch / bps),
|
||||
(int)(s->bytes_overlap / nch / bps),
|
||||
s->frames_search,
|
||||
(int)(s->bytes_queue / nch / bps),
|
||||
(use_int ? "s16" : "float"));
|
||||
|
||||
mp_aframe_config_copy(s->cur_format, s->in);
|
||||
|
||||
return true;
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static bool command(struct mp_filter *f, struct mp_filter_command *cmd)
|
||||
// Deallocate memory
|
||||
static void uninit(struct af_instance *af)
|
||||
{
|
||||
struct priv *s = f->priv;
|
||||
|
||||
if (cmd->type == MP_FILTER_COMMAND_SET_SPEED) {
|
||||
if (s->opts->speed_opt & SCALE_TEMPO) {
|
||||
if (s->opts->speed_opt & SCALE_PITCH)
|
||||
return false;
|
||||
update_speed(s, cmd->speed);
|
||||
return true;
|
||||
} else if (s->opts->speed_opt & SCALE_PITCH) {
|
||||
update_speed(s, cmd->speed);
|
||||
return false; // do not signal OK
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void reset(struct mp_filter *f)
|
||||
{
|
||||
struct priv *s = f->priv;
|
||||
|
||||
s->current_pts = MP_NOPTS_VALUE;
|
||||
s->bytes_queued = 0;
|
||||
s->bytes_to_slide = 0;
|
||||
s->frames_stride_error = 0;
|
||||
memset(s->buf_overlap, 0, s->bytes_overlap);
|
||||
TA_FREEP(&s->in);
|
||||
}
|
||||
|
||||
static void destroy(struct mp_filter *f)
|
||||
{
|
||||
struct priv *s = f->priv;
|
||||
af_scaletempo_t *s = af->priv;
|
||||
free(s->buf_queue);
|
||||
free(s->buf_overlap);
|
||||
free(s->buf_pre_corr);
|
||||
free(s->table_blend);
|
||||
free(s->table_window);
|
||||
TA_FREEP(&s->in);
|
||||
mp_filter_free_children(f);
|
||||
}
|
||||
|
||||
static const struct mp_filter_info af_scaletempo_filter = {
|
||||
.name = "scaletempo",
|
||||
.priv_size = sizeof(struct priv),
|
||||
.process = process,
|
||||
.command = command,
|
||||
.reset = reset,
|
||||
.destroy = destroy,
|
||||
};
|
||||
|
||||
static struct mp_filter *af_scaletempo_create(struct mp_filter *parent,
|
||||
void *options)
|
||||
// Allocate memory and set function pointers
|
||||
static int af_open(struct af_instance *af)
|
||||
{
|
||||
struct mp_filter *f = mp_filter_create(parent, &af_scaletempo_filter);
|
||||
if (!f) {
|
||||
talloc_free(options);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
mp_filter_add_pin(f, MP_PIN_IN, "in");
|
||||
mp_filter_add_pin(f, MP_PIN_OUT, "out");
|
||||
|
||||
struct priv *s = f->priv;
|
||||
s->opts = talloc_steal(s, options);
|
||||
s->speed = 1.0;
|
||||
s->cur_format = talloc_steal(s, mp_aframe_create());
|
||||
s->out_pool = mp_aframe_pool_create(s);
|
||||
|
||||
struct mp_autoconvert *conv = mp_autoconvert_create(f);
|
||||
if (!conv)
|
||||
abort();
|
||||
|
||||
mp_autoconvert_add_afmt(conv, AF_FORMAT_S16);
|
||||
mp_autoconvert_add_afmt(conv, AF_FORMAT_FLOAT);
|
||||
|
||||
mp_pin_connect(conv->f->pins[0], f->ppins[0]);
|
||||
s->in_pin = conv->f->pins[1];
|
||||
|
||||
return f;
|
||||
af->control = control;
|
||||
af->uninit = uninit;
|
||||
af->filter_frame = filter;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct f_opts
|
||||
#define OPT_BASE_STRUCT af_scaletempo_t
|
||||
|
||||
const struct mp_user_filter_entry af_scaletempo = {
|
||||
.desc = {
|
||||
.description = "Scale audio tempo while maintaining pitch",
|
||||
.name = "scaletempo",
|
||||
.priv_size = sizeof(OPT_BASE_STRUCT),
|
||||
.priv_defaults = &(const OPT_BASE_STRUCT) {
|
||||
.ms_stride = 60,
|
||||
.percent_overlap = .20,
|
||||
.ms_search = 14,
|
||||
.speed_opt = SCALE_TEMPO,
|
||||
.scale_nominal = 1.0,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_FLOAT("scale", scale_nominal, M_OPT_MIN, .min = 0.01),
|
||||
OPT_FLOAT("stride", ms_stride, M_OPT_MIN, .min = 0.01),
|
||||
OPT_FLOAT("overlap", percent_overlap, M_OPT_RANGE, .min = 0, .max = 1),
|
||||
OPT_FLOAT("search", ms_search, M_OPT_MIN, .min = 0),
|
||||
OPT_CHOICE("speed", speed_opt, 0,
|
||||
({"pitch", SCALE_PITCH},
|
||||
{"tempo", SCALE_TEMPO},
|
||||
{"none", 0},
|
||||
{"both", SCALE_TEMPO | SCALE_PITCH})),
|
||||
{0}
|
||||
},
|
||||
const struct af_info af_info_scaletempo = {
|
||||
.info = "Scale audio tempo while maintaining pitch",
|
||||
.name = "scaletempo",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(af_scaletempo_t),
|
||||
.priv_defaults = &(const af_scaletempo_t) {
|
||||
.ms_stride = 60,
|
||||
.percent_overlap = .20,
|
||||
.ms_search = 14,
|
||||
.speed_opt = SCALE_TEMPO,
|
||||
.speed = 1.0,
|
||||
.scale_nominal = 1.0,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_FLOAT("scale", scale_nominal, M_OPT_MIN, .min = 0.01),
|
||||
OPT_FLOAT("stride", ms_stride, M_OPT_MIN, .min = 0.01),
|
||||
OPT_FLOAT("overlap", percent_overlap, M_OPT_RANGE, .min = 0, .max = 1),
|
||||
OPT_FLOAT("search", ms_search, M_OPT_MIN, .min = 0),
|
||||
OPT_CHOICE("speed", speed_opt, 0,
|
||||
({"pitch", SCALE_PITCH},
|
||||
{"tempo", SCALE_TEMPO},
|
||||
{"none", 0},
|
||||
{"both", SCALE_TEMPO | SCALE_PITCH})),
|
||||
{0}
|
||||
},
|
||||
.create = af_scaletempo_create,
|
||||
};
|
||||
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <math.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "af.h"
|
||||
#include "demux/demux.h"
|
||||
|
||||
struct priv {
|
||||
float level; // User-specified gain level for each channel
|
||||
float rgain; // Replaygain level
|
||||
int rgain_track; // Enable/disable track based replaygain
|
||||
int rgain_album; // Enable/disable album based replaygain
|
||||
float rgain_preamp; // Set replaygain pre-amplification
|
||||
int rgain_clip; // Enable/disable clipping prevention
|
||||
float replaygain_fallback;
|
||||
int soft; // Enable/disable soft clipping
|
||||
int fast; // Use fix-point volume control
|
||||
int detach; // Detach if gain volume is neutral
|
||||
float cfg_volume;
|
||||
int warn;
|
||||
};
|
||||
|
||||
// Convert to gain value from dB. input <= -200dB will become 0 gain.
|
||||
static float from_dB(float in, float k, float mi, float ma)
|
||||
{
|
||||
if (in <= -200)
|
||||
return 0.0;
|
||||
return pow(10.0, MPCLAMP(in, mi, ma) / k);
|
||||
}
|
||||
|
||||
static int control(struct af_instance *af, int cmd, void *arg)
|
||||
{
|
||||
struct priv *s = af->priv;
|
||||
|
||||
switch (cmd) {
|
||||
case AF_CONTROL_REINIT: {
|
||||
struct mp_audio *in = arg;
|
||||
|
||||
mp_audio_copy_config(af->data, in);
|
||||
mp_audio_force_interleaved_format(af->data);
|
||||
|
||||
if (s->fast && af_fmt_from_planar(in->format) != AF_FORMAT_FLOAT) {
|
||||
mp_audio_set_format(af->data, AF_FORMAT_S16);
|
||||
} else {
|
||||
mp_audio_set_format(af->data, AF_FORMAT_FLOAT);
|
||||
}
|
||||
if (af_fmt_is_planar(in->format))
|
||||
mp_audio_set_format(af->data, af_fmt_to_planar(af->data->format));
|
||||
s->rgain = 1.0;
|
||||
struct replaygain_data *rg = af->replaygain_data;
|
||||
if ((s->rgain_track || s->rgain_album) && rg) {
|
||||
MP_VERBOSE(af, "Replaygain: Track=%f/%f Album=%f/%f\n",
|
||||
rg->track_gain, rg->track_peak,
|
||||
rg->album_gain, rg->album_peak);
|
||||
|
||||
float gain, peak;
|
||||
if (s->rgain_track) {
|
||||
gain = rg->track_gain;
|
||||
peak = rg->track_peak;
|
||||
} else {
|
||||
gain = rg->album_gain;
|
||||
peak = rg->album_peak;
|
||||
}
|
||||
|
||||
gain += s->rgain_preamp;
|
||||
s->rgain = from_dB(gain, 20.0, -200.0, 60.0);
|
||||
|
||||
MP_VERBOSE(af, "Applying replay-gain: %f\n", s->rgain);
|
||||
|
||||
if (!s->rgain_clip) { // clipping prevention
|
||||
s->rgain = MPMIN(s->rgain, 1.0 / peak);
|
||||
MP_VERBOSE(af, "...with clipping prevention: %f\n", s->rgain);
|
||||
}
|
||||
} else if (s->replaygain_fallback) {
|
||||
s->rgain = from_dB(s->replaygain_fallback, 20.0, -200.0, 60.0);
|
||||
MP_VERBOSE(af, "Applying fallback gain: %f\n", s->rgain);
|
||||
}
|
||||
if (s->detach && fabs(s->level * s->rgain - 1.0) < 0.00001)
|
||||
return AF_DETACH;
|
||||
return af_test_output(af, in);
|
||||
}
|
||||
case AF_CONTROL_SET_VOLUME:
|
||||
s->level = *(float *)arg;
|
||||
MP_VERBOSE(af, "volume gain: %f\n", s->level);
|
||||
return AF_OK;
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static void filter_plane(struct af_instance *af, struct mp_audio *data, int p)
|
||||
{
|
||||
struct priv *s = af->priv;
|
||||
|
||||
float level = s->level * s->rgain * from_dB(s->cfg_volume, 20.0, -200.0, 60.0);
|
||||
int num_samples = data->samples * data->spf;
|
||||
|
||||
if (af_fmt_from_planar(af->data->format) == AF_FORMAT_S16) {
|
||||
int vol = 256.0 * level;
|
||||
if (vol != 256) {
|
||||
if (af_make_writeable(af, data) < 0)
|
||||
return; // oom
|
||||
int16_t *a = data->planes[p];
|
||||
for (int i = 0; i < num_samples; i++) {
|
||||
int x = (a[i] * vol) >> 8;
|
||||
a[i] = MPCLAMP(x, SHRT_MIN, SHRT_MAX);
|
||||
}
|
||||
}
|
||||
} else if (af_fmt_from_planar(af->data->format) == AF_FORMAT_FLOAT) {
|
||||
float vol = level;
|
||||
if (vol != 1.0) {
|
||||
if (af_make_writeable(af, data) < 0)
|
||||
return; // oom
|
||||
float *a = data->planes[p];
|
||||
for (int i = 0; i < num_samples; i++) {
|
||||
float x = a[i] * vol;
|
||||
a[i] = s->soft ? af_softclip(x) : MPCLAMP(x, -1.0, 1.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int filter(struct af_instance *af, struct mp_audio *data)
|
||||
{
|
||||
if (data) {
|
||||
for (int n = 0; n < data->num_planes; n++)
|
||||
filter_plane(af, data, n);
|
||||
af_add_output_frame(af, data);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int af_open(struct af_instance *af)
|
||||
{
|
||||
struct priv *s = af->priv;
|
||||
if (s->warn)
|
||||
MP_WARN(af, "This filter is deprecated. Use --volume directly.\n");
|
||||
af->control = control;
|
||||
af->filter_frame = filter;
|
||||
s->level = 1.0;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
// Description of this filter
|
||||
const struct af_info af_info_volume = {
|
||||
.info = "Volume control audio filter",
|
||||
.name = "volume",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_FLOATRANGE("volumedb", cfg_volume, 0, -200, 60),
|
||||
OPT_FLAG("replaygain-track", rgain_track, 0),
|
||||
OPT_FLAG("replaygain-album", rgain_album, 0),
|
||||
OPT_FLOATRANGE("replaygain-preamp", rgain_preamp, 0, -15, 15),
|
||||
OPT_FLAG("replaygain-clip", rgain_clip, 0),
|
||||
OPT_FLOATRANGE("replaygain-fallback", replaygain_fallback, 0, -200, 60),
|
||||
OPT_FLAG("softclip", soft, 0),
|
||||
OPT_FLAG("s16", fast, 0),
|
||||
OPT_FLAG("detach", detach, 0),
|
||||
OPT_FLAG("warn", warn, 0, OPTDEF_INT(1)),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
+28
-19
@@ -20,7 +20,7 @@
|
||||
#include <limits.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "format.h"
|
||||
#include "audio/filter/af.h"
|
||||
|
||||
// number of bytes per sample, 0 if invalid/unknown
|
||||
int af_fmt_to_bytes(int format)
|
||||
@@ -55,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;
|
||||
@@ -73,30 +79,23 @@ static const int planar_formats[][2] = {
|
||||
{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++) {
|
||||
@@ -135,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);
|
||||
@@ -144,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;
|
||||
@@ -199,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
-2
@@ -64,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);
|
||||
|
||||
+11
-56
@@ -18,7 +18,6 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "mpv_talloc.h"
|
||||
@@ -82,7 +81,7 @@ static const struct ao_driver * const audio_out_drivers[] = {
|
||||
#if HAVE_OPENSLES
|
||||
&audio_out_opensles,
|
||||
#endif
|
||||
#if HAVE_SDL2
|
||||
#if HAVE_SDL1 || HAVE_SDL2
|
||||
&audio_out_sdl,
|
||||
#endif
|
||||
#if HAVE_SNDIO
|
||||
@@ -93,7 +92,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
|
||||
@@ -158,7 +159,6 @@ static struct ao *ao_alloc(bool probing, struct mpv_global *global,
|
||||
ao->priv = m_config_group_from_desc(ao, ao->log, global, &desc, name);
|
||||
if (!ao->priv)
|
||||
goto error;
|
||||
ao_set_gain(ao, 1.0f);
|
||||
return ao;
|
||||
error:
|
||||
talloc_free(ao);
|
||||
@@ -230,7 +230,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;
|
||||
@@ -420,7 +419,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);
|
||||
@@ -551,6 +550,13 @@ bool ao_hotplug_check_update(struct ao_hotplug *hp)
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *ao_hotplug_get_detected_device(struct ao_hotplug *hp)
|
||||
{
|
||||
if (!hp || !hp->ao)
|
||||
return NULL;
|
||||
return hp->ao->detected_device;
|
||||
}
|
||||
|
||||
// The return value is valid until the next call to this API.
|
||||
struct ao_device_list *ao_hotplug_get_device_list(struct ao_hotplug *hp)
|
||||
{
|
||||
@@ -639,57 +645,6 @@ void ao_print_devices(struct mpv_global *global, struct mp_log *log)
|
||||
ao_hotplug_destroy(hp);
|
||||
}
|
||||
|
||||
void ao_set_gain(struct ao *ao, float gain)
|
||||
{
|
||||
atomic_store(&ao->gain, gain);
|
||||
}
|
||||
|
||||
#define MUL_GAIN_i(d, num_samples, gain, low, center, high) \
|
||||
for (int n = 0; n < (num_samples); n++) \
|
||||
(d)[n] = MPCLAMP( \
|
||||
((((int64_t)((d)[n]) - (center)) * (gain) + 128) >> 8) + (center), \
|
||||
(low), (high))
|
||||
|
||||
#define MUL_GAIN_f(d, num_samples, gain) \
|
||||
for (int n = 0; n < (num_samples); n++) \
|
||||
(d)[n] = MPCLAMP(((d)[n]) * (gain), -1.0, 1.0)
|
||||
|
||||
static void process_plane(struct ao *ao, void *data, int num_samples)
|
||||
{
|
||||
float gain = atomic_load_explicit(&ao->gain, memory_order_relaxed);
|
||||
int gi = lrint(256.0 * gain);
|
||||
if (gi == 256)
|
||||
return;
|
||||
switch (af_fmt_from_planar(ao->format)) {
|
||||
case AF_FORMAT_U8:
|
||||
MUL_GAIN_i((uint8_t *)data, num_samples, gi, 0, 128, 255);
|
||||
break;
|
||||
case AF_FORMAT_S16:
|
||||
MUL_GAIN_i((int16_t *)data, num_samples, gi, INT16_MIN, 0, INT16_MAX);
|
||||
break;
|
||||
case AF_FORMAT_S32:
|
||||
MUL_GAIN_i((int32_t *)data, num_samples, gi, INT32_MIN, 0, INT32_MAX);
|
||||
break;
|
||||
case AF_FORMAT_FLOAT:
|
||||
MUL_GAIN_f((float *)data, num_samples, gain);
|
||||
break;
|
||||
case AF_FORMAT_DOUBLE:
|
||||
MUL_GAIN_f((double *)data, num_samples, gain);
|
||||
break;
|
||||
default:;
|
||||
// all other sample formats are simply not supported
|
||||
}
|
||||
}
|
||||
|
||||
void ao_post_process_data(struct ao *ao, void **data, int num_samples)
|
||||
{
|
||||
bool planar = af_fmt_is_planar(ao->format);
|
||||
int planes = planar ? ao->channels.num : 1;
|
||||
int plane_samples = num_samples * (planar ? 1: ao->channels.num);
|
||||
for (int n = 0; n < planes; n++)
|
||||
process_plane(ao, data[n], plane_samples);
|
||||
}
|
||||
|
||||
static int get_conv_type(struct ao_convert_fmt *fmt)
|
||||
{
|
||||
if (af_fmt_to_bytes(fmt->src_fmt) * 8 == fmt->dst_bits && !fmt->pad_msb)
|
||||
|
||||
+1
-4
@@ -48,7 +48,6 @@ enum aocontrol {
|
||||
enum {
|
||||
AO_EVENT_RELOAD = 1,
|
||||
AO_EVENT_HOTPLUG = 2,
|
||||
AO_EVENT_INITIAL_UNBLOCK = 4,
|
||||
};
|
||||
|
||||
enum {
|
||||
@@ -96,7 +95,6 @@ const char *ao_get_description(struct ao *ao);
|
||||
bool ao_untimed(struct ao *ao);
|
||||
int ao_play(struct ao *ao, void **data, int samples, int flags);
|
||||
int ao_control(struct ao *ao, enum aocontrol cmd, void *arg);
|
||||
void ao_set_gain(struct ao *ao, float gain);
|
||||
double ao_get_delay(struct ao *ao);
|
||||
int ao_get_space(struct ao *ao);
|
||||
void ao_reset(struct ao *ao);
|
||||
@@ -105,8 +103,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);
|
||||
|
||||
@@ -116,6 +112,7 @@ struct ao_hotplug *ao_hotplug_create(struct mpv_global *global,
|
||||
void *wakeup_ctx);
|
||||
void ao_hotplug_destroy(struct ao_hotplug *hp);
|
||||
bool ao_hotplug_check_update(struct ao_hotplug *hp);
|
||||
const char *ao_hotplug_get_detected_device(struct ao_hotplug *hp);
|
||||
struct ao_device_list *ao_hotplug_get_device_list(struct ao_hotplug *hp);
|
||||
|
||||
void ao_print_devices(struct mpv_global *global, struct mp_log *log);
|
||||
|
||||
+58
-75
@@ -12,25 +12,24 @@
|
||||
*
|
||||
* 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 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 Lesser General Public License for more details.
|
||||
* GNU 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/>.
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#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),
|
||||
};
|
||||
@@ -106,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) { \
|
||||
@@ -189,13 +182,15 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
ao_control_vol_t *vol = arg;
|
||||
set_vol = vol->left / f_multi + pmin + 0.5;
|
||||
|
||||
err = snd_mixer_selem_set_playback_volume(elem, 0, set_vol);
|
||||
err = snd_mixer_selem_set_playback_volume
|
||||
(elem, SND_MIXER_SCHN_FRONT_LEFT, set_vol);
|
||||
CHECK_ALSA_ERROR("Error setting left channel");
|
||||
MP_DBG(ao, "left=%li, ", set_vol);
|
||||
|
||||
set_vol = vol->right / f_multi + pmin + 0.5;
|
||||
|
||||
err = snd_mixer_selem_set_playback_volume(elem, 1, set_vol);
|
||||
err = snd_mixer_selem_set_playback_volume
|
||||
(elem, SND_MIXER_SCHN_FRONT_RIGHT, set_vol);
|
||||
CHECK_ALSA_ERROR("Error setting right channel");
|
||||
MP_DBG(ao, "right=%li, pmin=%li, pmax=%li, mult=%f\n",
|
||||
set_vol, pmin, pmax, f_multi);
|
||||
@@ -203,9 +198,11 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
}
|
||||
case AOCONTROL_GET_VOLUME: {
|
||||
ao_control_vol_t *vol = arg;
|
||||
snd_mixer_selem_get_playback_volume(elem, 0, &get_vol);
|
||||
snd_mixer_selem_get_playback_volume
|
||||
(elem, SND_MIXER_SCHN_FRONT_LEFT, &get_vol);
|
||||
vol->left = (get_vol - pmin) * f_multi;
|
||||
snd_mixer_selem_get_playback_volume(elem, 1, &get_vol);
|
||||
snd_mixer_selem_get_playback_volume
|
||||
(elem, SND_MIXER_SCHN_FRONT_RIGHT, &get_vol);
|
||||
vol->right = (get_vol - pmin) * f_multi;
|
||||
MP_DBG(ao, "left=%f, right=%f\n", vol->left, vol->right);
|
||||
break;
|
||||
@@ -215,9 +212,11 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
if (!snd_mixer_selem_has_playback_switch(elem))
|
||||
goto alsa_error;
|
||||
if (!snd_mixer_selem_has_playback_switch_joined(elem)) {
|
||||
snd_mixer_selem_set_playback_switch(elem, 1, !*mute);
|
||||
snd_mixer_selem_set_playback_switch
|
||||
(elem, SND_MIXER_SCHN_FRONT_RIGHT, !*mute);
|
||||
}
|
||||
snd_mixer_selem_set_playback_switch(elem, 0, !*mute);
|
||||
snd_mixer_selem_set_playback_switch
|
||||
(elem, SND_MIXER_SCHN_FRONT_LEFT, !*mute);
|
||||
break;
|
||||
}
|
||||
case AOCONTROL_GET_MUTE: {
|
||||
@@ -225,10 +224,12 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
if (!snd_mixer_selem_has_playback_switch(elem))
|
||||
goto alsa_error;
|
||||
int tmp = 1;
|
||||
snd_mixer_selem_get_playback_switch(elem, 0, &tmp);
|
||||
snd_mixer_selem_get_playback_switch
|
||||
(elem, SND_MIXER_SCHN_FRONT_LEFT, &tmp);
|
||||
*mute = !tmp;
|
||||
if (!snd_mixer_selem_has_playback_switch_joined(elem)) {
|
||||
snd_mixer_selem_get_playback_switch(elem, 1, &tmp);
|
||||
snd_mixer_selem_get_playback_switch
|
||||
(elem, SND_MIXER_SCHN_FRONT_RIGHT, &tmp);
|
||||
*mute &= !tmp;
|
||||
}
|
||||
break;
|
||||
@@ -525,7 +526,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;
|
||||
@@ -537,7 +538,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);
|
||||
}
|
||||
|
||||
@@ -652,7 +653,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;
|
||||
@@ -686,7 +686,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.
|
||||
@@ -694,7 +694,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
|
||||
@@ -706,10 +706,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];
|
||||
@@ -749,7 +749,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;
|
||||
@@ -778,7 +778,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);
|
||||
@@ -788,35 +788,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;
|
||||
@@ -856,6 +853,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);
|
||||
@@ -939,32 +941,17 @@ 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) {
|
||||
if (space == -EPIPE) {
|
||||
MP_WARN(ao, "ALSA XRUN hit, attempting to recover...\n");
|
||||
int err = snd_pcm_prepare(p->alsa);
|
||||
CHECK_ALSA_ERROR("Unable to recover from under/overrun!");
|
||||
return p->buffersize;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
CHECK_ALSA_ERROR("cannot get pcm status");
|
||||
|
||||
unsigned space = snd_pcm_status_get_avail(status);
|
||||
if (space > p->buffersize) // Buffer underrun?
|
||||
space = p->buffersize;
|
||||
return space / p->outburst * p->outburst;
|
||||
@@ -1111,9 +1098,10 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
|
||||
if (samples == 0)
|
||||
return 0;
|
||||
ao_convert_inplace(&p->convert, data, samples);
|
||||
|
||||
do {
|
||||
ao_convert_inplace(&p->convert, data, samples);
|
||||
|
||||
if (af_fmt_is_planar(ao->format)) {
|
||||
res = snd_pcm_writen(p->alsa, data, samples);
|
||||
} else {
|
||||
@@ -1127,11 +1115,6 @@ 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) {
|
||||
// For some reason, writing a smaller fragment at the end
|
||||
// immediately underruns.
|
||||
if (!(flags & AOPLAY_FINAL_CHUNK))
|
||||
MP_WARN(ao, "Device underrun detected.\n");
|
||||
} else {
|
||||
MP_ERR(ao, "Write error: %s\n", snd_strerror(res));
|
||||
}
|
||||
|
||||
+1
-6
@@ -40,10 +40,6 @@
|
||||
|
||||
#include <jack/jack.h>
|
||||
|
||||
#if !HAVE_GPL
|
||||
#error GPL only
|
||||
#endif
|
||||
|
||||
struct jack_opts {
|
||||
char *port;
|
||||
char *client_name;
|
||||
@@ -143,8 +139,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");
|
||||
|
||||
+230
-72
@@ -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,64 +83,72 @@ 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);
|
||||
@@ -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;
|
||||
@@ -330,11 +488,13 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
pthread_mutex_unlock(&ectx->lock);
|
||||
|
||||
if (flags & AOPLAY_FINAL_CHUNK) {
|
||||
if (bufpos < orig_samples)
|
||||
if (bufpos < orig_samples) {
|
||||
MP_ERR(ao, "did not write enough data at the end\n");
|
||||
}
|
||||
} else {
|
||||
if (bufpos > orig_samples)
|
||||
if (bufpos > orig_samples) {
|
||||
MP_ERR(ao, "audio buffer overflow (should never happen)\n");
|
||||
}
|
||||
}
|
||||
|
||||
return taken;
|
||||
@@ -349,8 +509,6 @@ const struct ao_driver audio_out_lavc = {
|
||||
.encode = true,
|
||||
.description = "audio encoding using libavcodec",
|
||||
.name = "lavc",
|
||||
.initially_blocked = true,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.get_space = get_space,
|
||||
|
||||
@@ -58,7 +58,6 @@ struct priv {
|
||||
int outburst; // samples
|
||||
|
||||
struct m_channels channel_layouts;
|
||||
int format;
|
||||
};
|
||||
|
||||
static void drain(struct ao *ao)
|
||||
@@ -89,9 +88,6 @@ static int init(struct ao *ao)
|
||||
{
|
||||
struct priv *priv = ao->priv;
|
||||
|
||||
if (priv->format)
|
||||
ao->format = priv->format;
|
||||
|
||||
ao->untimed = priv->untimed;
|
||||
|
||||
struct mp_chmap_sel sel = {.tmp = ao};
|
||||
@@ -245,7 +241,6 @@ const struct ao_driver audio_out_null = {
|
||||
OPT_FLAG("broken-eof", broken_eof, 0),
|
||||
OPT_FLAG("broken-delay", broken_delay, 0),
|
||||
OPT_CHANNELS("channel-layouts", channel_layouts, 0),
|
||||
OPT_AUDIOFORMAT("format", format, 0),
|
||||
{0}
|
||||
},
|
||||
.options_prefix = "ao-null",
|
||||
|
||||
+123
-223
@@ -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,83 +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);
|
||||
return CONTROL_TRUE;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -162,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);
|
||||
@@ -185,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) {
|
||||
@@ -201,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:
|
||||
@@ -279,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,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();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -317,7 +266,7 @@ static void reset(struct ao *ao)
|
||||
*/
|
||||
static void audio_pause(struct ao *ao)
|
||||
{
|
||||
alSourcePause(source);
|
||||
alSourcePausev(ao->channels.num, sources);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -325,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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -346,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
|
||||
@@ -424,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",
|
||||
};
|
||||
+37
-43
@@ -35,12 +35,12 @@ struct priv {
|
||||
SLBufferQueueItf buffer_queue;
|
||||
SLEngineItf engine;
|
||||
SLPlayItf play;
|
||||
char *buf;
|
||||
char *curbuf, *buf1, *buf2;
|
||||
size_t buffer_size;
|
||||
pthread_mutex_t buffer_lock;
|
||||
double audio_latency;
|
||||
|
||||
int cfg_frames_per_buffer;
|
||||
int cfg_sample_rate;
|
||||
};
|
||||
|
||||
static const int fmtmap[][2] = {
|
||||
@@ -69,8 +69,9 @@ 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;
|
||||
}
|
||||
|
||||
@@ -86,20 +87,21 @@ static void buffer_callback(SLBufferQueueItf buffer_queue, void *context)
|
||||
|
||||
pthread_mutex_lock(&p->buffer_lock);
|
||||
|
||||
data[0] = p->buf;
|
||||
data[0] = p->curbuf;
|
||||
delay = 2 * p->buffer_size / (double)ao->bps;
|
||||
delay += p->audio_latency;
|
||||
ao_read_data(ao, data, p->buffer_size / ao->sstride,
|
||||
mp_time_us() + 1000000LL * delay);
|
||||
|
||||
res = (*buffer_queue)->Enqueue(buffer_queue, p->buf, p->buffer_size);
|
||||
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 250
|
||||
#define DEFAULT_BUFFER_SIZE_MS 50
|
||||
|
||||
#define CHK(stmt) \
|
||||
{ \
|
||||
@@ -129,12 +131,12 @@ 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 = 1;
|
||||
locator_buffer_queue.numBuffers = 2;
|
||||
|
||||
pcm.formatType = SL_DATAFORMAT_PCM;
|
||||
pcm.numChannels = 2;
|
||||
|
||||
int compatible_formats[AF_FORMAT_COUNT + 1];
|
||||
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)
|
||||
@@ -152,6 +154,9 @@ static int init(struct ao *ao)
|
||||
pcm.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
|
||||
pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
|
||||
|
||||
if (p->cfg_sample_rate)
|
||||
ao->samplerate = p->cfg_sample_rate;
|
||||
|
||||
// samplesPerSec is misnamed, actually it's samples per ms
|
||||
pcm.samplesPerSec = ao->samplerate * 1000;
|
||||
|
||||
@@ -161,8 +166,10 @@ static int init(struct ao *ao)
|
||||
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->buffer_size);
|
||||
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;
|
||||
}
|
||||
@@ -181,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:
|
||||
@@ -225,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);
|
||||
}
|
||||
|
||||
@@ -249,7 +242,8 @@ const struct ao_driver audio_out_opensles = {
|
||||
|
||||
.priv_size = sizeof(struct priv),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_INTRANGE("frames-per-buffer", cfg_frames_per_buffer, 0, 1, 96000),
|
||||
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",
|
||||
|
||||
+2
-10
@@ -49,18 +49,10 @@
|
||||
#include "ao.h"
|
||||
#include "internal.h"
|
||||
|
||||
#if !HAVE_GPL
|
||||
#error GPL only
|
||||
#endif
|
||||
|
||||
// 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"
|
||||
@@ -315,7 +307,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];
|
||||
|
||||
@@ -454,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,
|
||||
@@ -831,7 +831,7 @@ 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),
|
||||
|
||||
+5
-6
@@ -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());
|
||||
|
||||
@@ -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++) {
|
||||
|
||||
@@ -61,6 +61,9 @@ struct ao {
|
||||
// default device should be used, this is set to NULL.
|
||||
char *device;
|
||||
|
||||
// Device actually chosen by the AO
|
||||
char *detected_device;
|
||||
|
||||
// Application name to report to the audio API.
|
||||
char *client_name;
|
||||
|
||||
@@ -70,9 +73,6 @@ struct ao {
|
||||
// Internal events (use ao_request_reload(), ao_hotplug_event())
|
||||
atomic_int events_;
|
||||
|
||||
// Float gain multiplicator
|
||||
mp_atomic_float gain;
|
||||
|
||||
int buffer;
|
||||
double def_buffer;
|
||||
void *api_priv;
|
||||
@@ -130,10 +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;
|
||||
// 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.
|
||||
@@ -219,8 +215,6 @@ bool ao_chmap_sel_get_def(struct ao *ao, const struct mp_chmap_sel *s,
|
||||
void ao_device_list_add(struct ao_device_list *list, struct ao *ao,
|
||||
struct ao_device_desc *e);
|
||||
|
||||
void ao_post_process_data(struct ao *ao, void **data, int num_samples);
|
||||
|
||||
struct ao_convert_fmt {
|
||||
int src_fmt; // source AF_FORMAT_*
|
||||
int channels; // number of channels
|
||||
|
||||
+1
-3
@@ -179,8 +179,6 @@ end:
|
||||
for (int n = 0; n < ao->num_planes; n++)
|
||||
af_fill_silence((char *)data[n] + bytes, full_bytes - bytes, ao->format);
|
||||
|
||||
ao_post_process_data(ao, data, samples);
|
||||
|
||||
return bytes / ao->sstride;
|
||||
}
|
||||
|
||||
@@ -192,7 +190,7 @@ int ao_read_data_converted(struct ao *ao, struct ao_convert_fmt *fmt,
|
||||
assert(ao->api == &ao_api_pull);
|
||||
|
||||
struct ao_pull_state *p = ao->api_priv;
|
||||
void *ndata[MP_NUM_CHANNELS] = {0};
|
||||
void *ndata[MP_NUM_CHANNELS];
|
||||
|
||||
if (!ao_need_conversion(fmt))
|
||||
return ao_read_data(ao, data, samples, out_time_us);
|
||||
|
||||
+48
-61
@@ -37,6 +37,7 @@
|
||||
#include "osdep/timer.h"
|
||||
#include "osdep/atomic.h"
|
||||
|
||||
#include "audio/audio.h"
|
||||
#include "audio/audio_buffer.h"
|
||||
|
||||
struct ao_push_state {
|
||||
@@ -48,15 +49,13 @@ struct ao_push_state {
|
||||
|
||||
struct mp_audio_buffer *buffer;
|
||||
|
||||
uint8_t *silence[MP_NUM_CHANNELS];
|
||||
int silence_samples;
|
||||
struct mp_audio *silence;
|
||||
|
||||
bool terminate;
|
||||
bool wait_on_ao;
|
||||
bool still_playing;
|
||||
bool need_wakeup;
|
||||
bool paused;
|
||||
bool initial_unblocked;
|
||||
|
||||
// Whether the current buffer contains the complete audio.
|
||||
bool final_chunk;
|
||||
@@ -238,7 +237,12 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
flags = flags & ~AOPLAY_FINAL_CHUNK;
|
||||
bool is_final = flags & AOPLAY_FINAL_CHUNK;
|
||||
|
||||
mp_audio_buffer_append(p->buffer, data, samples);
|
||||
struct mp_audio audio;
|
||||
mp_audio_buffer_get_format(p->buffer, &audio);
|
||||
for (int n = 0; n < ao->num_planes; n++)
|
||||
audio.planes[n] = data[n];
|
||||
audio.samples = write_samples;
|
||||
mp_audio_buffer_append(p->buffer, &audio);
|
||||
|
||||
bool got_data = write_samples > 0 || p->paused || p->final_chunk != is_final;
|
||||
|
||||
@@ -256,26 +260,22 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
return write_samples;
|
||||
}
|
||||
|
||||
static bool realloc_silence(struct ao *ao, int samples)
|
||||
static void ao_get_silence(struct ao *ao, struct mp_audio *data, int size)
|
||||
{
|
||||
struct ao_push_state *p = ao->api_priv;
|
||||
|
||||
if (samples <= 0 || !af_fmt_is_pcm(ao->format))
|
||||
return false;
|
||||
|
||||
if (samples > p->silence_samples) {
|
||||
talloc_free(p->silence[0]);
|
||||
|
||||
int bytes = af_fmt_to_bytes(ao->format) * samples * ao->channels.num;
|
||||
p->silence[0] = talloc_size(p, bytes);
|
||||
for (int n = 1; n < MP_NUM_CHANNELS; n++)
|
||||
p->silence[n] = p->silence[0];
|
||||
p->silence_samples = samples;
|
||||
|
||||
af_fill_silence(p->silence[0], bytes, ao->format);
|
||||
if (!p->silence) {
|
||||
p->silence = talloc_zero(p, struct mp_audio);
|
||||
mp_audio_set_format(p->silence, ao->format);
|
||||
mp_audio_set_channels(p->silence, &ao->channels);
|
||||
p->silence->rate = ao->samplerate;
|
||||
}
|
||||
|
||||
return true;
|
||||
if (p->silence->samples < size) {
|
||||
mp_audio_realloc_min(p->silence, size);
|
||||
p->silence->samples = size;
|
||||
mp_audio_fill_silence(p->silence, 0, size);
|
||||
}
|
||||
*data = *p->silence;
|
||||
data->samples = size;
|
||||
}
|
||||
|
||||
// called locked
|
||||
@@ -287,42 +287,39 @@ static void ao_play_data(struct ao *ao)
|
||||
space = MPMAX(space, 0);
|
||||
if (space % ao->period_size)
|
||||
MP_ERR(ao, "Audio device reports unaligned available buffer size.\n");
|
||||
uint8_t **planes;
|
||||
int samples;
|
||||
struct mp_audio data;
|
||||
if (play_silence) {
|
||||
planes = p->silence;
|
||||
samples = realloc_silence(ao, space) ? space : 0;
|
||||
ao_get_silence(ao, &data, space);
|
||||
} else {
|
||||
mp_audio_buffer_peek(p->buffer, &planes, &samples);
|
||||
mp_audio_buffer_peek(p->buffer, &data);
|
||||
}
|
||||
int max = samples;
|
||||
if (samples > space)
|
||||
samples = space;
|
||||
int max = data.samples;
|
||||
if (data.samples > space)
|
||||
data.samples = space;
|
||||
int flags = 0;
|
||||
if (p->final_chunk && samples == max) {
|
||||
if (p->final_chunk && data.samples == max) {
|
||||
flags |= AOPLAY_FINAL_CHUNK;
|
||||
} else {
|
||||
samples = samples / ao->period_size * ao->period_size;
|
||||
data.samples = data.samples / ao->period_size * ao->period_size;
|
||||
}
|
||||
MP_STATS(ao, "start ao fill");
|
||||
ao_post_process_data(ao, (void **)planes, samples);
|
||||
int r = 0;
|
||||
if (samples)
|
||||
r = ao->driver->play(ao, (void **)planes, samples, flags);
|
||||
if (data.samples)
|
||||
r = ao->driver->play(ao, data.planes, data.samples, flags);
|
||||
MP_STATS(ao, "end ao fill");
|
||||
if (r > samples) {
|
||||
MP_ERR(ao, "Audio device returned nonsense value.\n");
|
||||
r = samples;
|
||||
if (r > data.samples) {
|
||||
MP_ERR(ao, "Audio device returned non-sense value.\n");
|
||||
r = data.samples;
|
||||
} else if (r < 0) {
|
||||
MP_ERR(ao, "Error writing audio to device.\n");
|
||||
} else if (r != samples) {
|
||||
} else if (r != data.samples) {
|
||||
MP_ERR(ao, "Audio device returned broken buffer state (sent %d samples, "
|
||||
"got %d samples, %d period%s)!\n", samples, r,
|
||||
"got %d samples, %d period%s)!\n", data.samples, r,
|
||||
ao->period_size, flags & AOPLAY_FINAL_CHUNK ? " final" : "");
|
||||
}
|
||||
r = MPMAX(r, 0);
|
||||
// Probably can't copy the rest of the buffer due to period alignment.
|
||||
bool stuck_eof = r <= 0 && space >= max && samples > 0;
|
||||
bool stuck_eof = r <= 0 && space >= max && data.samples > 0;
|
||||
if ((flags & AOPLAY_FINAL_CHUNK) && stuck_eof) {
|
||||
MP_ERR(ao, "Audio output driver seems to ignore AOPLAY_FINAL_CHUNK.\n");
|
||||
r = max;
|
||||
@@ -358,8 +355,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);
|
||||
|
||||
@@ -495,26 +491,17 @@ const struct ao_driver ao_api_push = {
|
||||
int ao_play_silence(struct ao *ao, int samples)
|
||||
{
|
||||
assert(ao->api == &ao_api_push);
|
||||
|
||||
struct ao_push_state *p = ao->api_priv;
|
||||
|
||||
if (!realloc_silence(ao, samples) || !ao->driver->play)
|
||||
if (samples <= 0 || !af_fmt_is_pcm(ao->format) || !ao->driver->play)
|
||||
return 0;
|
||||
|
||||
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);
|
||||
}
|
||||
int bytes = af_fmt_to_bytes(ao->format) * samples * ao->channels.num;
|
||||
char *p = talloc_size(NULL, bytes);
|
||||
af_fill_silence(p, bytes, ao->format);
|
||||
void *tmp[MP_NUM_CHANNELS];
|
||||
for (int n = 0; n < MP_NUM_CHANNELS; n++)
|
||||
tmp[n] = p;
|
||||
int r = ao->driver->play(ao, tmp, samples, 0);
|
||||
talloc_free(p);
|
||||
return r;
|
||||
}
|
||||
|
||||
#ifndef __MINGW32__
|
||||
|
||||
+2
-2
@@ -5,10 +5,10 @@
|
||||
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()
|
||||
|
||||
@@ -1,34 +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-dvdread \
|
||||
--enable-dvdnav \
|
||||
--enable-uchardet \
|
||||
--enable-vulkan \
|
||||
--enable-shaderc \
|
||||
--enable-rubberband \
|
||||
--enable-lcms2
|
||||
python3 ./waf build --verbose
|
||||
@@ -1,16 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
python3 ./waf configure \
|
||||
--enable-cdda \
|
||||
--enable-dvbin \
|
||||
--enable-dvdnav \
|
||||
--enable-dvdread \
|
||||
--enable-libarchive \
|
||||
--enable-libmpv-shared \
|
||||
--enable-libsmbclient \
|
||||
--enable-manpage-build \
|
||||
--enable-shaderc \
|
||||
--enable-vulkan \
|
||||
--enable-zsh-comp
|
||||
python3 ./waf build --verbose
|
||||
+18
-36
@@ -26,7 +26,6 @@
|
||||
#include <libavutil/error.h>
|
||||
#include <libavutil/cpu.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
@@ -34,7 +33,6 @@
|
||||
#include "common/msg.h"
|
||||
#include "demux/packet.h"
|
||||
#include "demux/stheader.h"
|
||||
#include "misc/bstr.h"
|
||||
#include "video/fmt-conversion.h"
|
||||
#include "av_common.h"
|
||||
#include "codecs.h"
|
||||
@@ -219,47 +217,38 @@ void mp_set_avcodec_threads(struct mp_log *l, AVCodecContext *avctx, int threads
|
||||
avctx->thread_count = threads;
|
||||
}
|
||||
|
||||
static void add_codecs(struct mp_decoder_list *list, enum AVMediaType type,
|
||||
bool decoders)
|
||||
static bool is_crap(AVCodec *codec)
|
||||
{
|
||||
void *iter = NULL;
|
||||
return !!strstr(codec->name, "_vdpau");
|
||||
}
|
||||
|
||||
void mp_add_lavc_decoders(struct mp_decoder_list *list, enum AVMediaType type)
|
||||
{
|
||||
AVCodec *cur = NULL;
|
||||
for (;;) {
|
||||
const AVCodec *cur = av_codec_iterate(&iter);
|
||||
cur = av_codec_next(cur);
|
||||
if (!cur)
|
||||
break;
|
||||
if (av_codec_is_decoder(cur) == decoders &&
|
||||
(type == AVMEDIA_TYPE_UNKNOWN || cur->type == type))
|
||||
{
|
||||
mp_add_decoder(list, mp_codec_from_av_codec_id(cur->id),
|
||||
if (av_codec_is_decoder(cur) && cur->type == type && !is_crap(cur)) {
|
||||
mp_add_decoder(list, "lavc", mp_codec_from_av_codec_id(cur->id),
|
||||
cur->name, cur->long_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void mp_add_lavc_decoders(struct mp_decoder_list *list, enum AVMediaType type)
|
||||
{
|
||||
add_codecs(list, type, true);
|
||||
}
|
||||
|
||||
// (Abuses the decoder list data structures.)
|
||||
void mp_add_lavc_encoders(struct mp_decoder_list *list)
|
||||
{
|
||||
add_codecs(list, AVMEDIA_TYPE_UNKNOWN, false);
|
||||
}
|
||||
|
||||
char **mp_get_lavf_demuxers(void)
|
||||
{
|
||||
char **list = NULL;
|
||||
void *iter = NULL;
|
||||
int num = 0;
|
||||
AVCodec *cur = NULL;
|
||||
for (;;) {
|
||||
const AVInputFormat *cur = av_demuxer_iterate(&iter);
|
||||
cur = av_codec_next(cur);
|
||||
if (!cur)
|
||||
break;
|
||||
MP_TARRAY_APPEND(NULL, list, num, talloc_strdup(NULL, cur->name));
|
||||
if (av_codec_is_encoder(cur)) {
|
||||
mp_add_decoder(list, "lavc", mp_codec_from_av_codec_id(cur->id),
|
||||
cur->name, cur->long_name);
|
||||
}
|
||||
}
|
||||
MP_TARRAY_APPEND(NULL, list, num, NULL);
|
||||
return list;
|
||||
}
|
||||
|
||||
int mp_codec_to_av_codec_id(const char *codec)
|
||||
@@ -313,7 +302,7 @@ void mp_avdict_print_unset(struct mp_log *log, int msgl, AVDictionary *dict)
|
||||
// to the name of the n-th parameter.
|
||||
static void resolve_positional_arg(void *avobj, char **name)
|
||||
{
|
||||
if (!*name || (*name)[0] != '@' || !avobj)
|
||||
if (!*name || (*name)[0] != '@')
|
||||
return;
|
||||
|
||||
char *end = NULL;
|
||||
@@ -344,19 +333,12 @@ static void resolve_positional_arg(void *avobj, char **name)
|
||||
// Options which fail to set (error or not found) are printed to log.
|
||||
// Returns: >=0 success, <0 failed to set an option
|
||||
int mp_set_avopts(struct mp_log *log, void *avobj, char **kv)
|
||||
{
|
||||
return mp_set_avopts_pos(log, avobj, avobj, kv);
|
||||
}
|
||||
|
||||
// Like mp_set_avopts(), but the posargs argument is used to resolve positional
|
||||
// arguments. If posargs==NULL, positional args are disabled.
|
||||
int mp_set_avopts_pos(struct mp_log *log, void *avobj, void *posargs, char **kv)
|
||||
{
|
||||
int success = 0;
|
||||
for (int n = 0; kv && kv[n * 2]; n++) {
|
||||
char *k = kv[n * 2 + 0];
|
||||
char *v = kv[n * 2 + 1];
|
||||
resolve_positional_arg(posargs, &k);
|
||||
resolve_positional_arg(avobj, &k);
|
||||
int r = av_opt_set(avobj, k, v, AV_OPT_SEARCH_CHILDREN);
|
||||
if (r == AVERROR_OPTION_NOT_FOUND) {
|
||||
mp_err(log, "AVOption '%s' not found.\n", k);
|
||||
|
||||
@@ -41,12 +41,10 @@ double mp_pts_from_av(int64_t av_pts, AVRational *tb);
|
||||
void mp_set_avcodec_threads(struct mp_log *l, AVCodecContext *avctx, int threads);
|
||||
void mp_add_lavc_decoders(struct mp_decoder_list *list, enum AVMediaType type);
|
||||
void mp_add_lavc_encoders(struct mp_decoder_list *list);
|
||||
char **mp_get_lavf_demuxers(void);
|
||||
int mp_codec_to_av_codec_id(const char *codec);
|
||||
const char *mp_codec_from_av_codec_id(int codec_id);
|
||||
void mp_set_avdict(struct AVDictionary **dict, char **kv);
|
||||
void mp_avdict_print_unset(struct mp_log *log, int msgl, struct AVDictionary *d);
|
||||
int mp_set_avopts(struct mp_log *log, void *avobj, char **kv);
|
||||
int mp_set_avopts_pos(struct mp_log *log, void *avobj, void *posargs, char **kv);
|
||||
|
||||
#endif
|
||||
+22
-15
@@ -5,18 +5,20 @@
|
||||
*
|
||||
* 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 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 Lesser General Public License for more details.
|
||||
* GNU 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/>.
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Almost LGPL.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
@@ -42,9 +44,10 @@
|
||||
#include <libavdevice/avdevice.h>
|
||||
#endif
|
||||
|
||||
#if HAVE_LIBAV
|
||||
#if HAVE_IS_LIBAV
|
||||
#include <libavresample/avresample.h>
|
||||
#else
|
||||
#endif
|
||||
#if HAVE_IS_FFMPEG
|
||||
#include <libswresample/swresample.h>
|
||||
#endif
|
||||
|
||||
@@ -64,9 +67,9 @@ static bool log_print_prefix = true;
|
||||
static int av_log_level_to_mp_level(int av_level)
|
||||
{
|
||||
if (av_level > AV_LOG_VERBOSE)
|
||||
return MSGL_TRACE;
|
||||
if (av_level > AV_LOG_INFO)
|
||||
return MSGL_DEBUG;
|
||||
if (av_level > AV_LOG_INFO)
|
||||
return MSGL_V;
|
||||
if (av_level > AV_LOG_WARNING)
|
||||
return MSGL_V;
|
||||
if (av_level > AV_LOG_ERROR)
|
||||
@@ -93,10 +96,10 @@ static struct mp_log *get_av_log(void *ptr)
|
||||
AVCodecContext *s = ptr;
|
||||
if (s->codec) {
|
||||
if (s->codec->type == AVMEDIA_TYPE_AUDIO) {
|
||||
if (av_codec_is_decoder(s->codec))
|
||||
if (s->codec->decode)
|
||||
return log_decaudio;
|
||||
} else if (s->codec->type == AVMEDIA_TYPE_VIDEO) {
|
||||
if (av_codec_is_decoder(s->codec))
|
||||
if (s->codec->decode)
|
||||
return log_decvideo;
|
||||
}
|
||||
}
|
||||
@@ -159,7 +162,10 @@ void init_libav(struct mpv_global *global)
|
||||
}
|
||||
pthread_mutex_unlock(&log_lock);
|
||||
|
||||
avcodec_register_all();
|
||||
av_register_all();
|
||||
avformat_network_init();
|
||||
avfilter_register_all();
|
||||
|
||||
#if HAVE_LIBAVDEVICE
|
||||
avdevice_register_all();
|
||||
@@ -193,9 +199,10 @@ bool print_libav_versions(struct mp_log *log, int v)
|
||||
{"libavformat", LIBAVFORMAT_VERSION_INT, avformat_version()},
|
||||
{"libswscale", LIBSWSCALE_VERSION_INT, swscale_version()},
|
||||
{"libavfilter", LIBAVFILTER_VERSION_INT, avfilter_version()},
|
||||
#if HAVE_LIBAV
|
||||
#if HAVE_IS_LIBAV
|
||||
{"libavresample", LIBAVRESAMPLE_VERSION_INT, avresample_version()},
|
||||
#else
|
||||
#endif
|
||||
#if HAVE_IS_FFMPEG
|
||||
{"libswresample", LIBSWRESAMPLE_VERSION_INT, swresample_version()},
|
||||
#endif
|
||||
};
|
||||
|
||||
+9
-3
@@ -21,10 +21,11 @@
|
||||
#include "common/msg.h"
|
||||
#include "codecs.h"
|
||||
|
||||
void mp_add_decoder(struct mp_decoder_list *list, const char *codec,
|
||||
const char *decoder, const char *desc)
|
||||
void mp_add_decoder(struct mp_decoder_list *list, const char *family,
|
||||
const char *codec, const char *decoder, const char *desc)
|
||||
{
|
||||
struct mp_decoder_entry entry = {
|
||||
.family = talloc_strdup(list, family),
|
||||
.codec = talloc_strdup(list, codec),
|
||||
.decoder = talloc_strdup(list, decoder),
|
||||
.desc = talloc_strdup(list, desc),
|
||||
@@ -39,7 +40,7 @@ static void add_new(struct mp_decoder_list *to, struct mp_decoder_entry *entry,
|
||||
{
|
||||
if (!entry || (codec && strcmp(entry->codec, codec) != 0))
|
||||
return;
|
||||
mp_add_decoder(to, entry->codec, entry->decoder, entry->desc);
|
||||
mp_add_decoder(to, entry->family, entry->codec, entry->decoder, entry->desc);
|
||||
}
|
||||
|
||||
// Select a decoder from the given list for the given codec. The selection
|
||||
@@ -71,6 +72,11 @@ struct mp_decoder_list *mp_select_decoders(struct mp_log *log,
|
||||
stop = true;
|
||||
break;
|
||||
}
|
||||
if (bstr_find0(entry, ":") >= 0) {
|
||||
mp_err(log, "Codec family selection was removed. "
|
||||
"Pass the codec name directly.\n");
|
||||
break;
|
||||
}
|
||||
for (int n = 0; n < all->num_entries; n++) {
|
||||
struct mp_decoder_entry *cur = &all->entries[n];
|
||||
if (bstr_equals0(entry, cur->decoder))
|
||||
|
||||
+3
-2
@@ -21,6 +21,7 @@
|
||||
struct mp_log;
|
||||
|
||||
struct mp_decoder_entry {
|
||||
const char *family; // decoder module (e.g. ad_lavc => "lavc")
|
||||
const char *codec; // name of the codec (e.g. "mp3")
|
||||
const char *decoder; // decoder name (e.g. "mp3float")
|
||||
const char *desc; // human readable description
|
||||
@@ -31,8 +32,8 @@ struct mp_decoder_list {
|
||||
int num_entries;
|
||||
};
|
||||
|
||||
void mp_add_decoder(struct mp_decoder_list *list, const char *codec,
|
||||
const char *decoder, const char *desc);
|
||||
void mp_add_decoder(struct mp_decoder_list *list, const char *family,
|
||||
const char *codec, const char *decoder, const char *desc);
|
||||
|
||||
struct mp_decoder_list *mp_select_decoders(struct mp_log *log,
|
||||
struct mp_decoder_list *all,
|
||||
|
||||
+2
-18
@@ -16,7 +16,6 @@
|
||||
*/
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <math.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <libavutil/common.h>
|
||||
@@ -49,17 +48,14 @@ char *mp_format_time_fmt(const char *fmt, double time)
|
||||
time = time < 0 ? -time : time;
|
||||
long long int itime = time;
|
||||
long long int h, m, tm, s;
|
||||
int ms = lrint((time - itime) * 1000);
|
||||
if (ms >= 1000) {
|
||||
ms -= 1000;
|
||||
itime += 1;
|
||||
}
|
||||
int ms;
|
||||
s = itime;
|
||||
tm = s / 60;
|
||||
h = s / 3600;
|
||||
s -= h * 3600;
|
||||
m = s / 60;
|
||||
s -= m * 60;
|
||||
ms = (time - itime) * 1000;
|
||||
char *res = talloc_strdup(NULL, "");
|
||||
while (*fmt) {
|
||||
if (fmt[0] == '%') {
|
||||
@@ -72,7 +68,6 @@ char *mp_format_time_fmt(const char *fmt, double time)
|
||||
case 's': appendf(&res, "%s%lld", sign, itime); break;
|
||||
case 'S': appendf(&res, "%02lld", s); break;
|
||||
case 'T': appendf(&res, "%03d", ms); break;
|
||||
case 'f': appendf(&res, "%f", time); break;
|
||||
case '%': appendf(&res, "%s", "%"); break;
|
||||
default: goto error;
|
||||
}
|
||||
@@ -307,14 +302,3 @@ char *mp_tprintf_buf(char *buf, size_t buf_size, const char *format, ...)
|
||||
va_end(ap);
|
||||
return buf;
|
||||
}
|
||||
|
||||
char **mp_dup_str_array(void *tctx, char **s)
|
||||
{
|
||||
char **r = NULL;
|
||||
int num_r = 0;
|
||||
for (int n = 0; s && s[n]; n++)
|
||||
MP_TARRAY_APPEND(tctx, r, num_r, talloc_strdup(tctx, s[n]));
|
||||
if (r)
|
||||
MP_TARRAY_APPEND(tctx, r, num_r, NULL);
|
||||
return r;
|
||||
}
|
||||
+8
-14
@@ -58,9 +58,16 @@ enum stream_type {
|
||||
STREAM_TYPE_COUNT,
|
||||
};
|
||||
|
||||
enum {
|
||||
DATA_OK = 1, // data is actually being returned
|
||||
DATA_WAIT = 0, // async wait: check state again after next wakeup
|
||||
DATA_AGAIN = -2, // repeat request (internal progress was made)
|
||||
DATA_STARVE = -1, // need input (might require to drain other outputs)
|
||||
DATA_EOF = -3, // no more data available
|
||||
};
|
||||
|
||||
extern const char mpv_version[];
|
||||
extern const char mpv_builddate[];
|
||||
extern const char mpv_copyright[];
|
||||
|
||||
char *mp_format_time(double time, bool fractions);
|
||||
char *mp_format_time_fmt(const char *fmt, double time);
|
||||
@@ -104,17 +111,4 @@ char *mp_tag_str_buf(char *buf, size_t buf_size, uint32_t tag);
|
||||
char *mp_tprintf_buf(char *buf, size_t buf_size, const char *format, ...)
|
||||
PRINTF_ATTRIBUTE(3, 4);
|
||||
|
||||
char **mp_dup_str_array(void *tctx, char **s);
|
||||
|
||||
// We generally do not handle allocation failure of small malloc()s. This would
|
||||
// create a large number of rarely tested code paths, which would probably
|
||||
// regress and cause security issues. We prefer to fail fast.
|
||||
// This macro generally behaves like an assert(), except it will make sure to
|
||||
// kill the process even with NDEBUG.
|
||||
#define MP_HANDLE_OOM(x) do { \
|
||||
assert(x); \
|
||||
if (!(x)) \
|
||||
abort(); \
|
||||
} while (0)
|
||||
|
||||
#endif /* MPLAYER_MPCOMMON_H */
|
||||
+14
-10
@@ -34,32 +34,36 @@ struct encode_opts {
|
||||
char *file;
|
||||
char *format;
|
||||
char **fopts;
|
||||
float fps;
|
||||
float maxfps;
|
||||
char *vcodec;
|
||||
char **vopts;
|
||||
char *acodec;
|
||||
char **aopts;
|
||||
int harddup;
|
||||
float voffset;
|
||||
float aoffset;
|
||||
int copyts;
|
||||
int rawts;
|
||||
int autofps;
|
||||
int neverdrop;
|
||||
int video_first;
|
||||
int audio_first;
|
||||
int copy_metadata;
|
||||
char **set_metadata;
|
||||
char **remove_metadata;
|
||||
int metadata;
|
||||
};
|
||||
|
||||
// interface for player core
|
||||
struct encode_lavc_context *encode_lavc_init(struct mpv_global *global);
|
||||
bool encode_lavc_free(struct encode_lavc_context *ctx);
|
||||
// interface for mplayer.c
|
||||
struct encode_lavc_context *encode_lavc_init(struct encode_opts *options,
|
||||
struct mpv_global *global);
|
||||
void encode_lavc_finish(struct encode_lavc_context *ctx);
|
||||
void encode_lavc_free(struct encode_lavc_context *ctx);
|
||||
void encode_lavc_discontinuity(struct encode_lavc_context *ctx);
|
||||
bool encode_lavc_showhelp(struct mp_log *log, struct encode_opts *options);
|
||||
int encode_lavc_getstatus(struct encode_lavc_context *ctx, char *buf, int bufsize, float relative_position);
|
||||
void encode_lavc_expect_stream(struct encode_lavc_context *ctx,
|
||||
enum stream_type type);
|
||||
void encode_lavc_stream_eof(struct encode_lavc_context *ctx,
|
||||
enum stream_type type);
|
||||
void encode_lavc_expect_stream(struct encode_lavc_context *ctx, int mt);
|
||||
void encode_lavc_set_metadata(struct encode_lavc_context *ctx,
|
||||
struct mp_tags *metadata);
|
||||
void encode_lavc_set_video_fps(struct encode_lavc_context *ctx, float fps);
|
||||
void encode_lavc_set_audio_pts(struct encode_lavc_context *ctx, double pts);
|
||||
bool encode_lavc_didfail(struct encode_lavc_context *ctx); // check if encoding failed
|
||||
|
||||
|
||||
+838
-607
File diff suppressed because it is too large.
Load diff
+65
-56
@@ -31,26 +31,43 @@
|
||||
#include <libavutil/opt.h>
|
||||
#include <libavutil/mathematics.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "encode.h"
|
||||
#include "video/csputils.h"
|
||||
|
||||
struct encode_lavc_context {
|
||||
// --- Immutable after init
|
||||
struct mpv_global *global;
|
||||
struct encode_opts *options;
|
||||
struct mp_log *log;
|
||||
struct encode_priv *priv;
|
||||
AVOutputFormat *oformat;
|
||||
const char *filename;
|
||||
struct mp_tags *metadata;
|
||||
|
||||
// All entry points must be guarded with the lock. Functions called by
|
||||
// the playback core lock this automatically, but ao_lavc.c and vo_lavc.c
|
||||
// must lock manually before accessing state.
|
||||
pthread_mutex_t lock;
|
||||
|
||||
float vo_fps;
|
||||
|
||||
// FFmpeg contexts.
|
||||
AVFormatContext *avc;
|
||||
AVStream *vst;
|
||||
AVStream *ast;
|
||||
AVCodecContext *vcc;
|
||||
AVCodecContext *acc;
|
||||
|
||||
// these are processed from the options
|
||||
AVRational timebase;
|
||||
AVCodec *vc;
|
||||
AVCodec *ac;
|
||||
AVDictionary *foptions;
|
||||
AVDictionary *aoptions;
|
||||
AVDictionary *voptions;
|
||||
|
||||
// values created during encoding
|
||||
int header_written; // -1 means currently writing
|
||||
|
||||
// sync to audio mode
|
||||
double audio_pts_offset;
|
||||
double last_video_in_pts;
|
||||
|
||||
double last_audio_in_pts;
|
||||
int64_t samples_since_last_pts;
|
||||
@@ -58,59 +75,51 @@ struct encode_lavc_context {
|
||||
// anti discontinuity mode
|
||||
double next_in_pts;
|
||||
double discontinuity_pts_offset;
|
||||
|
||||
long long abytes;
|
||||
long long vbytes;
|
||||
struct stream *twopass_bytebuffer_a;
|
||||
struct stream *twopass_bytebuffer_v;
|
||||
double t0;
|
||||
unsigned int frames;
|
||||
double audioseconds;
|
||||
|
||||
bool expect_video;
|
||||
bool expect_audio;
|
||||
bool video_first;
|
||||
bool audio_first;
|
||||
|
||||
// has encoding failed?
|
||||
bool failed;
|
||||
bool finished;
|
||||
};
|
||||
|
||||
// --- interface for vo/ao drivers
|
||||
// interface for vo/ao drivers
|
||||
int encode_lavc_alloc_stream(struct encode_lavc_context *ctx,
|
||||
enum AVMediaType mt, AVStream **stream_out,
|
||||
AVCodecContext **codec_out);
|
||||
void encode_lavc_write_stats(struct encode_lavc_context *ctx,
|
||||
AVCodecContext *stream);
|
||||
int encode_lavc_write_frame(struct encode_lavc_context *ctx, AVStream *stream,
|
||||
AVPacket *packet);
|
||||
int encode_lavc_supports_pixfmt(struct encode_lavc_context *ctx, enum AVPixelFormat format);
|
||||
int encode_lavc_open_codec(struct encode_lavc_context *ctx,
|
||||
AVCodecContext *codec);
|
||||
int encode_lavc_available(struct encode_lavc_context *ctx);
|
||||
int encode_lavc_timesyncfailed(struct encode_lavc_context *ctx);
|
||||
int encode_lavc_start(struct encode_lavc_context *ctx); // returns 1 on success
|
||||
int encode_lavc_oformat_flags(struct encode_lavc_context *ctx);
|
||||
double encode_lavc_getoffset(struct encode_lavc_context *ctx,
|
||||
AVCodecContext *codec);
|
||||
void encode_lavc_fail(struct encode_lavc_context *ctx, const char *format, ...); // report failure of encoding
|
||||
|
||||
// Static information after encoder init. This never changes (even if there are
|
||||
// dynamic runtime changes, they have to work over AVPacket side data).
|
||||
// For use in encoder_context, most fields are copied from encoder_context.encoder
|
||||
// by encoder_init_codec_and_muxer().
|
||||
struct encoder_stream_info {
|
||||
AVRational timebase; // timebase used by the encoder (in frames/out packets)
|
||||
AVCodecParameters *codecpar;
|
||||
};
|
||||
|
||||
// The encoder parts for each stream (no muxing parts included).
|
||||
// This is private to each stream.
|
||||
struct encoder_context {
|
||||
struct mpv_global *global;
|
||||
struct encode_opts *options;
|
||||
struct mp_log *log;
|
||||
AVOutputFormat *oformat;
|
||||
|
||||
// (avoid using this)
|
||||
struct encode_lavc_context *encode_lavc_ctx;
|
||||
|
||||
enum stream_type type;
|
||||
|
||||
// (different access restrictions before/after encoder init)
|
||||
struct encoder_stream_info info;
|
||||
AVCodecContext *encoder;
|
||||
struct mux_stream *mux_stream;
|
||||
|
||||
struct stream *twopass_bytebuffer;
|
||||
};
|
||||
|
||||
// Free with talloc_free(). (Keep in mind actual deinitialization requires
|
||||
// sending a flush packet.)
|
||||
// This can fail and return NULL.
|
||||
struct encoder_context *encoder_context_alloc(struct encode_lavc_context *ctx,
|
||||
enum stream_type type,
|
||||
struct mp_log *log);
|
||||
|
||||
// After setting your codec parameters on p->encoder, you call this to "open"
|
||||
// the encoder. This also initializes p->mux_stream. Returns false on failure.
|
||||
// on_ready is called as soon as the muxer has been initialized. Then you are
|
||||
// allowed to write packets with encoder_encode().
|
||||
// Warning: the on_ready callback is called asynchronously, so you need to
|
||||
// make sure to properly synchronize everything.
|
||||
bool encoder_init_codec_and_muxer(struct encoder_context *p,
|
||||
void (*on_ready)(void *ctx), void *ctx);
|
||||
|
||||
// Encode the frame and write the packet. frame is ref'ed as need.
|
||||
bool encoder_encode(struct encoder_context *p, AVFrame *frame);
|
||||
|
||||
double encoder_get_offset(struct encoder_context *p);
|
||||
bool encode_lavc_set_csp(struct encode_lavc_context *ctx,
|
||||
AVCodecContext *codec, enum mp_csp csp);
|
||||
bool encode_lavc_set_csp_levels(struct encode_lavc_context *ctx,
|
||||
AVCodecContext *codec, enum mp_csp_levels lev);
|
||||
enum mp_csp encode_lavc_get_csp(struct encode_lavc_context *ctx,
|
||||
AVCodecContext *codec);
|
||||
enum mp_csp_levels encode_lavc_get_csp_levels(struct encode_lavc_context *ctx,
|
||||
AVCodecContext *codec);
|
||||
|
||||
#endif
|
||||
+11
-9
@@ -1,18 +1,20 @@
|
||||
/*
|
||||
* 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 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 Lesser General Public License for more details.
|
||||
* GNU 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/>.
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Almost LGPL.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
@@ -120,7 +122,7 @@ static void update_loglevel(struct mp_log *log)
|
||||
for (int n = 0; n < log->root->num_buffers; n++)
|
||||
log->level = MPMAX(log->level, log->root->buffers[n]->level);
|
||||
if (log->root->log_file)
|
||||
log->level = MPMAX(log->level, MSGL_DEBUG);
|
||||
log->level = MPMAX(log->level, MSGL_V);
|
||||
if (log->root->stats_file)
|
||||
log->level = MPMAX(log->level, MSGL_STATS);
|
||||
atomic_store(&log->reload_counter, atomic_load(&log->root->reload_counter));
|
||||
@@ -285,7 +287,7 @@ static void write_log_file(struct mp_log *log, int lev, char *text)
|
||||
{
|
||||
struct mp_log_root *root = log->root;
|
||||
|
||||
if (!root->log_file || lev > MPMAX(MSGL_DEBUG, log->terminal_level))
|
||||
if (lev > MSGL_V || !root->log_file)
|
||||
return;
|
||||
|
||||
fprintf(root->log_file, "[%8.3f][%c][%s] %s",
|
||||
|
||||
+1
-1
@@ -160,7 +160,7 @@ struct mp_recorder *mp_recorder_create(struct mpv_global *global,
|
||||
av_dict_set(&priv->mux->metadata, "encoding_tool", version, 0);
|
||||
|
||||
if (avformat_write_header(priv->mux, NULL) < 0) {
|
||||
MP_ERR(priv, "Writing header failed.\n");
|
||||
MP_ERR(priv, "Write header failed.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
|
||||
+7
-34
@@ -18,7 +18,6 @@
|
||||
#include <stddef.h>
|
||||
#include <limits.h>
|
||||
#include <strings.h>
|
||||
#include <assert.h>
|
||||
#include <libavutil/dict.h>
|
||||
#include "tags.h"
|
||||
#include "misc/bstr.h"
|
||||
@@ -45,24 +44,6 @@ void mp_tags_set_bstr(struct mp_tags *tags, bstr key, bstr value)
|
||||
tags->num_keys++;
|
||||
}
|
||||
|
||||
void mp_tags_remove_str(struct mp_tags *tags, const char *key)
|
||||
{
|
||||
mp_tags_remove_bstr(tags, bstr0(key));
|
||||
}
|
||||
|
||||
void mp_tags_remove_bstr(struct mp_tags *tags, bstr key)
|
||||
{
|
||||
for (int n = 0; n < tags->num_keys; n++) {
|
||||
if (bstrcasecmp0(key, tags->keys[n]) == 0) {
|
||||
talloc_free(tags->keys[n]);
|
||||
talloc_free(tags->values[n]);
|
||||
int num_keys = tags->num_keys; // copy so it's only decremented once
|
||||
MP_TARRAY_REMOVE_AT(tags->keys, num_keys, n);
|
||||
MP_TARRAY_REMOVE_AT(tags->values, tags->num_keys, n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char *mp_tags_get_str(struct mp_tags *tags, const char *key)
|
||||
{
|
||||
return mp_tags_get_bstr(tags, bstr0(key));
|
||||
@@ -83,25 +64,17 @@ void mp_tags_clear(struct mp_tags *tags)
|
||||
talloc_free_children(tags);
|
||||
}
|
||||
|
||||
|
||||
|
||||
struct mp_tags *mp_tags_dup(void *tparent, struct mp_tags *tags)
|
||||
{
|
||||
struct mp_tags *new = talloc_zero(tparent, struct mp_tags);
|
||||
mp_tags_replace(new, tags);
|
||||
return new;
|
||||
}
|
||||
|
||||
void mp_tags_replace(struct mp_tags *dst, struct mp_tags *src)
|
||||
{
|
||||
mp_tags_clear(dst);
|
||||
MP_RESIZE_ARRAY(dst, dst->keys, src->num_keys);
|
||||
MP_RESIZE_ARRAY(dst, dst->values, src->num_keys);
|
||||
dst->num_keys = src->num_keys;
|
||||
for (int n = 0; n < src->num_keys; n++) {
|
||||
dst->keys[n] = talloc_strdup(dst, src->keys[n]);
|
||||
dst->values[n] = talloc_strdup(dst, src->values[n]);
|
||||
MP_RESIZE_ARRAY(new, new->keys, tags->num_keys);
|
||||
MP_RESIZE_ARRAY(new, new->values, tags->num_keys);
|
||||
new->num_keys = tags->num_keys;
|
||||
for (int n = 0; n < tags->num_keys; n++) {
|
||||
new->keys[n] = talloc_strdup(new, tags->keys[n]);
|
||||
new->values[n] = talloc_strdup(new, tags->values[n]);
|
||||
}
|
||||
return new;
|
||||
}
|
||||
|
||||
// Return a copy of the tags, but containing only keys in list. Also forces
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
#ifndef MP_TAGS_H
|
||||
#define MP_TAGS_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "misc/bstr.h"
|
||||
|
||||
struct mp_tags {
|
||||
@@ -13,13 +11,10 @@ struct mp_tags {
|
||||
|
||||
void mp_tags_set_str(struct mp_tags *tags, const char *key, const char *value);
|
||||
void mp_tags_set_bstr(struct mp_tags *tags, bstr key, bstr value);
|
||||
void mp_tags_remove_str(struct mp_tags *tags, const char *key);
|
||||
void mp_tags_remove_bstr(struct mp_tags *tags, bstr key);
|
||||
char *mp_tags_get_str(struct mp_tags *tags, const char *key);
|
||||
char *mp_tags_get_bstr(struct mp_tags *tags, bstr key);
|
||||
void mp_tags_clear(struct mp_tags *tags);
|
||||
struct mp_tags *mp_tags_dup(void *tparent, struct mp_tags *tags);
|
||||
void mp_tags_replace(struct mp_tags *dst, struct mp_tags *src);
|
||||
struct mp_tags *mp_tags_filtered(void *tparent, struct mp_tags *tags, char **list);
|
||||
void mp_tags_merge(struct mp_tags *tags, struct mp_tags *src);
|
||||
struct AVDictionary;
|
||||
|
||||
@@ -24,4 +24,3 @@
|
||||
|
||||
const char mpv_version[] = "mpv " VERSION;
|
||||
const char mpv_builddate[] = BUILDDATE;
|
||||
const char mpv_copyright[] = MPVCOPYRIGHT;
|
||||
Loaded 100 of 477 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user