Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
723fd02919 | ||
|
|
7c2368ad7d | ||
|
|
008ddf42a1 | ||
|
|
5b9cf1d0a6 | ||
|
|
a985e8ed6b | ||
|
|
36eeeecff6 | ||
|
|
e74ffab36e | ||
|
|
4f47596043 | ||
|
|
3e8fd22f07 | ||
|
|
53a1a955d7 | ||
|
|
5a9add1fb3 | ||
|
|
89e52a228c | ||
|
|
46969b449a | ||
|
|
ed9d644e06 | ||
|
|
5a7aca4be8 | ||
|
|
af39f87258 | ||
|
|
e0682490d7 | ||
|
|
03e3abcb0c | ||
|
|
1dd77e41c1 | ||
|
|
8e0b94e7eb | ||
|
|
0f7b08b0fc | ||
|
|
a8a4c7def7 | ||
|
|
c994a81aa4 | ||
|
|
5ae13ad13b | ||
|
|
0a92548684 | ||
|
|
56098c5636 | ||
|
|
312b88d360 | ||
|
|
b97f172faa | ||
|
|
f3a58e5f4a | ||
|
|
49606f5a3b | ||
|
|
7d5468ace3 | ||
|
|
a4e7580c5a | ||
|
|
7b91bf7f20 | ||
|
|
4243d06761 | ||
|
|
4d074a960d | ||
|
|
071556efee | ||
|
|
876341417a | ||
|
|
6a57e1a298 | ||
|
|
0e316d7032 | ||
|
|
0914da082f | ||
|
|
021bea1592 | ||
|
|
0d134dc010 |
No files matched your search
@@ -1,5 +1,7 @@
|
||||
Read this before you submit this pull request:
|
||||
https://github.com/mpv-player/mpv/blob/master/DOCS/contribute.md
|
||||
Ideally, you shouldn't need enter any text here, and your commit messages should
|
||||
explain your changes sufficiently (especially why they are needed). Read
|
||||
https://github.com/mpv-player/mpv/blob/master/DOCS/contribute.md for coding
|
||||
style and development conventions. Remove this text block, but if you haven't
|
||||
agreed to it before, leave the following sentence in place:
|
||||
|
||||
Reading this link and following the rules will get your pull request reviewed
|
||||
and merged faster. Nobody wants lazy pull requests.
|
||||
I agree that my changes can be relicensed to LGPL 2.1 or later.
|
||||
+12
-42
@@ -1,34 +1,21 @@
|
||||
language: c
|
||||
language: generic
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- os: osx
|
||||
osx_image: xcode11
|
||||
compiler: clang
|
||||
- os: osx
|
||||
osx_image: xcode10.1
|
||||
compiler: clang
|
||||
- os: osx
|
||||
osx_image: xcode9.2
|
||||
compiler: clang
|
||||
- os: linux
|
||||
compiler: gcc
|
||||
env: CONTAINER=registry.cirno.systems/kiwi/containers/mpv-ci:stable-deps CI_SCRIPT=ci/build-tumbleweed.sh
|
||||
- os: linux
|
||||
compiler: clang
|
||||
env: CONTAINER=registry.cirno.systems/kiwi/containers/mpv-ci:stable-deps CI_SCRIPT=ci/build-tumbleweed.sh
|
||||
- os: linux
|
||||
env: CONTAINER=registry.cirno.systems/kiwi/containers/mpv-ci-mingw64:i686 CI_SCRIPT=ci/build-mingw64.sh TARGET=i686-w64-mingw32
|
||||
- os: linux
|
||||
env: CONTAINER=registry.cirno.systems/kiwi/containers/mpv-ci-mingw64:x86_64 CI_SCRIPT=ci/build-mingw64.sh TARGET=x86_64-w64-mingw32
|
||||
os:
|
||||
- linux
|
||||
|
||||
dist: bionic
|
||||
services:
|
||||
- docker
|
||||
|
||||
sudo: required
|
||||
|
||||
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
|
||||
global:
|
||||
# Coverity token
|
||||
- secure: "H21mSRlMhk4BKS0xHZvCFGJxteCP0hRVUxTuNfM2Z9HBsyutuLEYMtViLO86VtM+Tqla3xXPzUdS4ozLwI72Ax/5ZUDXACROj73yW6QhFB5D6rLut12+FjqC7M33Qv2hl0xwgNBmR5dsm1ToP37+Wn+ecJQNvN8fkTXF+HVzOEw="
|
||||
@@ -43,14 +30,10 @@ branches:
|
||||
- /release\/.*$/
|
||||
|
||||
before_install:
|
||||
- if [ "$TRAVIS_COMPILER" = "clang" ]; then export CXX="clang++"; fi
|
||||
- if [ "$TRAVIS_COMPILER" = "gcc" ]; then export CXX="g++"; fi
|
||||
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then docker pull $CONTAINER; fi
|
||||
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then "$TRAVIS_BUILD_DIR/ci/get_ffmpeg.sh"; fi
|
||||
- docker pull $CONTAINER
|
||||
script:
|
||||
- ./bootstrap.py
|
||||
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then docker run --env CC --env TARGET -v $TRAVIS_BUILD_DIR:/build $CONTAINER /bin/sh -c "cd /build && $CI_SCRIPT"; fi
|
||||
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then ./ci/build-macos.sh; fi
|
||||
- docker run --env CC --env TARGET -v $TRAVIS_BUILD_DIR:/build $CONTAINER /bin/sh -c "cd /build && $CI_SCRIPT"
|
||||
after_failure: cat ./build/config.log
|
||||
after_script: TOOLS/travis-rebuild-website
|
||||
|
||||
@@ -71,16 +54,3 @@ addons:
|
||||
build_command_prepend: "./bootstrap.py && ./waf configure"
|
||||
build_command: "./waf build"
|
||||
branch_pattern: coverity_scan
|
||||
homebrew:
|
||||
packages:
|
||||
- autoconf
|
||||
- automake
|
||||
- freetype
|
||||
- fribidi
|
||||
- libass
|
||||
- libtool
|
||||
- little-cms2
|
||||
- luajit
|
||||
- nasm
|
||||
- pkg-config
|
||||
update: true
|
||||
@@ -21,8 +21,6 @@ project can relicense the changes to LGPLv2.1+ at a later point without asking
|
||||
the contributor. This is a safeguard for making potential relicensing of
|
||||
remaining GPL code to LGPLv2.1+ easier.
|
||||
|
||||
See DOCS/contribute.md for binding rules wrt. licensing for contributions.
|
||||
|
||||
For information about authors and contributors, consult the git log, which
|
||||
contains the complete SVN and CVS history as well.
|
||||
|
||||
@@ -49,11 +47,19 @@ The following files are still GPL only (--enable-lgpl disables them):
|
||||
|
||||
audio/out/ao_jack.c will stay GPL
|
||||
audio/out/ao_oss.c will stay GPL
|
||||
demux/demux_tv.c will stay GPL
|
||||
stream/ai_* will stay GPL (TV code)
|
||||
stream/audio_in.* will stay GPL (TV code)
|
||||
stream/dvb* must stay GPL
|
||||
stream/frequencies.* must stay GPL
|
||||
stream/stream_cdda.c unknown
|
||||
stream/stream_dvb.* must stay GPL
|
||||
stream/stream_dvd.c unknown
|
||||
stream/stream_dvd_common.* unknown
|
||||
stream/stream_dvdnav.c unknown
|
||||
stream/stream_smb.c will stay GPLv3
|
||||
stream/stream_tv.c will stay GPL
|
||||
stream/tv* will stay GPL
|
||||
video/out/vo_caca.c unknown
|
||||
video/out/vo_direct3d.c unknown
|
||||
video/out/vo_vaapi.c probably impossible (some company's code)
|
||||
@@ -70,6 +76,10 @@ The following files are still GPL only (--enable-lgpl disables them):
|
||||
mpv.desktop unknown license, probably GPLv2+
|
||||
etc/restore-old-bindings.conf unknown license, probably GPLv2+
|
||||
|
||||
None of the cases listed above affect the final binary if it's built as
|
||||
The following files contain some optional GPL code (--enable-lgpl disables it):
|
||||
|
||||
options/parse_commandline.c dvd:// expansion
|
||||
|
||||
None of the exceptions listed above affect the final binary if it's built as
|
||||
LGPL. Linked libraries still can affect the final license (for example if
|
||||
FFmpeg was built as GPL).
|
||||
@@ -16,7 +16,7 @@ The version number is the same as used for MPV_CLIENT_API_VERSION (see
|
||||
``client.h`` how to convert between major/minor version numbers and the flat
|
||||
32 bit integer).
|
||||
|
||||
Also, read the section ``Compatibility`` in ``client.h``, and compatibility.rst.
|
||||
Also, read the section ``Compatibility`` in ``client.h``.
|
||||
|
||||
Options, commands, properties
|
||||
=============================
|
||||
@@ -32,24 +32,6 @@ API changes
|
||||
|
||||
::
|
||||
|
||||
--- mpv 0.30.0 ---
|
||||
1.106 - Add cancel_fn to mpv_stream_cb_info
|
||||
1.105 - Fix deadlock problems with MPV_RENDER_PARAM_ADVANCED_CONTROL and if
|
||||
the "vd-lavc-dr" option is enabled (which it is by default).
|
||||
There were no actual API changes.
|
||||
API users on older API versions and mpv releases should set
|
||||
"vd-lavc-dr" to "no" to avoid these issues.
|
||||
API users must still adhere to the tricky rules documented in render.h
|
||||
to avoid other deadlocks.
|
||||
1.104 - Deprecate struct mpv_opengl_drm_params. Replaced by mpv_opengl_drm_params_v2
|
||||
- Deprecate MPV_RENDER_PARAM_DRM_DISPLAY. Replaced by MPV_RENDER_PARAM_DRM_DISPLAY_V2.
|
||||
1.103 - redo handling of async commands
|
||||
- add mpv_event_command and make it possible to return values from
|
||||
commands issued with mpv_command_async() or mpv_command_node_async()
|
||||
- add mpv_abort_async_command()
|
||||
1.102 - rename struct mpv_opengl_drm_osd_size to mpv_opengl_drm_draw_surface_size
|
||||
- rename MPV_RENDER_PARAM_DRM_OSD_SIZE to MPV_RENDER_PARAM_DRM_DRAW_SURFACE_SIZE
|
||||
|
||||
--- mpv 0.29.0 ---
|
||||
1.101 - add MPV_RENDER_PARAM_ADVANCED_CONTROL and related API
|
||||
- add MPV_RENDER_PARAM_NEXT_FRAME_INFO and related symbols
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
CLI and API compatibility policy
|
||||
================================
|
||||
|
||||
Human users and API users rely on the mpv/libmpv/scripting/IPC interface not
|
||||
breaking their use case. On the other hand, active development occasionally
|
||||
requires breaking things, such as removing old options or changing options in
|
||||
a way that may break.
|
||||
|
||||
This document lists rules when, what, and how incompatible changes can be made.
|
||||
It's interesting both for mpv developers, who want to change user-visible parts,
|
||||
and mpv users, who want to know what is guaranteed to remain stable.
|
||||
|
||||
Any of the rules below may be overridden by statements in more specific
|
||||
documentation (for example, if the manpage says that a particular option may be
|
||||
removed any time, it means that, and the option probably won't even go through
|
||||
deprecation).
|
||||
|
||||
Additions
|
||||
---------
|
||||
|
||||
Additions are basically always allowed. API users etc. are supposed to deal with
|
||||
the possibility that for example new API functions are added. Some parts of the
|
||||
API may document how they are extended.
|
||||
|
||||
Options, commands, properties, events, hooks (command interface)
|
||||
----------------------------------------------------------------
|
||||
|
||||
All of these are important for interfacing both with end users and API users
|
||||
(which include Lua scripts, libmpv, and the JSON IPC). As such, they constitute
|
||||
a large part of the user interface and APIs.
|
||||
|
||||
All incompatible changes to this must be declared in interface-changes.rst.
|
||||
(This may also list compatible additions, but it's not a requirement.)
|
||||
|
||||
Degrees of importance and compatibility preservation
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Critical and central parts of the command interface have the strictest
|
||||
requirements. It may not be reasonable to break them, and other means to achieve
|
||||
some change have to be found. For example, the "seek" command is a bit of a
|
||||
mess, but since changing it would likely affect almost every user, it may be
|
||||
impossible to break at least the commonly used syntax. If changed anyway, there
|
||||
should be a deprecation period of at least 1 year, during which the command
|
||||
still works, and possibly a warning should remain even after this.
|
||||
|
||||
Important/often used parts must be deprecated for at least 2 releases before
|
||||
they can be broken. There must be at least 1 release where the deprecated
|
||||
functionality still works, and a replacement is available (if technically
|
||||
reasonable). For example, a feature deprecated in mpv 0.30.0 may be removed in
|
||||
mpv 0.32.0. Minor releases do not count towards this.
|
||||
|
||||
Less useful parts can be broken immediately, but must come with some sort of
|
||||
removal warning-
|
||||
|
||||
Parts for debugging and testing may be removed any time, potentially even
|
||||
without any sort of documentation.
|
||||
|
||||
Currently, the importance of a part is not documented and not even well-defined,
|
||||
which is probably a mistake.
|
||||
|
||||
Renaming or removing options
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Typically, renaming an option can be done in a compatible way with OPT_REPLACED.
|
||||
You may need to check whether the corresponding properties still work (including
|
||||
messy details like observing properties for changes).
|
||||
|
||||
OPT_REMOVED can be used to inform the user of alternatives or reasons for the
|
||||
removal, which is better than an option not found error. Likewise,
|
||||
m_option.deprecation_message should be set to something helpful.
|
||||
|
||||
Both OPT_REPLACED and OPT_REMOVED can remain in the code for a long time, since
|
||||
they're unintrusive and hopefully make incompatible changes less painful for
|
||||
users.
|
||||
|
||||
Scripting APIs
|
||||
--------------
|
||||
|
||||
This affects internal scripting APIs (currently Lua and JavaScript).
|
||||
|
||||
Vaguely the same rules as with the command interface apply. Since there is a
|
||||
large number of scripts, an effort should be made to provide compatibility
|
||||
for old scripts, but it does not need to be stronger than that of the command
|
||||
interface.
|
||||
|
||||
Undocumented parts of the scripting APIs are _not_ guaranteed for compatibility.
|
||||
This applies especially for internals. Languages like Lua do not have strict
|
||||
access control (nor does the mpv code try to emulate any), so if a script
|
||||
accesses private parts, and breaks on the next mpv release, it's not mpv's
|
||||
problem.
|
||||
|
||||
JSON IPC
|
||||
--------
|
||||
|
||||
The JSON IPC is a thin protocol wrapping the libmpv API and the command
|
||||
interface. Compatibility-wise, it's about the same as the scripting APIs.
|
||||
The JSON protocol commands should remain as compatible as possible, and it
|
||||
should probably accept the current way commands are delimited (line breaks)
|
||||
forever.
|
||||
|
||||
The protocol may accept non-standard JSON extensions, but only standard JSON
|
||||
(possibly with restrictions) is guaranteed for compatibility. Clients which want
|
||||
to remain compatible should not use any extensions.
|
||||
|
||||
CLI
|
||||
---
|
||||
|
||||
Things such as default key bindings do not necessarily require compatibility.
|
||||
However, the release notes should be extremely clear on changes to "important"
|
||||
key bindings. Bindings which restore the old behavior should be added to
|
||||
restore-old-bindings.conf.
|
||||
|
||||
Some option parsing is CLI-only and not available from libmpv or scripting. No
|
||||
compatibility guarantees come with them. However, the rules which mpv uses to
|
||||
distinguish between options and filenames must remain consistent (if the
|
||||
non-deprecated options syntax is used).
|
||||
|
||||
Terminal and log output
|
||||
-----------------------
|
||||
|
||||
There are no compatibility guarantees for the terminal output, or the text
|
||||
logged via ``MPV_EVENT_LOG_MESSAGE`` and similar APIs. In particular, scripts
|
||||
invoking mpv CLI are extremely discouraged from trying to parse text output,
|
||||
and should use other mechanisms such as the JSON IPC.
|
||||
|
||||
Protocols, filters, demuxers, etc.
|
||||
----------------------------------
|
||||
|
||||
Which of these are present is generally not guaranteed, and can even depend
|
||||
on compile time settings.
|
||||
|
||||
The filter list and their sub-options are considered part of the
|
||||
command-interface.
|
||||
|
||||
libmpv C API
|
||||
------------
|
||||
|
||||
The libmpv client API (such as ``<libmpv/client.h>``) mostly gives access to
|
||||
the command interface. The API itself (if looked at as a component separate
|
||||
from the command interface) is intended to be extremely stable.
|
||||
|
||||
All API changes are documented in client-api-changes.rst.
|
||||
|
||||
API compatibility
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
The API is *always* compatible. Incompatible changes are only allowed on major
|
||||
API version changes (see ``MPV_CLIENT_API_VERSION``). A major version change is
|
||||
an extremely rare event, which means usually no API symbols are ever removed.
|
||||
|
||||
Essentially removing API functions by making them always return an error, or
|
||||
making it do nothing is allowed in cases where it is unlikely to break most
|
||||
clients, but requires a deprecation period of 2 releases. (This has happened to
|
||||
``mpv_suspend()`` for example.)
|
||||
|
||||
API symbols can be deprecated. This should be clearly marked in the doxygen
|
||||
with ``@deprecated``, and if possible, the affected API symbols should not be
|
||||
visible if the API user defines ``MPV_ENABLE_DEPRECATED`` to 0.
|
||||
|
||||
ABI compatibility
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
The ABI must never be broken, except on major API version changes. For example,
|
||||
constants don't change their values.
|
||||
|
||||
Structs are tricky. If a struct can be allocated by a user (such as ``mpv_node``),
|
||||
no fields can be added. (Unless it's an union, and the addition does not change
|
||||
the offset or alignment of any of the fields or the struct itself. This has
|
||||
happened to ``mpv_node`` in the past.) If a struct is allocated by libmpv only,
|
||||
new fields can be appended to the end (for example ``mpv_event``).
|
||||
|
||||
The ABI is only backward compatible. This means if a host application is linked
|
||||
to an older libmpv, and libmpv is updated to a newer version, it will still
|
||||
work (as in not causing any undefined behavior).
|
||||
|
||||
Forward compatibility (an application would work with an older libmpv than it
|
||||
was linked to) is not required.
|
||||
@@ -136,7 +136,7 @@ pacman -S git python $MINGW_PACKAGE_PREFIX-{pkg-config,gcc}
|
||||
|
||||
# Install the most important MinGW-w64 dependencies. libass and lcms2 are also
|
||||
# pulled in as dependencies of ffmpeg.
|
||||
pacman -S $MINGW_PACKAGE_PREFIX-{ffmpeg,libjpeg-turbo,lua51}
|
||||
pacman -S $MINGW_PACKAGE_PREFIX-{ffmpeg,libjpeg-turbo,lua51,angleproject-git}
|
||||
```
|
||||
|
||||
Building mpv
|
||||
|
||||
+20
-69
@@ -5,53 +5,28 @@ General
|
||||
-------
|
||||
|
||||
The main contact for mpv development is IRC, specifically #mpv
|
||||
and #mpv-devel on Freenode. Github is used for code review and
|
||||
long term discussions.
|
||||
and #mpv-devel on Freenode.
|
||||
|
||||
Sending patches
|
||||
---------------
|
||||
|
||||
- Make a github pull request, or send a link to a plaintext patch created with
|
||||
``git format-patch``.
|
||||
- Plain diffs posted as pastebins are not acceptable! (Especially if the http
|
||||
link returns HTML.) They only cause extra work for everyone, because they lack
|
||||
commit message and authorship information.
|
||||
- Never send patches to any of the developers email addresses.
|
||||
- If your changes are not supposed to be merged immediately, mark them as
|
||||
"[RFC]" in the commit message or the pull request title.
|
||||
- Be sure to test your changes. If you didn't, please say so in the commit
|
||||
message and the pull request text.
|
||||
|
||||
Copyright of contributions
|
||||
--------------------------
|
||||
|
||||
- The copyright belongs to contributors. The project is a collaborative work. By
|
||||
sending your changes, you agree to license your contributions according to the
|
||||
requirements of this project.
|
||||
``git format-patch``. diffs posted as pastebins (especially if the http link
|
||||
returns HTML) just cause extra work for everyone, because they lack commit
|
||||
message and authorship information.
|
||||
- All new code must be LGPLv2.1+ licensed, or come with the implicit agreement
|
||||
that it will be relicensed to LGPLv2.1+ later (see ``Copyright`` in the
|
||||
repository root directory).
|
||||
- 100% compatible licenses are allowed too.
|
||||
- Changes in files with more liberal licenses (such as BSD, MIT, or ISC) are
|
||||
assumed to be dual-licensed under LGPLv2.1+ and the license indicated in the
|
||||
file header.
|
||||
- You must be either the exclusive author of the patch, or acknowledge all
|
||||
authors involved in the commit message. If you take 3rd party code, authorship
|
||||
and copyright must be properly acknowledged. If you're making changes on
|
||||
behalf of your employer, and the employer owns the copyright, you must mention
|
||||
this. If the license of the code is not LGPLv2.1+, you must mention this.
|
||||
- These license statements are legally binding.
|
||||
and copyright must be properly acknowledged. If the license of the code is not
|
||||
LGPLv2.1+, this must be mentioned.
|
||||
- Don't use fake names (something that looks like an actual names, and may be
|
||||
someone else's name, but is not your legal name). Using a pseudonyms is
|
||||
allowed if it can be used to identify or contact you, even if whatever
|
||||
account you used to submit the patch dies.
|
||||
- Do not add your name to the license header. This convention is not used by
|
||||
this project, and neither copyright law nor any of the used licenses require
|
||||
it.
|
||||
|
||||
Write good commit messages
|
||||
--------------------------
|
||||
|
||||
- When creating pull requests, be sure to test your changes. If you didn't,
|
||||
please say so in the pull request message.
|
||||
- Write informative commit messages. Use present tense to describe the
|
||||
situation with the patch applied, and past tense for the situation before
|
||||
the change.
|
||||
@@ -84,33 +59,18 @@ Write good commit messages
|
||||
|
||||
Important: put an empty line between the subject line and the commit message.
|
||||
If this is missing, it will break display in common git tools.
|
||||
- Another summary of good conventions: https://chris.beams.io/posts/git-commit/
|
||||
|
||||
Split changes into multiple commits
|
||||
-----------------------------------
|
||||
|
||||
- Follow git good practices, and split independent changes into several commits.
|
||||
It's usually OK to put them into a single pull request.
|
||||
- Try to separate cosmetic and functional changes. It's ok to make a few
|
||||
additional cosmetic changes in the same file you're working on. But don't do
|
||||
something like reformatting a whole file, and hiding an actual functional
|
||||
change in the same commit.
|
||||
|
||||
Touching user-visible parts may require updating the mpv docs
|
||||
-------------------------------------------------------------
|
||||
|
||||
- Most user-visible things are normally documented in DOCS/man/. If your commit
|
||||
touches documented behavior, list of sub-options, etc., you need to adjust the
|
||||
documentation.
|
||||
- These changes usually go into the same commit that changes the code.
|
||||
- Changes to command line options (addition/modification/removal) must be
|
||||
documented in options.rst.
|
||||
- Changes to input properties or input commands must be documented in input.rst.
|
||||
- All incompatible changes to the user interface (options, properties, commands)
|
||||
must be documented with a small note in interface-changes.rst. (Additions may
|
||||
be documented there as well, but this isn't required.)
|
||||
- Changes to the libmpv API must be reflected in the libmpv's headers doxygen,
|
||||
and in client-api-changes.rst.
|
||||
documented in options.rst. Changes to input properties or input commands must
|
||||
be documented in input.rst. All changes to the user interface (options,
|
||||
properties, commands) must be documented with a small note in
|
||||
interface-changes.rst (although documenting additions is optional, and
|
||||
obscure corner cases can potentially be skipped too). Changes to the libmpv
|
||||
API must be reflected in the libmpv's headers doxygen, and should be
|
||||
documented in client-api-changes.rst.
|
||||
|
||||
Code formatting
|
||||
---------------
|
||||
@@ -183,20 +143,18 @@ mpv uses C99 with K&R formatting, with some exceptions.
|
||||
|
||||
(If the if body is simple enough, this rule can be skipped.)
|
||||
- Remove any trailing whitespace.
|
||||
- Do not make stray whitespaces changes.
|
||||
|
||||
General coding
|
||||
--------------
|
||||
|
||||
- Use C99. Also freely make use of C99 features if it's appropriate, such as
|
||||
stdbool.h. (Except VLA and complex number types.)
|
||||
- Don't use non-standard language (such as GNU C-only features). In some cases
|
||||
they may be warranted, if they are optional (such as attributes enabling
|
||||
printf-like format string checks). "#pragma once" is allowed as an exception.
|
||||
stdbool.h.
|
||||
- Don't use GNU-only features. In some cases they may be warranted, if they
|
||||
are optional (such as attributes enabling printf-like format string checks).
|
||||
But in general, standard C99 should be used.
|
||||
- The same applies to libc functions. We have to be Windows-compatible too. Use
|
||||
functions guaranteed by C99 or POSIX only, unless your use is guarded by a
|
||||
configure check. There is some restricted use of C11 (ask on IRC for details).
|
||||
configure check.
|
||||
- Prefer fusing declaration and initialization, rather than putting declarations
|
||||
on the top of a block. Obvious data flow is more important than avoiding
|
||||
mixing declarations and statements, which is just a C90 artifact.
|
||||
@@ -204,11 +162,6 @@ General coding
|
||||
channel first. There might be a better way to add a feature and it can avoid
|
||||
wasted work.
|
||||
|
||||
Code of Conduct
|
||||
---------------
|
||||
|
||||
We have one, but the document describing it got lost accidentally.
|
||||
|
||||
Rules for git push access
|
||||
-------------------------
|
||||
|
||||
@@ -227,9 +180,7 @@ you got access, the following rules must be followed:
|
||||
major bug fixes, or you're the original author of the code). If there is more
|
||||
than one maintainer, you may need to come to an agreement with the others how
|
||||
to handle this to avoid conflict.
|
||||
- If you make a pull requests (especially if it's to code you maintain), and you
|
||||
want reviews, explicitly ping the people from which you expect reviews.
|
||||
- As a maintainer, you can approve pull requests by others to "your" code.
|
||||
- As a maintainer, you can approve pushes by others to "your" code.
|
||||
- If you approve or merge 3rd party changes, make sure they follow the general
|
||||
development rules.
|
||||
- Changes to user interface and public API must always be approved by the
|
||||
|
||||
@@ -100,18 +100,6 @@ entries affect all other file entries in the EDL file. Their format is highly
|
||||
implementation specific. They should generally follow the file header, and come
|
||||
before any file entries.
|
||||
|
||||
Disabling chapter generation and copying
|
||||
========================================
|
||||
|
||||
By default, chapters from the source ranges are copied to the virtual file's
|
||||
chapters. Also, a chapter is inserted after each range. This can be disabled
|
||||
with the ``no_chapters`` header.
|
||||
|
||||
Example::
|
||||
|
||||
!no_chapters
|
||||
|
||||
|
||||
MP4 DASH
|
||||
========
|
||||
|
||||
@@ -143,45 +131,6 @@ The current implementation will
|
||||
- not add segment boundaries as chapter points
|
||||
- require full compatibility between all segments (same codec etc.)
|
||||
|
||||
Another header part of this mechanism is ``no_clip``. This header is similar
|
||||
to ``mp4_dash``, but does not include on-demand opening/closing of segments,
|
||||
and does not support init segments. It also exists solely to support internal
|
||||
ytdl requirements.
|
||||
|
||||
The ``mp4_dash`` and ``no_clip`` headers are not part of the core EDL format.
|
||||
They may be changed or removed at any time, depending on mpv's internal
|
||||
requirements.
|
||||
|
||||
Separate files for tracks
|
||||
=========================
|
||||
|
||||
The special ``new_stream`` header lets you specify separate parts and time
|
||||
offsets for separate tracks. This can for example be used to source audio and
|
||||
video track from separate files.
|
||||
|
||||
Example::
|
||||
|
||||
# mpv EDL v0
|
||||
video.mkv
|
||||
!new_stream
|
||||
audio.mkv
|
||||
|
||||
This adds all tracks from both files to the virtual track list. Upon playback,
|
||||
the tracks will be played at the same time, instead of appending them. The files
|
||||
can contain more than 1 stream; the apparent effect is the same as if the second
|
||||
part after the ``!new_stream`` part were in a separate ``.edl`` file and added
|
||||
with ``--external-file``.
|
||||
|
||||
Note that all metadata between the stream sets created by ``new_stream`` is
|
||||
disjoint. Global metadata is taken from the first part only.
|
||||
|
||||
In context of mpv, this is redundant to the ``--audio-file`` and
|
||||
``--external-file`` options, but (as of this writing) has the advantage that
|
||||
this will use a unified cache for all streams.
|
||||
|
||||
The ``new_stream`` header is not part of the core EDL format. It may be changed
|
||||
or removed at any time, depending on mpv's internal requirements.
|
||||
|
||||
Timestamp format
|
||||
================
|
||||
|
||||
@@ -198,54 +147,6 @@ Example::
|
||||
|
||||
Plays chapter 3 and ends with the start of chapter 7 (4 chapters later).
|
||||
|
||||
Implicit chapters
|
||||
=================
|
||||
|
||||
mpv will add one chapter per segment entry to the virtual timeline.
|
||||
|
||||
By default, the chapter's titles will match the entries' filenames.
|
||||
You can override set the ``title`` option to override the chapter title for
|
||||
that segment.
|
||||
|
||||
Example::
|
||||
|
||||
# mpv EDL v0
|
||||
cap.ts,5,240
|
||||
OP.mkv,0,90,title=Show Opening
|
||||
|
||||
The virtual timeline will have two chapters, one called "cap.ts" from 0-240s
|
||||
and a second one called "Show Opening" from 240-330s.
|
||||
|
||||
Entry which defines the track layout
|
||||
====================================
|
||||
|
||||
Normally, you're supposed to put only files with compatible layouts into an EDL
|
||||
file. However, at least the mpv implementation accepts entries that use
|
||||
different codecs, or even have a different number of audio/video/subtitle
|
||||
tracks. In this case, it's not obvious, which virtual tracks the EDL show should
|
||||
expose when being played.
|
||||
|
||||
Currently, mpv will apply an arbitrary heuristic which tracks the EDL file
|
||||
should expose. (Before mpv 0.30.0, it always used the first source file in the
|
||||
segment list.)
|
||||
|
||||
You can set the ``layout`` option to ``this`` to make a specific entry define
|
||||
the track layout.
|
||||
|
||||
Example::
|
||||
|
||||
# mpv EDL v0
|
||||
file_with_2_streams.ts,5,240
|
||||
file_with_5_streams.mkv,0,90,layout=this
|
||||
|
||||
The way the different virtual EDL tracks are associated with the per-segment
|
||||
ones is highly implementation-defined, and uses a heuristic. If a segment is
|
||||
missing a track, there will be a "hole", and bad behavior may result. Improving
|
||||
this is subject to further development (due to being fringe cases, they don't
|
||||
have a high priority).
|
||||
|
||||
If future versions of mpv change this again, this option may be ignored.
|
||||
|
||||
Syntax of EDL URIs
|
||||
==================
|
||||
|
||||
@@ -254,6 +155,3 @@ header, the syntax is exactly the same. It's far more convenient to use ``;``
|
||||
instead of line breaks, but that is orthogonal.
|
||||
|
||||
Example: ``edl://f1.mkv,length=5,start=10;f2.mkv,30,20;f3.mkv``
|
||||
|
||||
As a quirks, mpv will accept arbitrary paths in EDLs originating from
|
||||
``edl://``, while ``.edl`` does not. This makes no sense.
|
||||
@@ -7,16 +7,11 @@ mpv provides access to its internals via the following means:
|
||||
- commands
|
||||
- properties
|
||||
- events
|
||||
- hooks
|
||||
|
||||
The sum of these mechanisms is sometimes called command interface.
|
||||
|
||||
All of these are important for interfacing both with end users and API users
|
||||
(which include Lua scripts, libmpv, and the JSON IPC). As such, they constitute
|
||||
a large part of the user interface and APIs.
|
||||
|
||||
Also see compatibility.rst.
|
||||
|
||||
This document lists changes to them. New changes are added to the top.
|
||||
|
||||
Interface changes
|
||||
@@ -24,87 +19,8 @@ Interface changes
|
||||
|
||||
::
|
||||
|
||||
--- mpv 0.30.0 ---
|
||||
- add `--d3d11-output-format` to enable explicit selection of a D3D11
|
||||
swap chain format.
|
||||
- rewrite DVB channel switching to use an integer value
|
||||
`--dvbin-channel-switch-offset` for switching instead of the old
|
||||
stream controls which are now gone. Cycling this property up or down will
|
||||
change the offset to the channel which was initially tuned to.
|
||||
Example for `input.conf`: `H cycle dvbin-channel-switch-offset up`,
|
||||
`K cycle dvbin-channel-switch-offset down`.
|
||||
- adapt `stream_dvb` to support writing to `dvbin-prog` at runtime
|
||||
and also to consistently use dvbin-configuration over URI parameters
|
||||
when provided
|
||||
- add `--d3d11-adapter` to enable explicit selection of a D3D11 rendering
|
||||
adapter by name.
|
||||
- rename `--drm-osd-plane-id` to `--drm-draw-plane`, `--drm-video-plane-id` to
|
||||
`--drm-drmprime-video-plane` and `--drm-osd-size` to `--drm-draw-surface-size`
|
||||
to better reflect what the options actually control, that the values they
|
||||
accept aren't actually internal DRM ID's (like with similar options in
|
||||
ffmpeg's KMS support), and that the video plane is only used when the drmprime
|
||||
overlay hwdec interop is active, with the video being drawn to the draw plane
|
||||
otherwise.
|
||||
- in addition to the above, the `--drm-draw-plane` and `--drm-drmprime-video-plane`
|
||||
options now accept either an integer index, or the values primary or overlay.
|
||||
`--drm-draw-plane` now defaults to primary and `--drm-drmprime-video-plane`
|
||||
defaults to overlay. This should be similar to previous behavior on most drivers
|
||||
due to how planes are usually sorted.
|
||||
- rename --opensles-frames-per-buffer to --opensles-frames-per-enqueue to
|
||||
better reflect its purpose. In the past it overrides the buffer size the AO
|
||||
requests (but not the default/value of the generic --audio-buffer option).
|
||||
Now it only guarantees that the soft buffer size will not be smaller than
|
||||
itself while setting the size per Enqueue.
|
||||
- add --opensles-buffer-size-in-ms, allowing user to tune the soft buffer size.
|
||||
It overrides the --audio-buffer option unless it's set to 0 (with the default
|
||||
being 250).
|
||||
- remove `--linear-scaling`, replaced by `--linear-upscaling` and
|
||||
`--linear-downscaling`. This means that `--sigmoid-upscaling` no longer
|
||||
implies linear light downscaling as well, which was confusing.
|
||||
- the built-in `gpu-hq` profile now includes` --linear-downscaling`.
|
||||
- support for `--spirv-compiler=nvidia` has been removed, leaving `shaderc`
|
||||
as the only option. The `--spirv-compiler` option itself has been marked
|
||||
as deprecated, and may be removed in the future.
|
||||
- split up `--tone-mapping-desaturate`` into strength + exponent, instead of
|
||||
only using a single value (which previously just controlled the exponent).
|
||||
The strength now linearly blends between the linear and nonlinear tone
|
||||
mapped versions of a color.
|
||||
- add --hdr-peak-decay-rate and --hdr-scene-threshold-low/high
|
||||
- add --tone-mapping-max-boost
|
||||
- ipc: require that "request_id" fields are integers. Other types are still
|
||||
accepted for compatibility, but this will stop in the future. Also, if no
|
||||
request_id is provided, 0 will be assumed.
|
||||
- mpv_command_node() and mp.command_native() now support named arguments
|
||||
(see manpage). If you want to use them, use a new version of the manpage
|
||||
as reference, which lists the definitive names.
|
||||
- edition and disc title switching will now fully reload playback (may have
|
||||
consequences for scripts, client API, or when using file-local options)
|
||||
- with the removal of the stream cache, the following properties and options were
|
||||
dropped: `cache`, `cache-size`, `cache-free`, `cache-used`, `--cache-default`,
|
||||
`--cache-initial`, `--cache-seek-min`, `--cache-backbuffer`, `--cache-file`,
|
||||
`--cache-file-size`
|
||||
- the --cache option does not take a number value anymore
|
||||
- remove async playback abort hack. This may make it impossible to abort
|
||||
playback if --demuxer-thread=no is forced.
|
||||
- remove `--macos-title-bar-style`, replaced by `--macos-title-bar-material`
|
||||
and `--macos-title-bar-appearance`.
|
||||
- The default for `--vulkan-async-compute` has changed to `yes` from `no`
|
||||
with the move to libplacebo as the back-end for vulkan rendering.
|
||||
- Remove "disc-titles", "disc-title", "disc-title-list", and "angle"
|
||||
properties. dvd:// does not support title ranges anymore.
|
||||
- Remove all "tv-..." options and properties, along with the classic Linux
|
||||
analog TV support.
|
||||
- remove "program" property (no replacement)
|
||||
- always prefer EGL over GLX, which helps with AMD/vaapi, but will break
|
||||
vdpau with --vo=gpu - use --gpu-context=x11 to be able to use vdpau. This
|
||||
does not affect --vo=vdpau or --hwdec=vdpau-copy.
|
||||
- remove deprecated --chapter option
|
||||
- deprecate --record-file
|
||||
- add `--demuxer-cue-codepage`
|
||||
- add ``track-list/N/demux-bitrate``, ``track-list/N/demux-rotation`` and
|
||||
``track-list/N/demux-par`` property
|
||||
- Deprecate ``--video-aspect`` and add ``--video-aspect-override`` to
|
||||
replace it. (The `video-aspect` option remains unchanged.)
|
||||
--- mpv 0.29.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,
|
||||
@@ -189,11 +105,10 @@ Interface changes
|
||||
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.
|
||||
- --[c]scale-[w]param[1|2] and --tone-mapping-param now accept "default",
|
||||
and if set to that value, reading them as property will also return
|
||||
"default", instead of float nan as in previous versions
|
||||
--- mpv 0.28.0 ---
|
||||
- rename --hwdec=mediacodec option to mediacodec-copy, to reflect
|
||||
conventions followed by other hardware video decoding APIs
|
||||
|
||||
@@ -24,6 +24,44 @@ See ``--vf`` group of options for info on how ``--af-defaults``, ``--af-add``,
|
||||
|
||||
Available filters are:
|
||||
|
||||
``lavrresample[=option1:option2:...]``
|
||||
This filter uses libavresample (or libswresample, depending on the build)
|
||||
to change sample rate, sample format, or channel layout of the audio stream.
|
||||
This filter is automatically enabled if the audio output does not support
|
||||
the audio configuration of the file being played.
|
||||
|
||||
.. 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>``
|
||||
Length of the filter with respect to the lower sampling rate. (default:
|
||||
16)
|
||||
``phase-shift=<count>``
|
||||
Log2 of the number of polyphase entries. (..., 10->1024, 11->2048,
|
||||
12->4096, ...) (default: 10->1024)
|
||||
``cutoff=<cutoff>``
|
||||
Cutoff frequency (0.0-1.0), default set depending upon filter length.
|
||||
``linear``
|
||||
If set then filters will be linearly interpolated between polyphase
|
||||
entries. (default: no)
|
||||
``no-detach``
|
||||
Do not detach if input and output audio format/rate/channels match.
|
||||
(If you just want to set defaults for this filter that will be used
|
||||
even by automatically inserted lavrresample instances, you should
|
||||
prefer setting them with the ``--audio-resample-...`` options.) This
|
||||
does not do anything anymore and the filter will never detach.
|
||||
``normalize=<yes|no|auto>``
|
||||
Whether to normalize when remixing channel layouts (default: auto).
|
||||
``auto`` uses the value set by ``--audio-normalize-downmix``.
|
||||
``o=<string>``
|
||||
Set AVOptions on the SwrContext or AVAudioResampleContext. These should
|
||||
be documented by FFmpeg or Libav.
|
||||
|
||||
``lavcac3enc[=options]``
|
||||
Encode multi-channel audio to AC-3 at runtime using libavcodec. Supports
|
||||
16-bit native-endian input format, maximum 6 channels. The output is
|
||||
|
||||
@@ -152,10 +152,6 @@ Available audio output drivers are:
|
||||
If you have stuttering video when using pulse, try to enable this
|
||||
option. (Or try to update PulseAudio.)
|
||||
|
||||
``--pulse-allow-suspended=<yes|no>``
|
||||
Allow mpv to use PulseAudio even if the sink is suspended (default: no).
|
||||
Can be useful if PulseAudio is running as a bridge to jack and mpv has its sink-input set to the one jack is using.
|
||||
|
||||
``sdl``
|
||||
SDL 1.2+ audio output driver. Should work on any platform supported by SDL
|
||||
1.2, but may require the ``SDL_AUDIODRIVER`` environment variable to be set
|
||||
|
||||
+185
-492
File diff suppressed because it is too large.
Load diff
+2
-40
@@ -74,12 +74,6 @@ some wrapper like .NET's NamedPipeClientStream.)
|
||||
Protocol
|
||||
--------
|
||||
|
||||
The protocol uses UTF-8-only JSON as defined by RFC-8259. Unlike standard JSON,
|
||||
"\u" escape sequences are not allowed to construct surrogate pairs. To avoid
|
||||
getting conflicts, encode all text characters including and above codepoint
|
||||
U+0020 as UTF-8. mpv might output broken UTF-8 in corner cases (see "UTF-8"
|
||||
section below).
|
||||
|
||||
Clients can execute commands on the player by sending JSON messages of the
|
||||
following form:
|
||||
|
||||
@@ -114,10 +108,7 @@ Because events can occur at any time, it may be difficult at times to determine
|
||||
which response goes with which command. Commands may optionally include a
|
||||
``request_id`` which, if provided in the command request, will be copied
|
||||
verbatim into the response. mpv does not intrepret the ``request_id`` in any
|
||||
way; it is solely for the use of the requester. The only requirement is that
|
||||
the ``request_id`` field must be an integer (a number without fractional parts
|
||||
in the range ``-2^63..2^63-1``). Using other types is deprecated and will
|
||||
currently show a warning. In the future, this will raise an error.
|
||||
way; it is solely for the use of the requester.
|
||||
|
||||
For example, this request:
|
||||
|
||||
@@ -131,11 +122,6 @@ Would generate this response:
|
||||
|
||||
{ "error": "success", "data": 1.468135, "request_id": 100 }
|
||||
|
||||
If you don't specify a ``request_id``, command replies will set it to 0.
|
||||
|
||||
Commands may run asynchronously in the future, instead of blocking the socket
|
||||
until a reply is sent.
|
||||
|
||||
All commands, replies, and events are separated from each other with a line
|
||||
break character (``\n``).
|
||||
|
||||
@@ -272,28 +258,4 @@ sometimes sends invalid JSON. If that is a problem for the client application's
|
||||
parser, it should filter the raw data for invalid UTF-8 sequences and perform
|
||||
the desired replacement, before feeding the data to its JSON parser.
|
||||
|
||||
mpv will not attempt to construct invalid UTF-8 with broken "\u" escape
|
||||
sequences. This includes surrogate pairs.
|
||||
|
||||
JSON extensions
|
||||
---------------
|
||||
|
||||
The following non-standard extensions are supported:
|
||||
|
||||
- a list or object item can have a trailing ","
|
||||
- object syntax accepts "=" in addition of ":"
|
||||
- object keys can be unquoted, if they start with a character in "A-Za-z\_"
|
||||
and contain only characters in "A-Za-z0-9\_"
|
||||
- byte escapes with "\xAB" are allowed (with AB being a 2 digit hex number)
|
||||
|
||||
Example:
|
||||
|
||||
::
|
||||
|
||||
{ objkey = "value\x0A" }
|
||||
|
||||
Is equivalent to:
|
||||
|
||||
::
|
||||
|
||||
{ "objkey": "value\n" }
|
||||
mpv will not attempt to construct invalid UTF-8 with broken escape sequences.
|
||||
@@ -88,7 +88,7 @@ Scripting APIs - identical to Lua
|
||||
|
||||
(LE) - Last-Error, indicates that ``mp.last_error()`` can be used after the
|
||||
call to test for success (empty string) or failure (non empty reason string).
|
||||
Where the Lua APIs use ``nil`` to indicate error, JS APIs use ``undefined``.
|
||||
Otherwise, where the Lua APIs return ``nil`` on error, JS returns ``undefined``.
|
||||
|
||||
``mp.command(string)`` (LE)
|
||||
|
||||
@@ -96,11 +96,6 @@ Where the Lua APIs use ``nil`` to indicate error, JS APIs use ``undefined``.
|
||||
|
||||
``mp.command_native(table [,def])`` (LE)
|
||||
|
||||
``id = mp.command_native_async(table [,fn])`` (LE) Notes: ``id`` is true-thy on
|
||||
success, ``fn`` is called always a-sync, ``error`` is empty string on success.
|
||||
|
||||
``mp.abort_async_command(id)``
|
||||
|
||||
``mp.get_property(name [,def])`` (LE)
|
||||
|
||||
``mp.get_property_osd(name [,def])`` (LE)
|
||||
|
||||
+39
-57
@@ -109,43 +109,12 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
``mp.command_native(table [,def])``
|
||||
Similar to ``mp.commandv``, but pass the argument list as table. This has
|
||||
the advantage that in at least some cases, arguments can be passed as
|
||||
native types. It also allows you to use named argument.
|
||||
|
||||
If the table is an array, each array item is like an argument in
|
||||
``mp.commandv()`` (but can be a native type instead of a string).
|
||||
|
||||
If the table contains string keys, it's interpreted as command with named
|
||||
arguments. This requires at least an entry with the key ``name`` to be
|
||||
present, which must be a string, and contains the command name. The special
|
||||
entry ``_flags`` is optional, and if present, must be an array of
|
||||
`Input Command Prefixes`_ to apply. All other entries are interpreted as
|
||||
arguments.
|
||||
native types.
|
||||
|
||||
Returns a result table on success (usually empty), or ``def, error`` on
|
||||
error. ``def`` is the second parameter provided to the function, and is
|
||||
nil if it's missing.
|
||||
|
||||
``mp.command_native_async(table [,fn])``
|
||||
Like ``mp.command_native()``, but the command is ran asynchronously (as far
|
||||
as possible), and upon completion, fn is called. fn has two arguments:
|
||||
``fn(success, result, error)``. ``success`` is always a Boolean and is true
|
||||
if the command was successful, otherwise false. The second parameter is
|
||||
the result value (can be nil) in case of success, nil otherwise (as returned
|
||||
by ``mp.command_native()``). The third parameter is the error string in case
|
||||
of an error, nil otherwise.
|
||||
|
||||
Returns a table with undefined contents, which can be used as argument for
|
||||
``mp.abort_async_command``.
|
||||
|
||||
If starting the command failed for some reason, ``nil, error`` is returned,
|
||||
and ``fn`` is called indicating failure, using the same error value.
|
||||
|
||||
``mp.abort_async_command(t)``
|
||||
Abort a ``mp.command_native_async`` call. The argument is the return value
|
||||
of that command (which starts asynchronous execution of the command).
|
||||
Whether this works and how long it takes depends on the command and the
|
||||
situation. The abort call itself is asynchronous. Does not return anything.
|
||||
|
||||
``mp.get_property(name [,def])``
|
||||
Return the value of the given property as string. These are the same
|
||||
properties as used in input.conf. See `Properties`_ for a list of
|
||||
@@ -344,16 +313,12 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
exact behavior depends on timing and other things.)
|
||||
|
||||
In some cases the function is not called even if the property changes.
|
||||
This depends on the property, and it's a valid feature request to ask for
|
||||
better update handling of a specific property.
|
||||
Whether this can happen depends on the property.
|
||||
|
||||
If the ``type`` is ``none`` or ``nil``, sporadic property change events are
|
||||
possible. This means the change function ``fn`` can be called even if the
|
||||
property doesn't actually change.
|
||||
|
||||
You always get an initial change notification. This is meant to initialize
|
||||
the user's state to the current value of the property.
|
||||
|
||||
``mp.unobserve_property(fn)``
|
||||
Undo ``mp.observe_property(..., fn)``. This removes all property handlers
|
||||
that are equal to the ``fn`` parameter. This uses normal Lua ``==``
|
||||
@@ -408,7 +373,7 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
(``true``). This value is used when the timer expires (but before
|
||||
the timer callback function fn is run).
|
||||
|
||||
Note that these are methods, and you have to call them using ``:`` instead
|
||||
Note that these are method, and you have to call them using ``:`` instead
|
||||
of ``.`` (Refer to http://www.lua.org/manual/5.2/manual.html#3.4.9 .)
|
||||
|
||||
Example:
|
||||
@@ -669,25 +634,45 @@ strictly part of the guaranteed API.
|
||||
|
||||
``utils.subprocess(t)``
|
||||
Runs an external process and waits until it exits. Returns process status
|
||||
and the captured output. This is a legacy wrapper around calling the
|
||||
``subprocess`` command with ``mp.command_native``. It does the following
|
||||
things:
|
||||
and the captured output.
|
||||
|
||||
- copy the table ``t``
|
||||
- rename ``cancellable`` field to ``playback_only``
|
||||
- rename ``max_size`` to ``capture_size``
|
||||
- set ``capture_stdout`` field to ``true`` if unset
|
||||
- set ``name`` field to ``subprocess``
|
||||
- call ``mp.command_native(copied_t)``
|
||||
- if the command failed, create a dummy result table
|
||||
- copy ``error_string`` to ``error`` field if the string is non-empty
|
||||
- return the result table
|
||||
The parameter ``t`` is a table. The function reads the following entries:
|
||||
|
||||
It is recommended to use ``mp.command_native`` or ``mp.command_native_async``
|
||||
directly, instead of calling this legacy wrapper. It is for compatibility
|
||||
only.
|
||||
``args``
|
||||
Array of strings. The first array entry is the executable. This
|
||||
can be either an absolute path, or a filename with no path
|
||||
components, in which case the ``PATH`` environment variable is
|
||||
used to resolve the executable. The other array elements are
|
||||
passed as command line arguments.
|
||||
|
||||
See the ``subprocess`` documentation for semantics and further parameters.
|
||||
``cancellable``
|
||||
Optional. If set to ``true`` (default), then if the user stops
|
||||
playback or goes to the next file while the process is running,
|
||||
the process will be killed.
|
||||
|
||||
``max_size``
|
||||
Optional. The maximum size in bytes of the data that can be captured
|
||||
from stdout. (Default: 16 MB.)
|
||||
|
||||
The function returns a table as result with the following entries:
|
||||
|
||||
``status``
|
||||
The raw exit status of the process. It will be negative on error.
|
||||
|
||||
``stdout``
|
||||
Captured output stream as string, limited to ``max_size``.
|
||||
|
||||
``error``
|
||||
``nil`` on success. The string ``killed`` if the process was
|
||||
terminated in an unusual way. The string ``init`` if the process
|
||||
could not be started.
|
||||
|
||||
On Windows, ``killed`` is only returned when the process has been
|
||||
killed by mpv as a result of ``cancellable`` being set to ``true``.
|
||||
|
||||
``killed_by_us``
|
||||
Set to ``true`` if the process has been killed by mpv as a result
|
||||
of ``cancellable`` being set to ``true``.
|
||||
|
||||
``utils.subprocess_detached(t)``
|
||||
Runs an external process and detaches it from mpv's control.
|
||||
@@ -700,9 +685,6 @@ strictly part of the guaranteed API.
|
||||
|
||||
The function returns ``nil``.
|
||||
|
||||
This is a legacy wrapper around calling the ``run`` command with
|
||||
``mp.commandv`` and other functions.
|
||||
|
||||
``utils.getpid()``
|
||||
Returns the process ID of the running mpv process. This can be used to identify
|
||||
the calling mpv when launching (detached) subprocesses.
|
||||
|
||||
+23
-10
@@ -218,11 +218,6 @@ F9
|
||||
Show the list of audio and subtitle streams (useful only if a UI window is
|
||||
used, broken on the terminal).
|
||||
|
||||
i and I
|
||||
Show/toggle an overlay displaying statistics about the currently playing
|
||||
file such as codec, framerate, number of dropped frames and so on. See
|
||||
`STATS`_ for more information.
|
||||
|
||||
(The following keys are valid only when using a video output that supports the
|
||||
corresponding adjustment.)
|
||||
|
||||
@@ -510,6 +505,23 @@ specifying the path to the local copy: ``--dvd-device=PATH``.
|
||||
Alternatively, running ``mpv PATH`` should auto-detect a DVD directory
|
||||
tree and play the longest title.
|
||||
|
||||
.. note:: DVD library choices
|
||||
|
||||
mpv uses a different default DVD library than MPlayer. MPlayer
|
||||
uses libdvdread by default, and mpv uses libdvdnav by default.
|
||||
Both libraries are developed in parallel, but libdvdnav is
|
||||
intended to support more sophisticated DVD features such as menus
|
||||
and multi-angle playback. mpv uses libdvdnav for files specified
|
||||
as either ``dvd://...`` or ``dvdnav://...``. To use libdvdread,
|
||||
which will produce behavior more like MPlayer, specify
|
||||
``dvdread://...`` instead. Some users have experienced problems
|
||||
when using libdvdnav, in which playback gets stuck in a DVD menu
|
||||
stream. These problems are reported to go away when auto-selecting
|
||||
the title (``dvd://`` rather than ``dvd://1``) or when using
|
||||
libdvdread (e.g. ``dvdread://0``). There are also outstanding bugs
|
||||
in libdvdnav with seeking backwards and forwards in a video
|
||||
stream. Specify ``dvdread://...`` to fix such problems.
|
||||
|
||||
.. note:: DVD subtitles
|
||||
|
||||
DVDs use image-based subtitles. Image subtitles are implemented as
|
||||
@@ -613,8 +625,7 @@ or at runtime with the ``apply-profile <name>`` command.
|
||||
|
||||
# a profile that can be enabled with --profile=big-cache
|
||||
[big-cache]
|
||||
cache=yes
|
||||
demuxer-max-bytes=123400KiB
|
||||
cache=123400
|
||||
demuxer-readahead-secs=20
|
||||
|
||||
[slow]
|
||||
@@ -719,10 +730,12 @@ listed.
|
||||
to the display as well, e.g.: ``Dropped: 4/34``. This happens only if
|
||||
decoder frame dropping is enabled with the ``--framedrop`` options.
|
||||
(``drop-frame-count`` property.)
|
||||
- Cache state, e.g. ``Cache: 2s/134KB``. Visible if the stream cache is enabled.
|
||||
- Cache state, e.g. ``Cache: 2s+134KB``. Visible if the stream cache is enabled.
|
||||
The first value shows the amount of video buffered in the demuxer in seconds,
|
||||
the second value shows the estimated size of the buffered amount in kilobytes.
|
||||
(``demuxer-cache-duration`` and ``demuxer-cache-state`` properties.)
|
||||
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
|
||||
|
||||
+437
-852
File diff suppressed because it is too large.
Load diff
+8
-50
@@ -167,14 +167,9 @@ Configurable Options
|
||||
``seekbarstyle``
|
||||
Default: bar
|
||||
|
||||
Sets the style of the playback position marker and overall shape
|
||||
of the seekbar: ``bar``, ``diamond`` or ``knob``.
|
||||
|
||||
``seekbarhandlesize``
|
||||
Default: 0.6
|
||||
|
||||
Size ratio of the seek handle if ``seekbarstyle`` is set to ``dimaond``
|
||||
or ``knob``. This is relative to the full height of the seekbar.
|
||||
Sets the style of the seekbar, slider (diamond marker), knob (circle
|
||||
marker with guide), or bar (fill).
|
||||
Default pre-0.21.0 was 'slider'.
|
||||
|
||||
``seekbarkeyframes``
|
||||
Default: yes
|
||||
@@ -185,28 +180,6 @@ Configurable Options
|
||||
useful in cases where keyframes cannot be found. Note that using exact
|
||||
seeks can potentially make mouse dragging much slower.
|
||||
|
||||
``seekrangestyle``
|
||||
Default: inverted
|
||||
|
||||
Display seekable ranges on the seekbar. ``bar`` shows them on the full
|
||||
height of the bar, ``line`` as a thick line and ``inverted`` as a thin
|
||||
line that is inverted over playback position markers. ``none`` will hide
|
||||
them. Additionally, ``slider`` will show a permanent handle inside the seekbar
|
||||
with cached ranges marked inside. Note that these will look differently
|
||||
based on the seekbarstyle option. Also, ``slider`` does not work with
|
||||
``seekbarstyle`` set to ``bar``.
|
||||
|
||||
``seekrangeseparate``
|
||||
Default: yes
|
||||
|
||||
Controls whether to show line-style seekable ranges on top of the
|
||||
seekbar or separately if ``seekbarstyle`` is set to ``bar``.
|
||||
|
||||
``seekrangealpha``
|
||||
Default: 200
|
||||
|
||||
Alpha of the seekable ranges, 0 (opaque) to 255 (fully transparent).
|
||||
|
||||
``deadzonesize``
|
||||
Default: 0.5
|
||||
|
||||
@@ -308,6 +281,11 @@ Configurable Options
|
||||
|
||||
Display timecodes with milliseconds
|
||||
|
||||
``seekranges``
|
||||
Default: yes
|
||||
|
||||
Display seekable ranges on the seekbar
|
||||
|
||||
``visibility``
|
||||
Default: auto (auto hide/show on mouse move)
|
||||
|
||||
@@ -321,26 +299,6 @@ Configurable Options
|
||||
default is conservative to allow wide fonts to be used without overflow.
|
||||
However, with many common fonts a bigger number can be used. YMMV.
|
||||
|
||||
``boxvideo``
|
||||
Default: no
|
||||
|
||||
Whether to overlay the osc over the video (``no``), or to box the video
|
||||
within the areas not covered by the osc (``yes``). If this option is set,
|
||||
the osc may overwrite the ``--video-margin-ratio-*`` options, even if the
|
||||
user has set them. (It will not overwrite them if all of them are set to
|
||||
default values.)
|
||||
|
||||
Currently, this is supported for the ``bottombar`` layout only. The other
|
||||
layouts do not change if this option is set.
|
||||
|
||||
The border is static and appears even if the OSC is configured to appear
|
||||
only on mouse interaction. If the OSC is invisible, the border is simply
|
||||
filled with the background color (black by default).
|
||||
|
||||
This currently still makes the OSC overlap with subtitles (if the
|
||||
``--sub-use-margins`` option is set to ``yes``, the default). This may be
|
||||
fixed later.
|
||||
|
||||
Script Commands
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
+70
-211
@@ -112,57 +112,15 @@ With filters that support it, you can access parameters by their name.
|
||||
Available mpv-only filters are:
|
||||
|
||||
``format=fmt=<value>:colormatrix=<value>:...``
|
||||
Applies video parameter overrides, with optional conversion. By default,
|
||||
this overrides the video's parameters without conversion (except for the
|
||||
``fmt`` parameter), but can be made to perform an appropriate conversion
|
||||
with ``convert=yes`` for parameters for which conversion is supported.
|
||||
Restricts the color space for the next filter without doing any conversion.
|
||||
Use together with the scale filter for a real conversion.
|
||||
|
||||
.. note::
|
||||
|
||||
For a list of available formats, see ``format=fmt=help``.
|
||||
|
||||
``<fmt>``
|
||||
Image format name, e.g. rgb15, bgr24, 420p, etc. (default: don't change).
|
||||
|
||||
This filter always performs conversion to the given format.
|
||||
|
||||
.. note::
|
||||
|
||||
For a list of available formats, use ``--vf=format=fmt=help``.
|
||||
|
||||
``<convert=yes|no>``
|
||||
Force conversion of color parameters (default: no).
|
||||
|
||||
If this is disabled (the default), the only conversion that is possibly
|
||||
performed is format conversion if ``<fmt>`` is set. All other parameters
|
||||
(like ``<colormatrix>``) are forced without conversion. This mode is
|
||||
typically useful when files have been incorrectly tagged.
|
||||
|
||||
If this is enabled, libswscale or zimg is used if any of the parameters
|
||||
mismatch. zimg is used of the input/output image formats are supported
|
||||
by mpv's zimg wrapper, and if ``--sws-allow-zimg=yes`` is used. Both
|
||||
libraries may not support all kinds of conversions. This typically
|
||||
results in silent incorrect conversion. zimg has in many cases a better
|
||||
chance of performing the conversion correctly.
|
||||
|
||||
In both cases, the color parameters are set on the output stage of the
|
||||
image format conversion (if ``fmt`` was set). The difference is that
|
||||
with ``convert=no``, the color parameters are not passed on to the
|
||||
converter.
|
||||
|
||||
If input and output video parameters are the same, conversion is always
|
||||
skipped.
|
||||
|
||||
.. admonition:: Examples
|
||||
|
||||
``mpv test.mkv --vf=format:colormatrix=ycgco``
|
||||
Results in incorrect colors (if test.mkv was tagged correctly).
|
||||
|
||||
``mpv test.mkv --vf=format:colormatrix=ycgco:convert=yes --sws-allow-zimg``
|
||||
Results in true conversion to ``ycgco``, assuming the renderer
|
||||
supports it (``--vo=gpu`` normally does). You can add ``--vo=xv``
|
||||
to force a VO which definitely does not support it, which should
|
||||
show incorrect colors as confirmation.
|
||||
|
||||
Using ``--sws-allow-zimg=no`` (or disabling zimg at build time)
|
||||
will use libswscale, which cannot perform this conversion as
|
||||
of this writing.
|
||||
Format name, e.g. rgb15, bgr24, 420p, etc. (default: don't change).
|
||||
|
||||
``<colormatrix>``
|
||||
Controls the YUV to RGB color space conversion when playing video. There
|
||||
@@ -258,10 +216,7 @@ Available mpv-only filters are:
|
||||
:srgb: IEC 61966-2-4 (sRGB)
|
||||
:linear: Linear light
|
||||
:gamma1.8: Pure power curve (gamma 1.8)
|
||||
:gamma2.0: Pure power curve (gamma 2.0)
|
||||
:gamma2.2: Pure power curve (gamma 2.2)
|
||||
:gamma2.4: Pure power curve (gamma 2.4)
|
||||
:gamma2.6: Pure power curve (gamma 2.6)
|
||||
:gamma2.8: Pure power curve (gamma 2.8)
|
||||
:prophoto: ProPhoto RGB (ROMM) curve
|
||||
:pq: ITU-R BT.2100 PQ (Perceptual quantizer) curve
|
||||
@@ -294,13 +249,12 @@ Available mpv-only filters are:
|
||||
:gamma1.2: Scene-referred using a pure power OOTF (gamma=1.2)
|
||||
|
||||
``<stereo-in>``
|
||||
Set the stereo mode the video is assumed to be encoded in. Use
|
||||
``--vf format:stereo-in=help`` to list all available modes. Check with
|
||||
the ``stereo3d`` filter documentation to see what the names mean.
|
||||
Set the stereo mode the video is assumed to be encoded in. Takes the
|
||||
same values as the ``--video-stereo-mode`` option.
|
||||
|
||||
``<stereo-out>``
|
||||
Set the stereo mode the video should be displayed as. Takes the
|
||||
same values as the ``stereo-in`` option.
|
||||
same values as the ``--video-stereo-mode`` option.
|
||||
|
||||
``<rotate>``
|
||||
Set the rotation the video is assumed to be encoded with in degrees.
|
||||
@@ -316,6 +270,17 @@ Available mpv-only filters are:
|
||||
but values such as ``[16:9]`` can be passed too (``[...]`` for quoting
|
||||
to prevent the option parser from interpreting the ``:`` character).
|
||||
|
||||
``<spherical-type>``
|
||||
Type of the spherical projection:
|
||||
|
||||
:auto: As indicated by the file (default)
|
||||
:none: Normal video
|
||||
:equirect: Equirectangular
|
||||
:unknown: Unknown projection
|
||||
|
||||
``<spherical-yaw>``, ``<spherical-pitch>``, ``<spherical-roll>``
|
||||
Reference angle in degree, if spherical video is used.
|
||||
|
||||
``lavfi=graph[:sws-flags[:o=opts]]``
|
||||
Filter video using FFmpeg's libavfilter.
|
||||
|
||||
@@ -395,31 +360,22 @@ Available mpv-only filters are:
|
||||
of that will return errors. As such, you can't use the full power of
|
||||
VapourSynth, but you can use certain filters.
|
||||
|
||||
.. warning::
|
||||
|
||||
Do not use this filter, unless you have expert knowledge in VapourSynth,
|
||||
and know how to fix bugs in the mpv VapourSynth wrapper code.
|
||||
|
||||
If you just want to play video generated by VapourSynth (i.e. using
|
||||
If you just want to play video generated by a VapourSynth (i.e. using
|
||||
a native VapourSynth video source), it's better to use ``vspipe`` and a
|
||||
pipe or FIFO to feed the video to mpv. The same applies if the filter script
|
||||
FIFO to feed the video to mpv. The same applies if the filter script
|
||||
requires random frame access (see ``buffered-frames`` parameter).
|
||||
|
||||
This filter is experimental. If it turns out that it works well and is
|
||||
used, it will be ported to libavfilter. Otherwise, it will be just removed.
|
||||
|
||||
``file``
|
||||
Filename of the script source. Currently, this is always a python
|
||||
script (``.vpy`` in VapourSynth convention).
|
||||
|
||||
The variable ``video_in`` is set to the mpv video source, and it is
|
||||
expected that the script reads video from it. (Otherwise, mpv will
|
||||
decode no video, and the video packet queue will overflow, eventually
|
||||
leading to only audio playing, or worse.)
|
||||
|
||||
The filter graph created by the script is also expected to pass through
|
||||
timestamps using the ``_DurationNum`` and ``_DurationDen`` frame
|
||||
properties.
|
||||
|
||||
See the end of the option list for a full list of script variables
|
||||
defined by mpv.
|
||||
script. The variable ``video_in`` is set to the mpv video source,
|
||||
and it is expected that the script reads video from it. (Otherwise,
|
||||
mpv will decode no video, and the video packet queue will overflow,
|
||||
eventually leading to audio being stopped.) The script is also
|
||||
expected to pass through timestamps using the ``_DurationNum`` and
|
||||
``_DurationDen`` frame properties.
|
||||
|
||||
.. admonition:: Example:
|
||||
|
||||
@@ -437,61 +393,21 @@ Available mpv-only filters are:
|
||||
``buffered-frames``
|
||||
Maximum number of decoded video frames that should be buffered before
|
||||
the filter (default: 4). This specifies the maximum number of frames
|
||||
the script can request in backward direction.
|
||||
|
||||
the script can request in reverse direction.
|
||||
E.g. if ``buffered-frames=5``, and the script just requested frame 15,
|
||||
it can still request frame 10, but frame 9 is not available anymore.
|
||||
If it requests frame 30, mpv will decode 15 more frames, and keep only
|
||||
frames 25-30.
|
||||
|
||||
The only reason why this buffer exists is to serve the random access
|
||||
requests the VapourSynth filter can make.
|
||||
|
||||
The VapourSynth API has a ``getFrameAsync`` function, which takes an
|
||||
absolute frame number. Source filters must respond to all requests. For
|
||||
example, a source filter can request frame 2432, and then frame 3.
|
||||
Source filters typically implement this by pre-indexing the entire
|
||||
file.
|
||||
|
||||
mpv on the other hand is stream oriented, and does not allow filters to
|
||||
seek. (And it would not make sense to allow it, because it would ruin
|
||||
performance.) Filters get frames sequentially in playback direction, and
|
||||
cannot request them out of order.
|
||||
|
||||
To compensate for this mismatch, mpv allows the filter to access frames
|
||||
within a certain window. ``buffered-frames`` controls the size of this
|
||||
window. Most VapourSynth filters happen to work with this, because mpv
|
||||
requests frames sequentially increasing from it, and most filters only
|
||||
require frames "close" to the requested frame.
|
||||
|
||||
If the filter requests a frame that has a higher frame number than the
|
||||
highest buffered frame, new frames will be decoded until the requested
|
||||
frame number is reached. Excessive frames will be flushed out in a FIFO
|
||||
manner (there are only at most ``buffered-frames`` in this buffer).
|
||||
|
||||
If the filter requests a frame that has a lower frame number than the
|
||||
lowest buffered frame, the request cannot be satisfied, and an error
|
||||
is returned to the filter. This kind of error is not supposed to happen
|
||||
in a "proper" VapourSynth environment. What exactly happens depends on
|
||||
the filters involved.
|
||||
|
||||
Increasing this buffer will not improve performance. Rather, it will
|
||||
waste memory, and slow down seeks (when enough frames to fill the buffer
|
||||
need to be decoded at once). It is only needed to prevent the error
|
||||
described in the previous paragraph.
|
||||
|
||||
How many frames a filter requires depends on filter implementation
|
||||
details, and mpv has no way of knowing. A scale filter might need only
|
||||
1 frame, an interpolation filter may require a small number of frames,
|
||||
and the ``Reverse`` filter will require an infinite number of frames.
|
||||
|
||||
If you want reliable operation to the full extend VapourSynth is
|
||||
capable, use ``vspipe``.
|
||||
|
||||
The actual number of buffered frames also depends on the value of the
|
||||
``concurrent-frames`` option. Currently, both option values are
|
||||
multiplied to get the final buffer size.
|
||||
|
||||
(Normally, VapourSynth source filters must provide random access, but
|
||||
mpv was made for playback, and does not provide frame-exact random
|
||||
access. The way this video filter works is a compromise to make simple
|
||||
filters work anyway.)
|
||||
|
||||
``concurrent-frames``
|
||||
Number of frames that should be requested in parallel. The
|
||||
level of concurrency depends on the filter and how quickly mpv can
|
||||
@@ -500,25 +416,15 @@ Available mpv-only filters are:
|
||||
making it higher than the number of cores can actually make it
|
||||
slower.
|
||||
|
||||
Technically, mpv will call the VapourSynth ``getFrameAsync`` function
|
||||
in a loop, until there are ``concurrent-frames`` frames that have not
|
||||
been returned by the filter yet. This also assumes that the rest of the
|
||||
mpv filter chain reads the output of the ``vapoursynth`` filter quickly
|
||||
enough. (For example, if you pause the player, filtering will stop very
|
||||
soon, because the filtered frames are waiting in a queue.)
|
||||
|
||||
Actual concurrency depends on many other factors.
|
||||
|
||||
By default, this uses the special value ``auto``, which sets the option
|
||||
to the number of detected logical CPU cores.
|
||||
|
||||
The following ``.vpy`` script variables are defined by mpv:
|
||||
The following variables are defined by mpv:
|
||||
|
||||
``video_in``
|
||||
The mpv video source as vapoursynth clip. Note that this has an
|
||||
incorrect (very high) length set, which confuses many filters. This is
|
||||
necessary, because the true number of frames is unknown. You can use the
|
||||
``Trim`` filter on the clip to reduce the length.
|
||||
The mpv video source as vapoursynth clip. Note that this has no length
|
||||
set, which confuses many filters. Using ``Trim`` on the clip with a
|
||||
high dummy length can turn it into a finite clip.
|
||||
|
||||
``video_in_dw``, ``video_in_dh``
|
||||
Display size of the video. Can be different from video size if the
|
||||
@@ -528,7 +434,7 @@ Available mpv-only filters are:
|
||||
FPS value as reported by file headers. This value can be wrong or
|
||||
completely broken (e.g. 0 or NaN). Even if the value is correct,
|
||||
if another filter changes the real FPS (by dropping or inserting
|
||||
frames), the value of this variable will not be useful. Note that
|
||||
frames), the value of this variable might not be useful. Note that
|
||||
the ``--fps`` command line option overrides this value.
|
||||
|
||||
Useful for some filters which insist on having a FPS.
|
||||
@@ -536,10 +442,34 @@ Available mpv-only filters are:
|
||||
``display_fps``
|
||||
Refresh rate of the current display. Note that this value can be 0.
|
||||
|
||||
``vapoursynth-lazy``
|
||||
The same as ``vapoursynth``, but doesn't load Python scripts. Instead, a
|
||||
custom backend using Lua and the raw VapourSynth API is used. The syntax
|
||||
is completely different, and absolutely no convenience features are
|
||||
provided. There's no type checking either, and you can trigger crashes.
|
||||
|
||||
.. admonition:: Example:
|
||||
|
||||
::
|
||||
|
||||
video_out = invoke("morpho", "Open", {clip = video_in})
|
||||
|
||||
The special variable ``video_in`` is the mpv video source, while the
|
||||
special variable ``video_out`` is used to read video from. The 1st argument
|
||||
is the plugin (queried with ``getPluginByNs``), the 2nd is the filter name,
|
||||
and the 3rd argument is a table with the arguments. Positional arguments
|
||||
are not supported. The types must match exactly. Since Lua is terrible and
|
||||
can't distinguish integers and floats, integer arguments must be prefixed
|
||||
with ``i_``, in which case the prefix is removed and the argument is cast
|
||||
to an integer. Should the argument's name start with ``i_``, you're out of
|
||||
luck.
|
||||
|
||||
Clips (VSNodeRef) are passed as light userdata, so trying to pass any
|
||||
other userdata type will result in hard crashes.
|
||||
|
||||
``vavpp``
|
||||
VA-API video post processing. Requires the system to support VA-API,
|
||||
i.e. Linux/BSD only. Works with ``--vo=vaapi`` and ``--vo=gpu`` only.
|
||||
Currently deinterlaces. This filter is automatically inserted if
|
||||
VA-AP-API video post processing. Works with ``--vo=vaapi`` and ``--vo=gpu``
|
||||
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).
|
||||
|
||||
@@ -639,74 +569,3 @@ Available mpv-only filters are:
|
||||
which algorithm is actually selected. ``none`` always falls back. On
|
||||
most if not all hardware, this option will probably do nothing, because
|
||||
a video processor usually supports all modes or none.
|
||||
|
||||
``fingerprint=...``
|
||||
Compute video frame fingerprints and provide them as metadata. Actually, it
|
||||
currently barely deserved to be called ``fingerprint``, because it does not
|
||||
compute "proper" fingerprints, only tiny downscaled images (but which can be
|
||||
used to compute image hashes or for similarity matching).
|
||||
|
||||
The main purpose of this filter is to support the ``skip-logo.lua`` script.
|
||||
If this script is dropped, or mpv ever gains a way to load user-defined
|
||||
filters (other than VapourSynth), this filter will be removed. Due to the
|
||||
"special" nature of this filter, it will be removed without warning.
|
||||
|
||||
The intended way to read from the filter is using ``vf-metadata`` (also
|
||||
see ``clear-on-query`` filter parameter). The property will return a list
|
||||
of key/value pairs as follows:
|
||||
|
||||
::
|
||||
|
||||
fp0.pts = 1.2345
|
||||
fp0.hex = 1234abcdef...bcde
|
||||
fp1.pts = 1.4567
|
||||
fp1.hex = abcdef1234...6789
|
||||
...
|
||||
fpN.pts = ...
|
||||
fpN.hex = ...
|
||||
type = gray-hex-16x16
|
||||
|
||||
Each ``fp<N>`` entry is for a frame. The ``pts`` entry specifies the
|
||||
timestamp of the frame (within the filter chain; in simple cases this is
|
||||
the same as the display timestamp). The ``hex`` field is the hex encoded
|
||||
fingerprint, whose size and meaning depend on the ``type`` filter option.
|
||||
The ``type`` field has the same value as the option the filter was created
|
||||
with.
|
||||
|
||||
This returns the frames that were filtered since the last query of the
|
||||
property. If ``clear-on-query=no`` was set, a query doesn't reset the list
|
||||
of frames. In both cases, a maximum of 10 frames is returned. If there are
|
||||
more frames, the oldest frames are discarded. Frames are returned in filter
|
||||
order.
|
||||
|
||||
(This doesn't return a structured list for the per-frame details because the
|
||||
internals of the ``vf-metadata`` mechanism suck. The returned format may
|
||||
change in the future.)
|
||||
|
||||
This filter uses zimg for speed and profit. However, it will fallback to
|
||||
libswscale in a number of situations: lesser pixel formats, unaligned data
|
||||
pointers or strides, or if zimg fails to initialize for unknown reasons. In
|
||||
these cases, the filter will use more CPU. Also, it will output different
|
||||
fingerprints, because libswscale cannot perform the full range expansion we
|
||||
normally request from zimg. As a consequence, the filter may be slower and
|
||||
not work correctly in random situations.
|
||||
|
||||
``type=...``
|
||||
What fingerprint to compute. Available types are:
|
||||
|
||||
:gray-hex-8x8: grayscale, 8 bit, 8x8 size
|
||||
:gray-hex-16x16: grayscale, 8 bit, 16x16 size (default)
|
||||
|
||||
Both types simply remove all colors, downscale the image, concatenate
|
||||
all pixel values to a byte array, and convert the array to a hex string.
|
||||
|
||||
``clear-on-query=yes|no``
|
||||
Clear the list of frame fingerprints if the ``vf-metadata`` property for
|
||||
this filter is queried (default: yes). This requires some care by the
|
||||
user. Some types of accesses might query the filter multiple times,
|
||||
which leads to lost frames.
|
||||
|
||||
``print=yes|no``
|
||||
Print computed fingerprints the the terminal (default: no). This is
|
||||
mostly for testing and such. Scripts should use ``vf-metadata`` to
|
||||
read information from this filter instead.
|
||||
+27
-78
@@ -308,7 +308,7 @@ Available video output drivers are:
|
||||
For tuning, refer to your copy of the file ``SDL_hints.h``.
|
||||
|
||||
.. note:: This driver is for compatibility with systems that don't provide
|
||||
proper graphics drivers.
|
||||
proper graphics drivers, or which support GLES only.
|
||||
|
||||
The following global options are supported by this video output:
|
||||
|
||||
@@ -415,8 +415,6 @@ Available video output drivers are:
|
||||
JPEG files, extension .jpeg.
|
||||
png
|
||||
PNG files.
|
||||
webp
|
||||
WebP files.
|
||||
|
||||
``--vo-image-png-compression=<0-9>``
|
||||
PNG compression factor (speed vs. file size tradeoff) (default: 7)
|
||||
@@ -427,12 +425,6 @@ Available video output drivers are:
|
||||
JPEG quality factor (default: 90)
|
||||
``--vo-image-jpeg-optimize=<0-100>``
|
||||
JPEG optimization factor (default: 100)
|
||||
``--vo-image-webp-lossless=<yes|no>``
|
||||
Enable writing lossless WebP files (default: no)
|
||||
``--vo-image-webp-quality=<0-100>``
|
||||
WebP quality (default: 75)
|
||||
``--vo-image-webp-compression=<0-6>``
|
||||
WebP compression factor (default: 4)
|
||||
``--vo-image-outdir=<dirname>``
|
||||
Specify the directory to save the image files to (default: ``./``).
|
||||
|
||||
@@ -487,59 +479,28 @@ Available video output drivers are:
|
||||
``--drm-connector=[<gpu_number>.]<name>``
|
||||
Select the connector to use (usually this is a monitor.) If ``<name>``
|
||||
is empty or ``auto``, mpv renders the output on the first available
|
||||
connector. Use ``--drm-connector=help`` to get a list of available
|
||||
connector. Use ``--drm-connector=help`` to get list of available
|
||||
connectors. When using multiple graphic cards, use the ``<gpu_number>``
|
||||
argument to disambiguate.
|
||||
(default: empty)
|
||||
|
||||
``--drm-mode=<preferred|highest|N|WxH[@R]>``
|
||||
Mode to use (resolution and frame rate).
|
||||
Possible values:
|
||||
``--drm-mode=<number>``
|
||||
Mode ID to use (resolution and frame rate).
|
||||
(default: 0)
|
||||
|
||||
:preferred: Use the preferred mode for the screen on the selected
|
||||
connector. (default)
|
||||
:highest: Use the mode with the highest resolution available on the
|
||||
selected connector.
|
||||
:N: Select mode by index.
|
||||
:WxH[@R]: Specify mode by width, height, and optionally refresh rate.
|
||||
In case several modes match, selects the mode that comes
|
||||
first in the EDID list of modes.
|
||||
``--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)
|
||||
|
||||
Use ``--drm-mode=help`` to get a list of available modes for all active
|
||||
connectors.
|
||||
|
||||
``--drm-atomic=<no|auto>``
|
||||
Toggle use of atomic modesetting. Mostly useful for debugging.
|
||||
|
||||
:no: Use legacy modesetting.
|
||||
:auto: Use atomic modesetting, falling back to legacy modesetting if
|
||||
not available. (default)
|
||||
|
||||
Note: Only affects ``gpu-context=drm``. ``vo=drm`` supports legacy
|
||||
modesetting only.
|
||||
|
||||
``--drm-draw-plane=<primary|overlay|N>``
|
||||
Select the DRM plane to which video and OSD is drawn to, under normal
|
||||
circumstances. The plane can be specified as ``primary``, which will
|
||||
pick the first applicable primary plane; ``overlay``, which will pick
|
||||
the first applicable overlay plane; or by index. The index is zero
|
||||
based, and related to the CRTC.
|
||||
(default: primary)
|
||||
|
||||
When using this option with the drmprime-drm hwdec interop, only the OSD
|
||||
is rendered to this plane.
|
||||
|
||||
``--drm-drmprime-video-plane=<primary|overlay|N>``
|
||||
Select the DRM plane to use for video with the drmprime-drm hwdec
|
||||
interop (used by e.g. the rkmpp hwdec on RockChip SoCs, and v4l2 hwdec:s
|
||||
on various other SoC:s). The plane is unused otherwise. This option
|
||||
accepts the same values as ``--drm-draw-plane``. (default: overlay)
|
||||
|
||||
To be able to successfully play 4K video on various SoCs you might need
|
||||
to set ``--drm-draw-plane=overlay --drm-drmprime-video-plane=primary``
|
||||
and setting ``--drm-draw-surface-size=1920x1080``, to render the OSD at a
|
||||
lower resolution (the video when handled by the hwdec will be on the
|
||||
drmprime-video plane and at full 4K resolution)
|
||||
``--drm-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
|
||||
@@ -548,23 +509,17 @@ Available video output drivers are:
|
||||
xrgb2101010 is a packed 30 bits per pixel/10 bits per channel packed RGB
|
||||
format with 2 bits of padding.
|
||||
|
||||
There are cases when xrgb2101010 will work with the ``drm`` VO, but not
|
||||
with the ``drm`` backend for the ``gpu`` VO. This is because with the
|
||||
``gpu`` VO, in addition to requiring support in your DRM driver,
|
||||
requires support for xrgb2101010 in your EGL driver
|
||||
Unless you have an intel graphics card, a recent kernel and a recent
|
||||
version of mesa (>=18) xrgb2101010 is unlikely to work for you.
|
||||
|
||||
``--drm-draw-surface-size=<[WxH]>``
|
||||
Sets the size of the surface used on the draw plane. The surface will
|
||||
then be upscaled to the current screen resolution. This option can be
|
||||
useful when used together with the drmprime-drm hwdec interop at high
|
||||
resolutions, as it allows scaling the draw plane (which in this case
|
||||
only handles the OSD) down to a size the GPU can handle.
|
||||
This currently only has an effect when used together with the ``drm``
|
||||
backend for the ``gpu`` VO. The ``drm`` VO always uses xrgb8888.
|
||||
|
||||
When used without the drmprime-drm hwdec interop this option will just
|
||||
cause the video to get rendered at a different resolution and then
|
||||
scaled to screen size.
|
||||
|
||||
Note: this option is only available with DRM atomic support.
|
||||
``--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)
|
||||
@@ -576,11 +531,5 @@ Available video output drivers are:
|
||||
many of mpv's features (subtitle rendering, OSD/OSC, video filters, etc)
|
||||
are not available with this driver.
|
||||
|
||||
To use hardware decoding with ``--vo=gpu`` instead, use
|
||||
To use hardware decoding with ``--vo-gpu`` instead, use
|
||||
``--hwdec=mediacodec-copy`` along with ``--gpu-context=android``.
|
||||
|
||||
``wlshm`` (Wayland only)
|
||||
Shared memory video output driver without hardware acceleration that works
|
||||
whenever Wayland is present.
|
||||
|
||||
.. note:: This is a fallback only, and should not be normally used.
|
||||
@@ -139,6 +139,9 @@ stream/*:
|
||||
Some stream inputs are just there to invoke special demuxers, like
|
||||
stream_mf.c. (Basically to make the prefix "mf://" do something special.)
|
||||
|
||||
cache.c is a caching wrapper around streams implementations, needed for
|
||||
smooth network playback.
|
||||
|
||||
demux/:
|
||||
Demuxers split data streams into audio/video/sub streams, which in turn
|
||||
are split in packets. Packets (see demux_packet.h) are mostly byte chunks
|
||||
@@ -152,10 +155,6 @@ demux/:
|
||||
There is a stream header for each audio/video/sub stream, and each of them
|
||||
holds codec information about the stream and other information.
|
||||
|
||||
demux.c is a bit big, the main reason being that it contains the demuxer
|
||||
cache, which is implemented as a list of packets. The cache is complex
|
||||
because it support seeking, multiple ranges, prefetching, and so on.
|
||||
|
||||
video/:
|
||||
This contains several things related to audio/video decoding, as well as
|
||||
video filters.
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||

|
||||

|
||||
|
||||
# mpv
|
||||
## mpv
|
||||
|
||||
--------------
|
||||
|
||||
|
||||
* [External links](#external-links)
|
||||
@@ -9,9 +11,12 @@
|
||||
* [Downloads](#downloads)
|
||||
* [Changelog](#changelog)
|
||||
* [Compilation](#compilation)
|
||||
* [FFmpeg vs. Libav](#ffmpeg-vs-libav)
|
||||
* [FFmpeg ABI compatibility](#ffmpeg-abi-compatibility)
|
||||
* [Release cycle](#release-cycle)
|
||||
* [Bug reports](#bug-reports)
|
||||
* [Contributing](#contributing)
|
||||
* [Relation to MPlayer and mplayer2](#relation-to-mplayer-and-mplayer2)
|
||||
* [License](#license)
|
||||
* [Contact](#contact)
|
||||
|
||||
@@ -111,7 +116,7 @@ Essential dependencies (incomplete list):
|
||||
- Lua (optional, required for the OSC pseudo-GUI and youtube-dl integration)
|
||||
- libjpeg (optional, used for screenshots only)
|
||||
- uchardet (optional, for subtitle charset detection)
|
||||
- nvdec and vaapi libraries for hardware decoding on Linux (optional)
|
||||
- vdpau and vaapi libraries for hardware decoding on Linux (optional)
|
||||
|
||||
Libass dependencies:
|
||||
|
||||
@@ -128,10 +133,10 @@ FFmpeg dependencies:
|
||||
- libx264/libmp3lame/libfdk-aac if you want to use encoding (have to be
|
||||
explicitly enabled when compiling FFmpeg)
|
||||
- For native DASH playback, FFmpeg needs to be built with --enable-libxml2
|
||||
(although there are security implications, and DAHS support has lots of bugs).
|
||||
- AV1 decoding support requires dav1d.
|
||||
(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.)
|
||||
|
||||
Most of the above libraries are available in suitable versions on normal
|
||||
Linux distributions. For ease of compiling the latest git master of everything,
|
||||
@@ -144,6 +149,28 @@ or cross-compile from Linux with MinGW. See
|
||||
[Windows compilation][windows_compilation].
|
||||
|
||||
|
||||
## FFmpeg vs. Libav
|
||||
|
||||
|
||||
Generally, mpv should work with the latest release as well as the git version
|
||||
of FFmpeg. Libav support is currently broken, because they did not add certain
|
||||
FFmpeg API changes which mpv relies on.
|
||||
|
||||
|
||||
## FFmpeg ABI compatibility
|
||||
|
||||
mpv does not support linking against FFmpeg versions it was not built with, even
|
||||
if the linked version is supposedly ABI-compatible with the version it was
|
||||
compiled against. Expect malfunctions, crashes, and security issues if you
|
||||
do it anyway.
|
||||
|
||||
The reason for not supporting this is because it creates far too much complexity
|
||||
with little to no benefit, coupled with absurd and unusable FFmpeg API
|
||||
artifacts.
|
||||
|
||||
Newer mpv versions will refuse to start if runtime and compile time FFmpeg
|
||||
library versions mismatch.
|
||||
|
||||
## Release cycle
|
||||
|
||||
Every other month, an arbitrary git snapshot is made, and is assigned
|
||||
@@ -180,6 +207,16 @@ You can check [the wiki](https://github.com/mpv-player/mpv/wiki/Stuff-to-do)
|
||||
or the [issue tracker](https://github.com/mpv-player/mpv/issues?q=is%3Aopen+is%3Aissue+label%3A%22feature+request%22)
|
||||
for ideas on what you could contribute with.
|
||||
|
||||
## Relation to MPlayer and mplayer2
|
||||
|
||||
mpv is a fork of MPlayer. Much has changed, and in general, mpv should be
|
||||
considered a completely new program, rather than a MPlayer drop-in replacement.
|
||||
|
||||
For details see [FAQ entry](https://github.com/mpv-player/mpv/wiki/FAQ#How_is_mpv_related_to_MPlayer).
|
||||
|
||||
If you are wondering what's different from mplayer2 and MPlayer, an incomplete
|
||||
and largely unmaintained list of changes is located [here][mplayer-changes].
|
||||
|
||||
## License
|
||||
|
||||
GPLv2 "or later" by default, LGPLv2.1 "or later" with `--enable-lgpl`.
|
||||
@@ -191,15 +228,21 @@ See [details.](https://github.com/mpv-player/mpv/blob/master/Copyright)
|
||||
|
||||
Most activity happens on the IRC channel and the github issue tracker.
|
||||
|
||||
- **GitHub issue tracker**: [issue tracker][issue-tracker] (report bugs here)
|
||||
- **User IRC Channel**: `#mpv` on `irc.freenode.net`
|
||||
- **Developer IRC Channel**: `#mpv-devel` on `irc.freenode.net`
|
||||
- **GitHub issue tracker**: [issue tracker][issue-tracker] (report bugs here)
|
||||
- **User IRC Channel**: `#mpv` on `irc.freenode.net`
|
||||
- **Developer IRC Channel**: `#mpv-devel` on `irc.freenode.net`
|
||||
|
||||
To contact the `mpv` team in private write to `mpv-team@googlegroups.com`. Use
|
||||
only if discretion is required.
|
||||
|
||||
[releases]: https://github.com/mpv-player/mpv/releases
|
||||
[mpv-build]: https://github.com/mpv-player/mpv-build
|
||||
[homebrew-mpv]: https://github.com/mpv-player/homebrew-mpv
|
||||
[issue-tracker]: https://github.com/mpv-player/mpv/issues
|
||||
[ffmpeg_vs_libav]: https://github.com/mpv-player/mpv/wiki/FFmpeg-versus-Libav
|
||||
[release-policy]: https://github.com/mpv-player/mpv/blob/master/DOCS/release-policy.md
|
||||
[windows_compilation]: https://github.com/mpv-player/mpv/blob/master/DOCS/compile-windows.md
|
||||
[mplayer-changes]: https://github.com/mpv-player/mpv/blob/master/DOCS/mplayer-changes.rst
|
||||
[interface-changes]: https://github.com/mpv-player/mpv/blob/master/DOCS/interface-changes.rst
|
||||
[api-changes]: https://github.com/mpv-player/mpv/blob/master/DOCS/client-api-changes.rst
|
||||
[restore-old-bindings]: https://github.com/mpv-player/mpv/blob/master/etc/restore-old-bindings.conf
|
||||
|
||||
+124
-78
@@ -1,9 +1,48 @@
|
||||
Release 0.30.0
|
||||
Release 0.29.1
|
||||
==============
|
||||
|
||||
A bug fix release for the 0.29 release branch.
|
||||
|
||||
Fixes and Minor Enhancements
|
||||
----------------------------
|
||||
|
||||
- af_rubberband: Fix for A-V drift on seeking.
|
||||
- ao_alsa: Fixes to remove spam during pausing and end-of-file.
|
||||
- ao_jack: Fix for mpv attempting to connect to non-standard devices such as MIDI.
|
||||
- ao_openal: Drop support for Apple's bundled OpenAL due to lack of features to successfully build.
|
||||
- ao_pulse: Fix for audio buffer length calculation.
|
||||
- demux_lavf: Fix EDL playback of certain DASH streams.
|
||||
- demux_lavf: Mark v4l streams as not seekable.
|
||||
- demux_raw: Fix for gapless playback from raw audio input.
|
||||
- drm_atomic: Fix to enable video output with devices without a video overlay plane.
|
||||
- encode: Fix for crashes in case of failure to write to output.
|
||||
- encode: Fix for encoding when utilizing lavfi-complex.
|
||||
- Fix to strip HDR peak metadata if attached to SDR video (#6111)
|
||||
- macOS: Capability to fall back on SW rendering for testing purposes.
|
||||
- macOS: Fix for issues with the main menu.
|
||||
- macOS: Fix for side by side split view.
|
||||
- macOS: Fixes for crashes, most notably those happening on 10.14 (#5908, #6097, #6041, #6062).
|
||||
- macOS: No longer let older, unsupported versions of the Swift build tools through configure.
|
||||
- manpage: Misc fixups.
|
||||
- stream_{smb,file}: Fix for partial writes to buffers.
|
||||
- stream_libarchive: Fix hang when an out-of-bound seek happens.
|
||||
- stream_smb: Fix for crashes due to libsmbclient and threading (#5936).
|
||||
- vo_gpu: Fix for corruption when multiple compute shaders override each others' block sizes (#6083).
|
||||
- ytdl_hook: Always load "ytdl://" URLs with ytdl_hook first.
|
||||
- ytdl_hook: Fix audio streams not being picked up for some sites.
|
||||
|
||||
Options and Commands
|
||||
--------------------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- `--cocoa-cb-sw-renderer` to control whether or not to fall back on software rendering on macOS.
|
||||
|
||||
Release 0.29.0
|
||||
==============
|
||||
|
||||
This release requires FFmpeg 4.0 or newer.
|
||||
This release has replaced the internal Vulkan rendering abstraction
|
||||
with libplacebo's more up-to-date implementation.
|
||||
|
||||
|
||||
Features
|
||||
@@ -12,54 +51,40 @@ Features
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- vo_gpu: vulkan: hwdec_cuda: Add support for Vulkan interop
|
||||
- command: support for async commands
|
||||
- input: support for named arguments
|
||||
- cocoa-cb: add support for mac 10.14 Dark mode and run time switching
|
||||
- vo_gpu: switch to Vulkan API implementation based on libplacebo
|
||||
- context_drm_egl: Add support for presentation feedback
|
||||
- vo_gpu: implement error diffusion for dithering
|
||||
- vo/gpu: hwdec_vdpau: Support direct mode for 4:4:4 content
|
||||
- vo_gpu: hwdec_vaapi: Add Vulkan interop
|
||||
- video, TOOLS: add vf_fingerprint and a skip-logo script
|
||||
- vo_gpu: d3d11: add support for presentation feedback
|
||||
- vo_drm: 30bpp support
|
||||
- cocoa-cb: add support for 10bit opengl rendering
|
||||
- video: add pure gamma TRC curves for 2.0, 2.4 and 2.6
|
||||
- vo_drm: Implement N-buffering and presentation feedback
|
||||
- Reintroduce vo_wayland as vo_wlshm
|
||||
- video: add zimg wrapper
|
||||
- wayland: add presentation time
|
||||
- input: add gamepad support through SDL2
|
||||
- ao/openal: Add better sample format and channel layout selection
|
||||
- ao/openal: Add support for direct channels output
|
||||
- cocoa-cb: initial implementation via opengl-cb API
|
||||
- context_drm_egl: Introduce 30bpp support (requires Mesa 18.0.0_rc4 or later)
|
||||
- osx: add some more menu bar items as suggested by Apples's HIG
|
||||
- vo_gpu: make screenshots use the GL renderer (#5498, #5240)
|
||||
- x11: support Shift+TAB as an input key bind (#5849)
|
||||
- ytdl_hook: support native dash demuxer, if present
|
||||
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- cocoa-cb: use libmpv's advanced rendering control and timing
|
||||
- vo_gpu: improve tone mapping desaturation
|
||||
- vo_gpu: redesign peak detection algorithm
|
||||
- vo_gpu: allow boosting dark scenes when tone mapping
|
||||
- osc: improve look of seekranges
|
||||
- vo_gpu: x11: remove special vdpau probing, use EGL by default
|
||||
- demux: sort filenames naturally when playing a directory / archive
|
||||
- stream_dvb: rewrite channel switching, remove old stream control
|
||||
- filters: extend vf_format so that it can convert color parameters
|
||||
- ao_alsa, ao_pulse: reduce requested buffer size
|
||||
- audio: change format negotiation, remove channel remix fudging
|
||||
- client API: deprecate opengl-cb API and introduce a replacement API
|
||||
- demux: lower demuxer cache default sizes from 400MB/400MB to 50MB/150MB
|
||||
- osx: always deactivate the early opengl flush on macOS
|
||||
- scripting: change when/how player waits for scripts being loaded
|
||||
- vo_gpu: various improvements to HDR peak detection
|
||||
- vo_gpu: various improvements to HDR tone mapping
|
||||
- wayland_common: update to stable xdg-shell
|
||||
- ytdl_hook: parse youtube playlist urls to set start index if `--ytdl-raw-option=yes-playlist=` is specified
|
||||
|
||||
Deprecated
|
||||
~~~~~~~~~~
|
||||
|
||||
- af_lavrresample: deprecate this filter
|
||||
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- demux, stream: rip out the classic stream cache
|
||||
- vo_gpu: removed internal Vulkan implementation
|
||||
- Remove libdvdread support in favor of libdvdnav
|
||||
- demux, stream: remove much of the optical disc support
|
||||
- Remove classic Linux analog TV support, and DVB runtime controls
|
||||
- demux, stream: remove old rar support in favor of libarchive
|
||||
- vo_gpu: remove mali-fbdev
|
||||
- vo_gpu: remove vdpau/GLX backend
|
||||
- vf_vapourynth: remove Lua backend
|
||||
- vo_gpu: remove hwdec_d3d11eglrgb
|
||||
- build: drop support for SDL1 (only applied to ao_sdl)
|
||||
|
||||
|
||||
Options and Commands
|
||||
@@ -68,67 +93,88 @@ Options and Commands
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- command: add a subprocess command
|
||||
- vo_gpu: added --tone-mapping-max-boost, --hdr-peak-decay-rate, --hdr-scene-threshold-low/high
|
||||
- player: add --demuxer-cache-wait option
|
||||
- Implement backwards playback
|
||||
- demux: add a on-disk cache
|
||||
- demux, command: add another stream recording mechanism, `dump-cache` command
|
||||
- ao_pulse: add --pulse-allow-suspended
|
||||
- command: add video-add/video-remove/video-reload commands
|
||||
- demux_cue: added --demuxer-cue-codepage to select CUE sheet charset
|
||||
- input: add keybind command
|
||||
- command: add sub-start & sub-end properties
|
||||
- video/d3d11: added --d3d11-adapter to select a rendering adapter by name
|
||||
- vo_gpu/d3d11: added --d3d11-output-format for configuring swap chain format
|
||||
- add a number of --audio-resample-* options, which should from now on be used instead of --af-defaults=lavrresample:
|
||||
- ao/openal: --openal-direct-channels
|
||||
- command: add --osd-on-seek option defaulting to bar
|
||||
- command: add a change-list command (#5435)
|
||||
- options: add --http-proxy
|
||||
- options: add a builtin low-latency profile
|
||||
- vaapi: add option to select a non-default device path with --vaapi-device (#4320)
|
||||
- video: add option to reduce latency by 1 or 2 frames
|
||||
- vo_gpu: introduce --target-peak (#5521)
|
||||
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- vo_gpu: split --linear-scaling into --linear-upscaling and --linear-downscaling
|
||||
- vo_gpu: split --tone-mapping-desaturate into strength and exponent
|
||||
- drm: rename --drm-osd-plane-id to --drm-draw-plane, --drm-video-plane-id to --drm-drmprime-video-plane, --drm-osd-size to --drm-draw-surface-size
|
||||
- command: change cycle-value command behavior (#5772)
|
||||
- config: replace config dir lua-settings/ with dir script-opts/
|
||||
- options: --lavfi-complex now requires uniquely named filter pads.
|
||||
- options: --ytdl is now always enabled, even for libmpv
|
||||
- options: make --deinterlace=yes always deinterlace (#5219)
|
||||
- options: slightly improve filter help output for lavfi bridge
|
||||
- vo: rename --vo=opengl-cb to --vo=libmpv
|
||||
|
||||
|
||||
Deprecated
|
||||
~~~~~~~~~~
|
||||
|
||||
- options: deprecate --video-aspect, replaced by --video-aspect-override
|
||||
- options: deprecate --record-file, --spirv-compiler
|
||||
- encoding: deprecate a bunch of obscure options (--ovoffset, --oaoffset, --ovfirst, --oafirst)
|
||||
- options: deprecate --vf-defaults and --af-defaults
|
||||
- osx: --gpu-context=cocoa with --gpu-api=opengl is now deprecated in favour of --vo=libmpv
|
||||
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- options: remove deprecated --chapter option
|
||||
- demux: remove "program" property
|
||||
- stream: remove "disc-titles", "disc-title", "disc-title-list", and "angle" properties
|
||||
- cocoa-cb: remove --macos-title-bar-style, replaced by --macos-title-bar-material and --macos-title-bar-appearance
|
||||
- spirv: remove support for --spirv-compiler=nvidia (shaderc is the only remaining option)
|
||||
- command: remove an old compatibility hack that allowed CLI aliases to be set as property, deprecated in 0.26.0
|
||||
- input: remove legacy command handling for MPlayer commands that were deprecated in 2013
|
||||
- options: drop --opensles-sample-rate, as --audio-samplerate should be used if desired
|
||||
- options: drop deprecated --videotoolbox-format, --ff-aid, --ff-vid, --ff-sid, --ad-spdif-dtshd, --softvol options
|
||||
- options: remove --video-stereo-mode
|
||||
- options: remove deprecated ao/vo auto profiles
|
||||
- options: 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
|
||||
|
||||
|
||||
Fixes and Minor Enhancements
|
||||
----------------------------
|
||||
|
||||
- context_drm_egl: implement n-buffering
|
||||
- cocoa-cb: add support for custom colored title bar
|
||||
- vo_gpu: x11egl: support Mesa OML sync extension
|
||||
- demux, demux_edl: add extension for tracks sourced from separate streams
|
||||
- context_drm_egl: Use eglGetPlatformDisplayEXT if available
|
||||
- vo_gpu: vulkan: add Android context
|
||||
- wayland: use xdg-decoration if available
|
||||
- vo_gpu: glx: use GLX_OML_sync_control for better vsync reporting
|
||||
- demux: runtime option changing for cache and stream recording
|
||||
- x11: fix ICC profiling for multiple monitors
|
||||
- zsh completion: move generation to runtime and improve
|
||||
- HIDRemote: fix volume buttons on macOS 10.13 (#5721)
|
||||
- TOOLS/autoload: Fix broken "disabled" option
|
||||
- TOOLS/autoload: be more robust with slow directory listings (#5618)
|
||||
- ao_sdl: fix default buffer size
|
||||
- build: add static libraries to libmpv.pc
|
||||
- build: bump waf to 2.0.9 (fixes Python 3.7 compatibility)
|
||||
- build: manually add standard library search paths for linking (#5791)
|
||||
- common: round all integer times to milliseconds
|
||||
- demux, player: fix playback of sparse video streams (w/ still images)
|
||||
- demux: support for some kinds of timed metadata (e.g. ICY)
|
||||
- demux_mkv: add V_AV1 identifier for AV1
|
||||
- demux_mkv: support Meridian Lossless Packing in Matroska (#5923)
|
||||
- display additional metadata tags during video playback
|
||||
- drm_atomic: Fix memory leaks in drm_atomic_create
|
||||
- enable cache for FUSE filesystems on OpenBSD and FreeBSD
|
||||
- gpu: prefer 16bit floating point FBO formats to 16bit integer ones
|
||||
- hwdec_vaegl: Fix VAAPI EGL interop used with gpu-context=drm
|
||||
- input.conf: use exact value for [ binding, which does the inverse of ]
|
||||
- input: add a keybinding to toggle hardware decoding
|
||||
- input: minor additions to default key bindings (#973)
|
||||
- osc: fix accidentally skipping files when seeking with slider
|
||||
- player: fix strange behavior on edition switching
|
||||
- player: fix track autoselection of external files yet again
|
||||
- terminal-unix: stop trying to read when terminal disappears (#5842)
|
||||
- video: actually wait for last frame being rendered on EOF
|
||||
- video: fix --video-rotate in some cases
|
||||
- video: fix crash with vdpau when reinitializing rendering (#5447)
|
||||
- vo_gpu: fix anamorphic screenshots (#5619)
|
||||
- vo_vdpau: fix resizing and rotation problems
|
||||
|
||||
|
||||
This listing is not complete. Check DOCS/client-api-changes.rst for a history
|
||||
of changes to the client API, and DOCS/interface-changes.rst for a history
|
||||
of changes to other user-visible interfaces.
|
||||
|
||||
A complete changelog can be seen by running `git log v0.29.1..v0.30.0`
|
||||
A complete changelog can be seen by running `git log v0.28.2..v0.29.0`
|
||||
in the git repository or by visiting either
|
||||
https://github.com/mpv-player/mpv/compare/v0.29.1...v0.30.0 or
|
||||
https://git.srsfckn.biz/mpv/log/?qt=range&q=v0.29.1..v0.30.0
|
||||
https://github.com/mpv-player/mpv/compare/v0.28.2...v0.29.0 or
|
||||
https://git.srsfckn.biz/mpv/log/?qt=range&q=v0.28.2..v0.29.0
|
||||
@@ -10,9 +10,11 @@ export PYTHON=/usr/bin/python3
|
||||
"$PYTHON" bootstrap.py
|
||||
"$PYTHON" waf configure \
|
||||
--check-c-compiler=gcc \
|
||||
--enable-spirv-cross \
|
||||
--disable-egl-angle-lib \
|
||||
--enable-crossc \
|
||||
--enable-d3d-hwaccel \
|
||||
--enable-d3d11 \
|
||||
--enable-egl-angle \
|
||||
--enable-jpeg \
|
||||
--enable-lcms2 \
|
||||
--enable-libarchive \
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
#!/usr/bin/bash
|
||||
set -e
|
||||
|
||||
export PYTHON=/usr/bin/python3
|
||||
|
||||
# Write an empty fonts.conf to speed up fc-cache
|
||||
export FONTCONFIG_FILE=/dummy-fonts.conf
|
||||
cat >"$FONTCONFIG_FILE" <<EOF
|
||||
@@ -14,12 +12,12 @@ EOF
|
||||
# Install build dependencies for mpv
|
||||
pacman -S --noconfirm --needed \
|
||||
$MINGW_PACKAGE_PREFIX-toolchain \
|
||||
$MINGW_PACKAGE_PREFIX-angleproject-git \
|
||||
$MINGW_PACKAGE_PREFIX-cmake \
|
||||
$MINGW_PACKAGE_PREFIX-lcms2 \
|
||||
$MINGW_PACKAGE_PREFIX-libarchive \
|
||||
$MINGW_PACKAGE_PREFIX-libass \
|
||||
$MINGW_PACKAGE_PREFIX-libjpeg-turbo \
|
||||
$MINGW_PACKAGE_PREFIX-libplacebo \
|
||||
$MINGW_PACKAGE_PREFIX-lua51 \
|
||||
$MINGW_PACKAGE_PREFIX-ninja \
|
||||
$MINGW_PACKAGE_PREFIX-rubberband \
|
||||
@@ -54,19 +52,21 @@ pacman -Sc --noconfirm
|
||||
# Compile shaderc
|
||||
(
|
||||
git clone --depth=1 https://github.com/google/shaderc && cd shaderc
|
||||
"$PYTHON" utils/git-sync-deps
|
||||
git clone --depth=1 https://github.com/google/glslang.git third_party/glslang
|
||||
git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Tools.git third_party/spirv-tools
|
||||
git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Headers.git third_party/spirv-headers
|
||||
|
||||
mkdir build && cd build
|
||||
cmake -GNinja -DCMAKE_BUILD_TYPE=Release -DSHADERC_SKIP_TESTS=ON \
|
||||
-DCMAKE_INSTALL_PREFIX=$MINGW_PREFIX ..
|
||||
ninja install
|
||||
cp -f libshaderc/libshaderc_shared.dll $MINGW_PREFIX/bin/
|
||||
)
|
||||
|
||||
# Compile SPIRV-Cross
|
||||
# Compile crossc
|
||||
(
|
||||
git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Cross && cd SPIRV-Cross
|
||||
git clone --depth=1 https://github.com/rossy/crossc && cd crossc
|
||||
git submodule update --init
|
||||
|
||||
mkdir build && cd build
|
||||
cmake -GNinja -DSPIRV_CROSS_SHARED=ON -DCMAKE_INSTALL_PREFIX=$MINGW_PREFIX ..
|
||||
ninja install
|
||||
make -j4 install prefix=$MINGW_PREFIX
|
||||
)
|
||||
+2
-50
@@ -20,37 +20,19 @@ def is_user_lib(objfile, libname):
|
||||
not "libc." in libname and \
|
||||
not "libgcc." in libname and \
|
||||
not os.path.basename(libname) == 'Python' and \
|
||||
not os.path.basename(objfile) in libname and \
|
||||
not "libswift" in libname
|
||||
not os.path.basename(objfile) in libname
|
||||
|
||||
def otool(objfile):
|
||||
command = "otool -L '%s' | grep -e '\t' | awk '{ print $1 }'" % objfile
|
||||
command = "otool -L %s | grep -e '\t' | awk '{ print $1 }'" % objfile
|
||||
output = subprocess.check_output(command, shell = True, universal_newlines=True)
|
||||
return set(filter(partial(is_user_lib, objfile), output.split()))
|
||||
|
||||
def get_rpaths_dev_tools(binary):
|
||||
command = "otool -l '%s' | grep -A2 LC_RPATH | grep path | grep \"Xcode\|CommandLineTools\"" % binary
|
||||
result = subprocess.check_output(command, shell = True, universal_newlines=True)
|
||||
pathRe = re.compile("^\s*path (.*) \(offset \d*\)$")
|
||||
output = []
|
||||
|
||||
for line in result.splitlines():
|
||||
output.append(pathRe.search(line).group(1).strip())
|
||||
|
||||
return output
|
||||
|
||||
def install_name_tool_change(old, new, objfile):
|
||||
subprocess.call(["install_name_tool", "-change", old, new, objfile])
|
||||
|
||||
def install_name_tool_id(name, objfile):
|
||||
subprocess.call(["install_name_tool", "-id", name, objfile])
|
||||
|
||||
def install_name_tool_add_rpath(rpath, binary):
|
||||
subprocess.call(["install_name_tool", "-add_rpath", rpath, binary])
|
||||
|
||||
def install_name_tool_delete_rpath(rpath, binary):
|
||||
subprocess.call(["install_name_tool", "-delete_rpath", rpath, binary])
|
||||
|
||||
def libraries(objfile, result = dict()):
|
||||
libs_list = otool(objfile)
|
||||
result[objfile] = libs_list
|
||||
@@ -100,45 +82,15 @@ def process_libraries(libs_dict, binary, processed = []):
|
||||
|
||||
return ls
|
||||
|
||||
def process_swift_libraries(binary):
|
||||
command = ['xcrun', '--find', 'swift-stdlib-tool']
|
||||
swiftStdlibTool = subprocess.check_output(command, universal_newlines=True).strip()
|
||||
# from xcode11 on the dynamic swift libs reside in a separate directory from
|
||||
# the std one, might need versioned paths for future swift versions
|
||||
swiftLibPath = os.path.join(swiftStdlibTool, '../../lib/swift-5.0/macosx')
|
||||
swiftLibPath = os.path.abspath(swiftLibPath)
|
||||
|
||||
command = [swiftStdlibTool, '--copy', '--platform', 'macosx', '--scan-executable', binary, '--destination', lib_path(binary)]
|
||||
|
||||
if os.path.exists(swiftLibPath):
|
||||
command.extend(['--source-libraries', swiftLibPath])
|
||||
|
||||
subprocess.check_output(command, universal_newlines=True)
|
||||
|
||||
print(">> setting additional rpath for swift libraries")
|
||||
install_name_tool_add_rpath("@executable_path/lib", binary)
|
||||
|
||||
def remove_dev_tools_rapths(binary):
|
||||
for path in get_rpaths_dev_tools(binary):
|
||||
install_name_tool_delete_rpath(path, binary)
|
||||
|
||||
def main():
|
||||
binary = os.path.abspath(sys.argv[1])
|
||||
if not os.path.exists(lib_path(binary)):
|
||||
os.makedirs(lib_path(binary))
|
||||
print(">> gathering all linked libraries")
|
||||
libs = libraries(binary)
|
||||
|
||||
print(">> copying and processing all linked libraries")
|
||||
libs_processed = process_libraries(libs, binary)
|
||||
while libs_processed is not None:
|
||||
libs_processed = process_libraries(libs, binary, libs_processed)
|
||||
|
||||
print(">> removing rpath definitions towards dev tools")
|
||||
remove_dev_tools_rapths(binary)
|
||||
|
||||
print(">> copying and processing swift libraries")
|
||||
process_swift_libraries(binary)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+4
-42
@@ -5,20 +5,6 @@
|
||||
-- the internal playlist. (It stops if the it would add an already existing
|
||||
-- playlist entry at the same position - this makes it "stable".)
|
||||
-- Add at most 5000 * 2 files when starting a file (before + after).
|
||||
|
||||
--[[
|
||||
To configure this script use file autoload.conf in director script-opts (the "script-opts"
|
||||
directory must be in the mpv configuration directory, typically ~/.config/mpv/).
|
||||
|
||||
Example configuration would be:
|
||||
|
||||
disabled=false
|
||||
images=false
|
||||
videos=true
|
||||
audio=true
|
||||
|
||||
--]]
|
||||
|
||||
MAXENTRIES = 5000
|
||||
|
||||
local msg = require 'mp.msg'
|
||||
@@ -26,10 +12,7 @@ local options = require 'mp.options'
|
||||
local utils = require 'mp.utils'
|
||||
|
||||
o = {
|
||||
disabled = false,
|
||||
images = true,
|
||||
videos = true,
|
||||
audio = true
|
||||
disabled = false
|
||||
}
|
||||
options.read_options(o)
|
||||
|
||||
@@ -39,30 +22,11 @@ function Set (t)
|
||||
return set
|
||||
end
|
||||
|
||||
function SetUnion (a,b)
|
||||
local res = {}
|
||||
for k in pairs(a) do res[k] = true end
|
||||
for k in pairs(b) do res[k] = true end
|
||||
return res
|
||||
end
|
||||
|
||||
EXTENSIONS_VIDEO = Set {
|
||||
'mkv', 'avi', 'mp4', 'ogv', 'webm', 'rmvb', 'flv', 'wmv', 'mpeg', 'mpg', 'm4v', '3gp'
|
||||
EXTENSIONS = Set {
|
||||
'mkv', 'avi', 'mp4', 'ogv', 'webm', 'rmvb', 'flv', 'wmv', 'mpeg', 'mpg', 'm4v', '3gp',
|
||||
'mp3', 'wav', 'ogm', 'flac', 'm4a', 'wma', 'ogg', 'opus',
|
||||
}
|
||||
|
||||
EXTENSIONS_AUDIO = Set {
|
||||
'mp3', 'wav', 'ogm', 'flac', 'm4a', 'wma', 'ogg', 'opus'
|
||||
}
|
||||
|
||||
EXTENSIONS_IMAGES = Set {
|
||||
'jpg', 'jpeg', 'png', 'tif', 'tiff', 'gif', 'webp', 'svg', 'bmp'
|
||||
}
|
||||
|
||||
EXTENSIONS = Set {}
|
||||
if o.videos then EXTENSIONS = SetUnion(EXTENSIONS, EXTENSIONS_VIDEO) end
|
||||
if o.audio then EXTENSIONS = SetUnion(EXTENSIONS, EXTENSIONS_AUDIO) end
|
||||
if o.images then EXTENSIONS = SetUnion(EXTENSIONS, EXTENSIONS_IMAGES) end
|
||||
|
||||
function add_files_at(index, files)
|
||||
index = index - 1
|
||||
local oldcount = mp.get_property_number("playlist-count", 1)
|
||||
@@ -138,7 +102,6 @@ function find_and_add_entries()
|
||||
-- check if this is a manually made playlist
|
||||
if (pl_count > 1 and autoloaded == nil) or
|
||||
(pl_count == 1 and EXTENSIONS[string.lower(get_extension(filename))] == nil) then
|
||||
msg.verbose("stopping: manually made playlist")
|
||||
return
|
||||
else
|
||||
autoloaded = true
|
||||
@@ -151,7 +114,6 @@ function find_and_add_entries()
|
||||
|
||||
local files = utils.readdir(dir, "files")
|
||||
if files == nil then
|
||||
msg.verbose("no other files in directory")
|
||||
return
|
||||
end
|
||||
table.filter(files, function (v, k)
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
-- Test script for some command API details.
|
||||
|
||||
local utils = require("mp.utils")
|
||||
|
||||
function join(sep, arr, count)
|
||||
local r = ""
|
||||
if count == nil then
|
||||
count = #arr
|
||||
end
|
||||
for i = 1, count do
|
||||
if i > 1 then
|
||||
r = r .. sep
|
||||
end
|
||||
r = r .. utils.to_string(arr[i])
|
||||
end
|
||||
return r
|
||||
end
|
||||
|
||||
mp.observe_property("vo-configured", "bool", function(_, v)
|
||||
if v ~= true then
|
||||
return
|
||||
end
|
||||
|
||||
print("async expand-text")
|
||||
mp.command_native_async({"expand-text", "hello ${path}!"},
|
||||
function(res, val, err)
|
||||
print("done async expand-text: " .. join(" ", {res, val, err}))
|
||||
end)
|
||||
|
||||
-- make screenshot writing very slow
|
||||
mp.set_property("screenshot-format", "png")
|
||||
mp.set_property("screenshot-png-compression", "9")
|
||||
|
||||
timer = mp.add_periodic_timer(0.1, function() print("I'm alive") end)
|
||||
timer:resume()
|
||||
|
||||
print("Slow screenshot command...")
|
||||
res, err = mp.command_native({"screenshot"})
|
||||
print("done, res: " .. utils.to_string(res))
|
||||
|
||||
print("Slow screenshot async command...")
|
||||
res, err = mp.command_native_async({"screenshot"}, function(res)
|
||||
print("done (async), res: " .. utils.to_string(res))
|
||||
timer:kill()
|
||||
end)
|
||||
print("done (sending), res: " .. utils.to_string(res))
|
||||
|
||||
print("Broken screenshot async command...")
|
||||
mp.command_native_async({"screenshot-to-file", "/nonexistent/bogus.png"},
|
||||
function(res, val, err)
|
||||
print("done err scr.: " .. join(" ", {res, val, err}))
|
||||
end)
|
||||
|
||||
mp.command_native_async({name = "subprocess", args = {"sh", "-c", "echo hi && sleep 10s"}, capture_stdout = true},
|
||||
function(res, val, err)
|
||||
print("done subprocess: " .. join(" ", {res, val, err}))
|
||||
end)
|
||||
|
||||
local x = mp.command_native_async({name = "subprocess", args = {"sleep", "inf"}},
|
||||
function(res, val, err)
|
||||
print("done sleep inf subprocess: " .. join(" ", {res, val, err}))
|
||||
end)
|
||||
mp.add_timeout(15, function()
|
||||
print("aborting sleep inf subprocess after timeout")
|
||||
mp.abort_async_command(x)
|
||||
end)
|
||||
|
||||
-- (assuming this "freezes")
|
||||
local y = mp.command_native_async({name = "sub-add", url = "-"},
|
||||
function(res, val, err)
|
||||
print("done sub-add stdin: " .. join(" ", {res, val, err}))
|
||||
end)
|
||||
mp.add_timeout(20, function()
|
||||
print("aborting sub-add stdin after timeout")
|
||||
mp.abort_async_command(y)
|
||||
end)
|
||||
|
||||
|
||||
|
||||
-- This should get killed on script exit.
|
||||
mp.command_native_async({name = "subprocess", playback_only = false,
|
||||
args = {"sleep", "inf"}}, function()end)
|
||||
|
||||
-- Runs detached; should be killed on player exit (forces timeout)
|
||||
mp.command_native({_flags={"async"}, name = "subprocess",
|
||||
playback_only = false, args = {"sleep", "inf"}})
|
||||
end)
|
||||
|
||||
function freeze_test(playback_only)
|
||||
-- This "freezes" the script, should be killed via timeout.
|
||||
counter = counter and counter + 1 or 0
|
||||
print("freeze! " .. counter)
|
||||
local x = mp.command_native({name = "subprocess",
|
||||
playback_only = playback_only,
|
||||
args = {"sleep", "inf"}})
|
||||
print("done, killed=" .. utils.to_string(x.killed_by_us))
|
||||
end
|
||||
|
||||
mp.register_event("shutdown", function()
|
||||
freeze_test(false)
|
||||
end)
|
||||
|
||||
mp.register_event("idle", function()
|
||||
freeze_test(true)
|
||||
end)
|
||||
@@ -1,37 +0,0 @@
|
||||
-- Test a float property which internally uses NaN.
|
||||
-- Run with --no-config (or just scale-param1 not set).
|
||||
|
||||
local utils = require 'mp.utils'
|
||||
|
||||
prop_name = "scale-param1"
|
||||
|
||||
-- internal NaN, return string "default" instead of NaN
|
||||
v = mp.get_property_native(prop_name, "fail")
|
||||
print("Exp:", "string", "\"default\"")
|
||||
print("Got:", type(v), utils.to_string(v))
|
||||
|
||||
v = mp.get_property(prop_name)
|
||||
print("Exp:", "default")
|
||||
print("Got:", v)
|
||||
|
||||
-- not representable -> return provided fallback value
|
||||
v = mp.get_property_number(prop_name, -100)
|
||||
print("Exp:", -100)
|
||||
print("Got:", v)
|
||||
|
||||
mp.set_property_native(prop_name, 123)
|
||||
v = mp.get_property_number(prop_name, -100)
|
||||
print("Exp:", "number", 123)
|
||||
print("Got:", type(v), utils.to_string(v))
|
||||
|
||||
-- try to set an actual NaN
|
||||
st, msg = mp.set_property_number(prop_name, 0.0/0)
|
||||
print("Exp:", nil, "<message>")
|
||||
print("Got:", st, msg)
|
||||
|
||||
-- set default
|
||||
mp.set_property(prop_name, "default")
|
||||
|
||||
v = mp.get_property(prop_name)
|
||||
print("Exp:", "default")
|
||||
print("Got:", v)
|
||||
@@ -1,266 +0,0 @@
|
||||
--[[
|
||||
|
||||
Automatically skip in files if video frames with pre-supplied fingerprints are
|
||||
detected. This will skip ahead by a pre-configured amount of time if a matching
|
||||
video frame is detected.
|
||||
|
||||
This requires the vf_fingerprint video filter to be compiled in. Read the
|
||||
documentation of this filter for caveats (which will automatically apply to
|
||||
this script as well), such as no support for zero-copy hardware decoding.
|
||||
|
||||
You need to manually gather and provide fingerprints for video frames and add
|
||||
them to a configuration file in script-opts/skip-logo.conf (the "script-opts"
|
||||
directory must be in the mpv configuration directory, typically ~/.config/mpv/).
|
||||
|
||||
Example script-opts/skip-logo.conf:
|
||||
|
||||
|
||||
cases = {
|
||||
{
|
||||
-- Skip ahead 10 seconds if a black frame was detected
|
||||
-- Note: this is dangerous non-sense. It's just for demonstration.
|
||||
name = "black frame", -- print if matched
|
||||
skip = 10, -- number of seconds to skip forward
|
||||
score = 0.3, -- required score
|
||||
fingerprint = "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||
},
|
||||
{
|
||||
-- Skip ahead 20 seconds if a white frame was detected
|
||||
-- Note: this is dangerous non-sense. It's just for demonstration.
|
||||
name = "fun2",
|
||||
skip = 20,
|
||||
fingerprint = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
},
|
||||
}
|
||||
|
||||
This is actually a lua file. Lua was chosen because it seemed less of a pain to
|
||||
parse. Future versions of this script may change the format.
|
||||
|
||||
The fingerprint is a video frame, converted to "gray" (8 bit per pixels), full
|
||||
range, each pixel concatenated into an array, converted to a hex string. You
|
||||
can produce these fingerprints by running this manually:
|
||||
|
||||
mpv --vf=fingerprint:print yourfile.mkv
|
||||
|
||||
This will log the fingerprint of each video frame to the console, along with its
|
||||
timestamp. You find the fingerprint of a unique-enough looking frame, and add
|
||||
it as entry to skip-logo.conf.
|
||||
|
||||
You can provide a score for "fuzziness". If no score is provided, a default
|
||||
value of 0.3 is used. The score is inverse: 0 means exactly the same, while a
|
||||
higher score means a higher difference. Currently, the score is computed as
|
||||
euclidean distance between the video frame and the pre-provided fingerprint,
|
||||
thus the highest score is 16. You probably want a score lower than 1 at least.
|
||||
(This algorithm is very primitive, but also simple and fast to compute.)
|
||||
|
||||
There's always the danger of false positives, which might be quite annoying.
|
||||
It's up to you what you hate more, the logo, or random skips if false positives
|
||||
are detected. Also, it's always active, and might eat too much CPU with files
|
||||
that have a high resolution or framerate. To temporarily disable the script,
|
||||
having a keybind like this in your input.conf will be helpful:
|
||||
|
||||
ctrl+k vf toggle @skip-logo
|
||||
|
||||
This will disable/enable the fingerprint filter, which the script automatically
|
||||
adds at start.
|
||||
|
||||
Another important caveat is that the script currently disables matching during
|
||||
seeking or playback initialization, which means it cannot match the first few
|
||||
frames of a video. This could be fixed, but the author was too lazy to do so.
|
||||
|
||||
--]]
|
||||
|
||||
local utils = require "mp.utils"
|
||||
local msg = require "mp.msg"
|
||||
|
||||
local label = "skip-logo"
|
||||
local meta_property = string.format("vf-metadata/%s", label)
|
||||
|
||||
local config = {}
|
||||
local cases = {}
|
||||
local cur_bmp
|
||||
local seeking = false
|
||||
local playback_start_pts = nil
|
||||
|
||||
-- Convert a hex string to an array. Convert each byte to a [0,1] float by
|
||||
-- interpreting it as normalized uint8_t.
|
||||
-- The data parameter, if not nil, may be used as storage (avoiding garbage).
|
||||
local function hex_to_norm8(hex, data)
|
||||
local size = math.floor(#hex / 2)
|
||||
if #hex ~= size * 2 then
|
||||
return nil
|
||||
end
|
||||
local res
|
||||
if (data ~= nil) and (#data == size) then
|
||||
res = data
|
||||
else
|
||||
res = {}
|
||||
end
|
||||
for i = 1, size do
|
||||
local num = tonumber(hex:sub(i * 2, i * 2 + 1), 16)
|
||||
if num == nil then
|
||||
return nil
|
||||
end
|
||||
res[i] = num / 255.0
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
local function compare_bmp(a, b)
|
||||
if #a ~= #b then
|
||||
return nil -- can't compare
|
||||
end
|
||||
local sum = 0
|
||||
for i = 1, #a do
|
||||
local diff = a[i] - b[i]
|
||||
sum = sum + diff * diff
|
||||
end
|
||||
return math.sqrt(sum)
|
||||
end
|
||||
|
||||
local function load_config()
|
||||
local conf_file = mp.find_config_file("script-opts/skip-logo.conf")
|
||||
local conf_fn
|
||||
local err = nil
|
||||
if conf_file then
|
||||
if setfenv then
|
||||
conf_fn, err = loadfile(conf_file)
|
||||
if conf_fn then
|
||||
setfenv(conf_fn, config)
|
||||
end
|
||||
else
|
||||
msg.warn("Lua 5.2 was not tested, this might go wrong.")
|
||||
conf_fn, err = loadfile(conf_file, "t", config)
|
||||
end
|
||||
else
|
||||
err = "config file not found"
|
||||
end
|
||||
|
||||
if conf_fn and (not err) then
|
||||
local ok, err2 = pcall(conf_fn)
|
||||
err = err2
|
||||
end
|
||||
|
||||
if err then
|
||||
msg.error("Failed to load config file:", err)
|
||||
end
|
||||
|
||||
if config.cases then
|
||||
for n, case in ipairs(config.cases) do
|
||||
local err = nil
|
||||
case.bitmap = hex_to_norm8(case.fingerprint)
|
||||
if case.bitmap == nil then
|
||||
err = "invalid or missing fingerprint field"
|
||||
end
|
||||
if case.score == nil then
|
||||
case.score = 0.3
|
||||
end
|
||||
if type(case.score) ~= "number" then
|
||||
err = "score field is not a number"
|
||||
end
|
||||
if type(case.skip) ~= "number" then
|
||||
err = "skip field is not a number or missing"
|
||||
end
|
||||
if case.name == nil then
|
||||
case.name = ("Entry %d"):format(n)
|
||||
end
|
||||
if err == nil then
|
||||
cases[#cases + 1] = case
|
||||
else
|
||||
msg.error(("Entry %s: %s, ignoring."):format(case.name, err))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
load_config()
|
||||
|
||||
-- Returns true on match and if something was done.
|
||||
local function check_fingerprint(hex, pts)
|
||||
local bmp = hex_to_norm8(hex, cur_bmp)
|
||||
cur_bmp = bmp
|
||||
|
||||
-- If parsing the filter's result failed (well, it shouldn't).
|
||||
assert(bmp ~= nil, "filter returned nonsense")
|
||||
|
||||
for _, case in ipairs(cases) do
|
||||
local score = compare_bmp(case.bitmap, bmp)
|
||||
if (score ~= nil) and (score <= case.score) then
|
||||
msg.warn(("Matching %s: score=%f (required: %f) at %s, skipping %f seconds"):
|
||||
format(case.name, score, case.score, mp.format_time(pts), case.skip))
|
||||
mp.commandv("seek", pts + case.skip, "absolute+exact")
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
local function read_frames()
|
||||
local result = mp.get_property_native(meta_property)
|
||||
if result == nil then
|
||||
return
|
||||
end
|
||||
|
||||
-- Try to get all entries. Out of laziness, assume that there are at most
|
||||
-- 100 entries. (In fact, vf_fingerprint limits it to 10.)
|
||||
for i = 0, 99 do
|
||||
local prefix = string.format("fp%d.", i)
|
||||
local hex = result[prefix .. "hex"]
|
||||
|
||||
local pts = tonumber(result[prefix .. "pts"])
|
||||
if (hex == nil) or (pts == nil) then
|
||||
break
|
||||
end
|
||||
|
||||
local skip = false -- blame Lua for not having "continue" or "goto", not me
|
||||
|
||||
-- If seeking just stopped, there will be frames before the seek target,
|
||||
-- ignore them by checking the timestamps.
|
||||
if playback_start_pts ~= nil then
|
||||
if pts >= playback_start_pts then
|
||||
playback_start_pts = nil -- just for robustness
|
||||
else
|
||||
skip = true
|
||||
end
|
||||
end
|
||||
|
||||
if not skip then
|
||||
if check_fingerprint(hex, pts) then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
mp.observe_property(meta_property, "none", function()
|
||||
-- Ignore frames that are decoded/filtered during seeking.
|
||||
if seeking then
|
||||
return
|
||||
end
|
||||
|
||||
read_frames()
|
||||
end)
|
||||
|
||||
mp.observe_property("seeking", "bool", function(name, val)
|
||||
seeking = val
|
||||
if seeking == false then
|
||||
playback_start_pts = mp.get_property_number("playback-time")
|
||||
read_frames()
|
||||
end
|
||||
end)
|
||||
|
||||
local filters = mp.get_property_native("option-info/vf/choices", {})
|
||||
local found = false
|
||||
for _, f in ipairs(filters) do
|
||||
if f == "fingerprint" then
|
||||
found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if found then
|
||||
mp.command(("no-osd vf add @%s:fingerprint"):format(label, filter))
|
||||
else
|
||||
msg.warn("vf_fingerprint not found")
|
||||
end
|
||||
+2
-2
@@ -7,7 +7,7 @@ import fileinput
|
||||
from optparse import OptionParser
|
||||
|
||||
def sh(command):
|
||||
return os.popen(command).read().strip()
|
||||
return os.popen(command).read()
|
||||
|
||||
def bundle_path(binary_name):
|
||||
return "%s.app" % binary_name
|
||||
@@ -80,7 +80,7 @@ def main():
|
||||
|
||||
if options.deps:
|
||||
print("> bundling dependencies")
|
||||
print(sh(" ".join(["TOOLS/dylib-unhell.py", target_binary(binary_name)])))
|
||||
sh(" ".join(["TOOLS/dylib-unhell.py", target_binary(binary_name)]))
|
||||
|
||||
print("done.")
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ "x$TARGET" != "xx86_64-w64-mingw32" ]; then
|
||||
if [ "x$LIBAV" != "xffmpeg-git" ] || [ "x$TRAVIS_OS_NAME" != "xlinux" ]; then
|
||||
# trigger build only on one of the matrix nodes
|
||||
exit;
|
||||
fi
|
||||
|
||||
Executable
+283
@@ -0,0 +1,283 @@
|
||||
#!/usr/bin/perl
|
||||
|
||||
# Generate ZSH completion
|
||||
|
||||
#
|
||||
# This file is part of mpv.
|
||||
#
|
||||
# mpv is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2.1 of the License, or (at your option) any later version.
|
||||
#
|
||||
# mpv is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use warnings FATAL => 'uninitialized';
|
||||
|
||||
my $mpv = $ARGV[0] || 'mpv';
|
||||
|
||||
my @opts = parse_main_opts('--list-options', '^ (\-\-[^\s\*]*)\*?\s*(.*)');
|
||||
|
||||
die "Couldn't find any options" unless (@opts);
|
||||
|
||||
my @ao = parse_opts('--ao=help', '^ ([^\s\:]*)\s*(.*)');
|
||||
my @vo = parse_opts('--vo=help', '^ ([^\s\:]*)\s*(.*)');
|
||||
|
||||
my @af = parse_opts('--af=help', '^ ([^\s\:]*)\s*(.*)');
|
||||
my @vf = parse_opts('--vf=help', '^ ([^\s\:]*)\s*(.*)');
|
||||
|
||||
my @protos = parse_opts('--list-protocols', '^ ([^\s]*)');
|
||||
|
||||
my ($opts_str, $ao_str, $vo_str, $af_str, $vf_str, $protos_str);
|
||||
|
||||
$opts_str .= qq{ '$_' \\\n} foreach (@opts);
|
||||
chomp $opts_str;
|
||||
|
||||
$ao_str .= qq{ '$_' \\\n} foreach (@ao);
|
||||
chomp $ao_str;
|
||||
|
||||
$vo_str .= qq{ '$_' \\\n} foreach (@vo);
|
||||
chomp $vo_str;
|
||||
|
||||
$af_str .= qq{ '$_' \\\n} foreach (@af);
|
||||
chomp $af_str;
|
||||
|
||||
$vf_str .= qq{ '$_' \\\n} foreach (@vf);
|
||||
chomp $vf_str;
|
||||
|
||||
$protos_str = join(' ', @protos);
|
||||
|
||||
my $runtime_completions = <<'EOS';
|
||||
profile|show-profile)
|
||||
local -a profiles
|
||||
local current
|
||||
for current in "${(@f)$($words[1] --profile=help)}"; do
|
||||
current=${current//\*/\\\*}
|
||||
current=${current//\:/\\\:}
|
||||
current=${current//\[/\\\[}
|
||||
current=${current//\]/\\\]}
|
||||
if [[ $current =~ $'\t'([^$'\t']*)$'\t'(.*) ]]; then
|
||||
if [[ -n $match[2] ]]; then
|
||||
current="$match[1][$match[2]]"
|
||||
else
|
||||
current="$match[1]"
|
||||
fi
|
||||
profiles=($profiles $current)
|
||||
fi
|
||||
done
|
||||
if [[ $state == show-profile ]]; then
|
||||
# For --show-profile, only one allowed
|
||||
if (( ${#profiles} > 0 )); then
|
||||
_values 'profile' $profiles && rc=0
|
||||
fi
|
||||
else
|
||||
# For --profile, multiple allowed
|
||||
profiles=($profiles 'help[list profiles]')
|
||||
_values -s , 'profile(s)' $profiles && rc=0
|
||||
fi
|
||||
;;
|
||||
|
||||
audio-device)
|
||||
local -a audio_devices
|
||||
local current
|
||||
for current in "${(@f)$($words[1] --audio-device=help)}"; do
|
||||
current=${current//\*/\\\*}
|
||||
current=${current//\:/\\\:}
|
||||
current=${current//\[/\\\[}
|
||||
current=${current//\]/\\\]}
|
||||
if [[ $current =~ ' '\'([^\']*)\'' \('(.*)'\)' ]]; then
|
||||
audio_devices=($audio_devices "$match[1][$match[2]]")
|
||||
fi
|
||||
done
|
||||
audio_devices=($audio_devices 'help[list audio devices]')
|
||||
_values 'audio device' $audio_devices && rc=0
|
||||
;;
|
||||
EOS
|
||||
chomp $runtime_completions;
|
||||
|
||||
my $tmpl = <<"EOS";
|
||||
#compdef mpv
|
||||
|
||||
# For customization, see:
|
||||
# https://github.com/mpv-player/mpv/wiki/Zsh-completion-customization
|
||||
|
||||
local curcontext="\$curcontext" state state_descr line
|
||||
typeset -A opt_args
|
||||
|
||||
local -a match mbegin mend
|
||||
local MATCH MBEGIN MEND
|
||||
|
||||
# By default, don't complete URLs unless no files match
|
||||
local -a tag_order
|
||||
zstyle -a ":completion:*:*:\$service:*" tag-order tag_order || \
|
||||
zstyle ":completion:*:*:\$service:*" tag-order '!urls'
|
||||
|
||||
local rc=1
|
||||
|
||||
_arguments -C -S \\
|
||||
$opts_str
|
||||
'*:files:->mfiles' && rc=0
|
||||
|
||||
case \$state in
|
||||
ao)
|
||||
_values -s , 'audio outputs' \\
|
||||
$ao_str
|
||||
&& rc=0
|
||||
;;
|
||||
|
||||
vo)
|
||||
_values -s , 'video outputs' \\
|
||||
$vo_str
|
||||
&& rc=0
|
||||
;;
|
||||
|
||||
af)
|
||||
_values -s , 'audio filters' \\
|
||||
$af_str
|
||||
&& rc=0
|
||||
;;
|
||||
|
||||
vf)
|
||||
_values -s , 'video filters' \\
|
||||
$vf_str
|
||||
&& rc=0
|
||||
;;
|
||||
|
||||
$runtime_completions
|
||||
|
||||
files)
|
||||
compset -P '*,'
|
||||
compset -S ',*'
|
||||
_files -r ',/ \\t\\n\\-' && rc=0
|
||||
;;
|
||||
|
||||
mfiles)
|
||||
local expl
|
||||
_tags files urls
|
||||
while _tags; do
|
||||
_requested files expl 'media file' _files && rc=0
|
||||
if _requested urls; then
|
||||
while _next_label urls expl URL; do
|
||||
_urls "\$expl[@]" && rc=0
|
||||
compadd -S '' "\$expl[@]" $protos_str && rc=0
|
||||
done
|
||||
fi
|
||||
(( rc )) || return 0
|
||||
done
|
||||
;;
|
||||
esac
|
||||
|
||||
return rc
|
||||
EOS
|
||||
|
||||
print $tmpl;
|
||||
|
||||
sub parse_main_opts {
|
||||
my ($cmd, $regex) = @_;
|
||||
|
||||
my @list;
|
||||
my @lines = call_mpv($cmd);
|
||||
|
||||
foreach my $line (@lines) {
|
||||
my ($name, $desc) = ($line =~ /^$regex/) or next;
|
||||
|
||||
next if ($desc eq 'removed' || $desc eq 'alias');
|
||||
|
||||
if ($desc =~ /^Flag/) {
|
||||
|
||||
push @list, $name;
|
||||
|
||||
$name =~ /^--(.*)/;
|
||||
if ($1 !~ /^(\{|\}|v|list-options|really-quiet|no-.*)$/) {
|
||||
push @list, "--no-$1";
|
||||
}
|
||||
|
||||
} elsif ($desc =~ /^Print/) {
|
||||
|
||||
push @list, $name;
|
||||
|
||||
} else {
|
||||
|
||||
# Option takes argument
|
||||
|
||||
my $entry = $name;
|
||||
|
||||
$desc =~ s/\:/\\:/g;
|
||||
$entry .= "=-:$desc:";
|
||||
|
||||
if ($desc =~ /^Choices\\: ([^(]*)/) {
|
||||
my $choices = $1;
|
||||
$choices =~ s/ +$//; # strip trailing space
|
||||
$entry .= "($choices)";
|
||||
|
||||
# If "no" is one of the choices, it can also be
|
||||
# negated like a flag (--no-whatever).
|
||||
if ($choices =~ /\bno\b/) {
|
||||
$name =~ s/^--/--no-/;
|
||||
push @list, $name;
|
||||
}
|
||||
} elsif ($line =~ /\[file\]/) {
|
||||
$entry .= '->files';
|
||||
} elsif ($name =~ /^--(ao|vo|af|vf|profile|show-profile|audio-device)$/) {
|
||||
$entry .= "->$1";
|
||||
}
|
||||
push @list, $entry;
|
||||
}
|
||||
}
|
||||
|
||||
# Sort longest first, because zsh won't complete an option listed
|
||||
# after one that's a prefix of it.
|
||||
@list = sort {
|
||||
$a =~ /([^=]*)/; my $ma = $1;
|
||||
$b =~ /([^=]*)/; my $mb = $1;
|
||||
|
||||
length($mb) <=> length($ma)
|
||||
} @list;
|
||||
|
||||
return @list;
|
||||
}
|
||||
|
||||
sub parse_opts {
|
||||
my ($cmd, $regex) = @_;
|
||||
|
||||
my @list;
|
||||
my @lines = call_mpv($cmd);
|
||||
|
||||
foreach my $line (@lines) {
|
||||
if ($line !~ /^$regex/) {
|
||||
next;
|
||||
}
|
||||
|
||||
my $entry = $1;
|
||||
|
||||
if (defined $2) {
|
||||
my $desc = $2;
|
||||
$desc =~ s/\:/\\:/g;
|
||||
$entry .= "[$desc]";
|
||||
}
|
||||
|
||||
push @list, $entry
|
||||
}
|
||||
|
||||
return @list;
|
||||
}
|
||||
|
||||
sub call_mpv {
|
||||
my ($cmd) = @_;
|
||||
my $output = `"$mpv" --no-config $cmd`;
|
||||
if ($? == -1) {
|
||||
die "Could not run mpv: $!";
|
||||
} elsif ((my $exit_code = $? >> 8) != 0) {
|
||||
die "mpv returned $exit_code with output:\n$output";
|
||||
}
|
||||
return split /\n/, $output;
|
||||
}
|
||||
@@ -15,12 +15,6 @@ shallow_clone: true
|
||||
test: off
|
||||
|
||||
install:
|
||||
# Support for Ada and Objective-C was removed from MSYS2. GCC won't update if
|
||||
# these packages are installed.
|
||||
- >-
|
||||
C:\msys64\usr\bin\pacman -R --noconfirm --noprogressbar
|
||||
mingw-w64-i686-gcc-ada mingw-w64-i686-gcc-objc mingw-w64-x86_64-gcc-ada
|
||||
mingw-w64-x86_64-gcc-objc
|
||||
# Update core packages
|
||||
- C:\msys64\usr\bin\pacman -Syyuu --noconfirm --noprogressbar --ask=20
|
||||
# Update non-core packages
|
||||
|
||||
@@ -520,56 +520,6 @@ bool mp_aframe_set_silence(struct mp_aframe *f, int offset, int samples)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mp_aframe_reverse(struct mp_aframe *f)
|
||||
{
|
||||
int format = mp_aframe_get_format(f);
|
||||
size_t bps = af_fmt_to_bytes(format);
|
||||
if (!af_fmt_is_pcm(format) || bps > 16)
|
||||
return false;
|
||||
|
||||
uint8_t **d = mp_aframe_get_data_rw(f);
|
||||
if (!d)
|
||||
return false;
|
||||
|
||||
int planes = mp_aframe_get_planes(f);
|
||||
int samples = mp_aframe_get_size(f);
|
||||
int channels = mp_aframe_get_channels(f);
|
||||
size_t sstride = mp_aframe_get_sstride(f);
|
||||
|
||||
int plane_samples = channels;
|
||||
if (af_fmt_is_planar(format))
|
||||
plane_samples = 1;
|
||||
|
||||
for (int p = 0; p < planes; p++) {
|
||||
for (int n = 0; n < samples / 2; n++) {
|
||||
int s1_offset = n * sstride;
|
||||
int s2_offset = (samples - 1 - n) * sstride;
|
||||
for (int c = 0; c < plane_samples; c++) {
|
||||
// Nobody said it'd be fast.
|
||||
char tmp[16];
|
||||
uint8_t *s1 = d[p] + s1_offset + c * bps;
|
||||
uint8_t *s2 = d[p] + s2_offset + c * bps;
|
||||
memcpy(tmp, s2, bps);
|
||||
memcpy(s2, s1, bps);
|
||||
memcpy(s1, tmp, bps);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int mp_aframe_approx_byte_size(struct mp_aframe *frame)
|
||||
{
|
||||
// God damn, AVFrame is too fucking annoying. Just go with the size that
|
||||
// allocating a new frame would use.
|
||||
int planes = mp_aframe_get_planes(frame);
|
||||
size_t sstride = mp_aframe_get_sstride(frame);
|
||||
int samples = frame->av_frame->nb_samples;
|
||||
int plane_size = MP_ALIGN_UP(sstride * MPMAX(samples, 1), 32);
|
||||
return plane_size * planes + sizeof(*frame);
|
||||
}
|
||||
|
||||
struct mp_aframe_pool {
|
||||
AVBufferPool *avpool;
|
||||
int element_size;
|
||||
|
||||
@@ -51,10 +51,6 @@ int mp_aframe_get_planes(struct mp_aframe *frame);
|
||||
int mp_aframe_get_total_plane_samples(struct mp_aframe *frame);
|
||||
size_t mp_aframe_get_sstride(struct mp_aframe *frame);
|
||||
|
||||
bool mp_aframe_reverse(struct mp_aframe *frame);
|
||||
|
||||
int mp_aframe_approx_byte_size(struct mp_aframe *frame);
|
||||
|
||||
char *mp_aframe_format_str_buf(char *buf, size_t buf_size, struct mp_aframe *fmt);
|
||||
#define mp_aframe_format_str(fmt) mp_aframe_format_str_buf((char[32]){0}, 32, (fmt))
|
||||
|
||||
|
||||
@@ -87,9 +87,6 @@ static void copy_planes(struct mp_audio_buffer *ab,
|
||||
uint8_t **dst, int dst_offset,
|
||||
uint8_t **src, int src_offset, int length)
|
||||
{
|
||||
if (!length)
|
||||
return;
|
||||
|
||||
for (int n = 0; n < ab->num_planes; n++) {
|
||||
memmove((char *)dst[n] + dst_offset * ab->sstride,
|
||||
(char *)src[n] + src_offset * ab->sstride,
|
||||
|
||||
+4
-12
@@ -37,7 +37,6 @@
|
||||
#include "demux/stheader.h"
|
||||
#include "filters/f_decoder_wrapper.h"
|
||||
#include "filters/filter_internal.h"
|
||||
#include "options/m_config.h"
|
||||
#include "options/options.h"
|
||||
|
||||
struct priv {
|
||||
@@ -81,9 +80,8 @@ static bool init(struct mp_filter *da, struct mp_codec_params *codec,
|
||||
const char *decoder)
|
||||
{
|
||||
struct priv *ctx = da->priv;
|
||||
struct MPOpts *mpopts = mp_get_config_group(ctx, da->global, GLOBAL_CONFIG);
|
||||
struct ad_lavc_params *opts =
|
||||
mp_get_config_group(ctx, da->global, &ad_lavc_conf);
|
||||
struct MPOpts *mpopts = da->global->opts;
|
||||
struct ad_lavc_params *opts = mpopts->ad_lavc_params;
|
||||
AVCodecContext *lavc_context;
|
||||
AVCodec *lavc_codec;
|
||||
|
||||
@@ -214,10 +212,8 @@ static bool receive_frame(struct mp_filter *da, struct mp_frame *out)
|
||||
double out_pts = mp_pts_from_av(priv->avframe->pts, &priv->codec_timebase);
|
||||
|
||||
struct mp_aframe *mpframe = mp_aframe_from_avframe(priv->avframe);
|
||||
if (!mpframe) {
|
||||
MP_ERR(da, "Converting libavcodec frame to mpv frame failed.\n");
|
||||
if (!mpframe)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (priv->force_channel_map.num)
|
||||
mp_aframe_set_chmap(mpframe, &priv->force_channel_map);
|
||||
@@ -256,11 +252,7 @@ static bool receive_frame(struct mp_filter *da, struct mp_frame *out)
|
||||
priv->trim_samples -= trim;
|
||||
}
|
||||
|
||||
if (mp_aframe_get_size(mpframe) > 0) {
|
||||
*out = MAKE_FRAME(MP_FRAME_AUDIO, mpframe);
|
||||
} else {
|
||||
talloc_free(mpframe);
|
||||
}
|
||||
*out = MAKE_FRAME(MP_FRAME_AUDIO, mpframe);
|
||||
|
||||
av_frame_unref(priv->avframe);
|
||||
|
||||
|
||||
+3
-11
@@ -208,21 +208,13 @@ static int init_filter(struct mp_filter *da, AVPacket *pkt)
|
||||
break;
|
||||
case AV_CODEC_ID_DTS: {
|
||||
bool is_hd = profile == FF_PROFILE_DTS_HD_HRA ||
|
||||
profile == FF_PROFILE_DTS_HD_MA ||
|
||||
profile == FF_PROFILE_DTS_HD_MA ||
|
||||
profile == FF_PROFILE_UNKNOWN;
|
||||
|
||||
// Apparently, DTS-HD over SPDIF is specified to be 7.1 (8 channels)
|
||||
// for DTS-HD MA, and stereo (2 channels) for DTS-HD HRA. The bit
|
||||
// streaming rate as well as the signaled channel count are defined
|
||||
// based on this value.
|
||||
int dts_hd_spdif_channel_count = profile == FF_PROFILE_DTS_HD_HRA ?
|
||||
2 : 8;
|
||||
if (spdif_ctx->use_dts_hd && is_hd) {
|
||||
av_dict_set_int(&format_opts, "dtshd_rate",
|
||||
dts_hd_spdif_channel_count * 96000, 0);
|
||||
av_dict_set(&format_opts, "dtshd_rate", "768000", 0); // 4*192000
|
||||
sample_format = AF_FORMAT_S_DTSHD;
|
||||
samplerate = 192000;
|
||||
num_channels = dts_hd_spdif_channel_count;
|
||||
num_channels = 2*4;
|
||||
} else {
|
||||
sample_format = AF_FORMAT_S_DTS;
|
||||
samplerate = 48000;
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright (c) 2004 Michael Niedermayer <michaelni@gmx.at>
|
||||
* Copyright (c) 2013 Stefano Pigozzi <stefano.pigozzi@gmail.com>
|
||||
*
|
||||
* Based on Michael Niedermayer's lavcresample.
|
||||
*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
#include <math.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "config.h"
|
||||
|
||||
#include "common/av_common.h"
|
||||
#include "common/msg.h"
|
||||
#include "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"
|
||||
|
||||
struct af_resample {
|
||||
int allow_detach;
|
||||
struct mp_resample_opts opts;
|
||||
int global_normalize;
|
||||
};
|
||||
|
||||
static void set_defaults(struct mpv_global *global, void *p)
|
||||
{
|
||||
struct af_resample *s = p;
|
||||
|
||||
struct mp_resample_opts *opts = &s->opts;
|
||||
|
||||
struct mp_resample_opts *src_opts =
|
||||
mp_get_config_group(s, global, &resample_conf);
|
||||
|
||||
s->global_normalize = src_opts->normalize;
|
||||
|
||||
assert(!opts->avopts); // we don't set a default value, so it must be NULL
|
||||
|
||||
*opts = *src_opts;
|
||||
|
||||
opts->avopts = NULL;
|
||||
struct m_option dummy = {.type = &m_option_type_keyvalue_list};
|
||||
m_option_copy(&dummy, &opts->avopts, &src_opts->avopts);
|
||||
}
|
||||
|
||||
#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,
|
||||
},
|
||||
.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,
|
||||
},
|
||||
.create = af_lavrresample_create,
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2002 Anders Johansson ajh@atri.curtin.edu.au
|
||||
*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MPLAYER_EQUALIZER_H
|
||||
#define MPLAYER_EQUALIZER_H
|
||||
|
||||
/* Equalizer plugin header file defines struct used for setting or
|
||||
getting the gain of a specific channel and frequency */
|
||||
|
||||
typedef struct equalizer_s
|
||||
{
|
||||
float gain; // Gain in dB -15 - 15
|
||||
int channel; // Channel number 0 - 5
|
||||
int band; // Frequency band 0 - 9
|
||||
}equalizer_t;
|
||||
|
||||
/* The different frequency bands are:
|
||||
nr. center frequency
|
||||
0 31.25 Hz
|
||||
1 62.50 Hz
|
||||
2 125.0 Hz
|
||||
3 250.0 Hz
|
||||
4 500.0 Hz
|
||||
5 1.000 kHz
|
||||
6 2.000 kHz
|
||||
7 4.000 kHz
|
||||
8 8.000 kHz
|
||||
9 16.00 kHz
|
||||
*/
|
||||
|
||||
#endif /* MPLAYER_EQUALIZER_H */
|
||||
@@ -27,14 +27,12 @@ static const struct {
|
||||
{AV_SAMPLE_FMT_U8, AF_FORMAT_U8},
|
||||
{AV_SAMPLE_FMT_S16, AF_FORMAT_S16},
|
||||
{AV_SAMPLE_FMT_S32, AF_FORMAT_S32},
|
||||
{AV_SAMPLE_FMT_S64, AF_FORMAT_S64},
|
||||
{AV_SAMPLE_FMT_FLT, AF_FORMAT_FLOAT},
|
||||
{AV_SAMPLE_FMT_DBL, AF_FORMAT_DOUBLE},
|
||||
|
||||
{AV_SAMPLE_FMT_U8P, AF_FORMAT_U8P},
|
||||
{AV_SAMPLE_FMT_S16P, AF_FORMAT_S16P},
|
||||
{AV_SAMPLE_FMT_S32P, AF_FORMAT_S32P},
|
||||
{AV_SAMPLE_FMT_S64P, AF_FORMAT_S64P},
|
||||
{AV_SAMPLE_FMT_FLTP, AF_FORMAT_FLOATP},
|
||||
{AV_SAMPLE_FMT_DBLP, AF_FORMAT_DOUBLEP},
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ int af_fmt_to_bytes(int format)
|
||||
case AF_FORMAT_U8: return 1;
|
||||
case AF_FORMAT_S16: return 2;
|
||||
case AF_FORMAT_S32: return 4;
|
||||
case AF_FORMAT_S64: return 8;
|
||||
case AF_FORMAT_FLOAT: return 4;
|
||||
case AF_FORMAT_DOUBLE: return 8;
|
||||
}
|
||||
@@ -70,7 +69,6 @@ static const int planar_formats[][2] = {
|
||||
{AF_FORMAT_U8P, AF_FORMAT_U8},
|
||||
{AF_FORMAT_S16P, AF_FORMAT_S16},
|
||||
{AF_FORMAT_S32P, AF_FORMAT_S32},
|
||||
{AF_FORMAT_S64P, AF_FORMAT_S64},
|
||||
{AF_FORMAT_FLOATP, AF_FORMAT_FLOAT},
|
||||
{AF_FORMAT_DOUBLEP, AF_FORMAT_DOUBLE},
|
||||
};
|
||||
@@ -119,13 +117,11 @@ const char *af_fmt_to_str(int format)
|
||||
case AF_FORMAT_U8: return "u8";
|
||||
case AF_FORMAT_S16: return "s16";
|
||||
case AF_FORMAT_S32: return "s32";
|
||||
case AF_FORMAT_S64: return "s64";
|
||||
case AF_FORMAT_FLOAT: return "float";
|
||||
case AF_FORMAT_DOUBLE: return "double";
|
||||
case AF_FORMAT_U8P: return "u8p";
|
||||
case AF_FORMAT_S16P: return "s16p";
|
||||
case AF_FORMAT_S32P: return "s32p";
|
||||
case AF_FORMAT_S64P: return "s64p";
|
||||
case AF_FORMAT_FLOATP: return "floatp";
|
||||
case AF_FORMAT_DOUBLEP: return "doublep";
|
||||
case AF_FORMAT_S_AAC: return "spdif-aac";
|
||||
|
||||
@@ -27,7 +27,6 @@ enum af_format {
|
||||
AF_FORMAT_U8,
|
||||
AF_FORMAT_S16,
|
||||
AF_FORMAT_S32,
|
||||
AF_FORMAT_S64,
|
||||
AF_FORMAT_FLOAT,
|
||||
AF_FORMAT_DOUBLE,
|
||||
|
||||
@@ -35,7 +34,6 @@ enum af_format {
|
||||
AF_FORMAT_U8P,
|
||||
AF_FORMAT_S16P,
|
||||
AF_FORMAT_S32P,
|
||||
AF_FORMAT_S64P,
|
||||
AF_FORMAT_FLOATP,
|
||||
AF_FORMAT_DOUBLEP,
|
||||
|
||||
|
||||
+4
-37
@@ -82,7 +82,7 @@ static const struct ao_driver * const audio_out_drivers[] = {
|
||||
#if HAVE_OPENSLES
|
||||
&audio_out_opensles,
|
||||
#endif
|
||||
#if HAVE_SDL2_AUDIO
|
||||
#if HAVE_SDL2
|
||||
&audio_out_sdl,
|
||||
#endif
|
||||
#if HAVE_SNDIO
|
||||
@@ -120,7 +120,7 @@ static bool get_desc(struct m_obj_desc *dst, int index)
|
||||
}
|
||||
|
||||
// For the ao option
|
||||
static const struct m_obj_list ao_obj_list = {
|
||||
const struct m_obj_list ao_obj_list = {
|
||||
.get_desc = get_desc,
|
||||
.description = "audio outputs",
|
||||
.allow_unknown_entries = true,
|
||||
@@ -129,30 +129,13 @@ static const struct m_obj_list ao_obj_list = {
|
||||
.use_global_options = true,
|
||||
};
|
||||
|
||||
#define OPT_BASE_STRUCT struct ao_opts
|
||||
const struct m_sub_options ao_conf = {
|
||||
.opts = (const struct m_option[]) {
|
||||
OPT_SETTINGSLIST("ao", audio_driver_list, 0, &ao_obj_list, ),
|
||||
OPT_STRING("audio-device", audio_device, UPDATE_AUDIO),
|
||||
OPT_STRING("audio-client-name", audio_client_name, UPDATE_AUDIO),
|
||||
OPT_DOUBLE("audio-buffer", audio_buffer, M_OPT_MIN | M_OPT_MAX,
|
||||
.min = 0, .max = 10),
|
||||
{0}
|
||||
},
|
||||
.size = sizeof(OPT_BASE_STRUCT),
|
||||
.defaults = &(const OPT_BASE_STRUCT){
|
||||
.audio_buffer = 0.2,
|
||||
.audio_device = "auto",
|
||||
.audio_client_name = "mpv",
|
||||
},
|
||||
};
|
||||
|
||||
static struct ao *ao_alloc(bool probing, struct mpv_global *global,
|
||||
void (*wakeup_cb)(void *ctx), void *wakeup_ctx,
|
||||
char *name)
|
||||
{
|
||||
assert(wakeup_cb);
|
||||
|
||||
struct MPOpts *opts = global->opts;
|
||||
struct mp_log *log = mp_log_new(NULL, global->log, "ao");
|
||||
struct m_obj_desc desc;
|
||||
if (!m_obj_list_find(&desc, &ao_obj_list, bstr0(name))) {
|
||||
@@ -160,7 +143,6 @@ static struct ao *ao_alloc(bool probing, struct mpv_global *global,
|
||||
talloc_free(log);
|
||||
return NULL;
|
||||
};
|
||||
struct ao_opts *opts = mp_get_config_group(NULL, global, &ao_conf);
|
||||
struct ao *ao = talloc_ptrtype(NULL, ao);
|
||||
talloc_steal(ao, log);
|
||||
*ao = (struct ao) {
|
||||
@@ -173,7 +155,6 @@ static struct ao *ao_alloc(bool probing, struct mpv_global *global,
|
||||
.def_buffer = opts->audio_buffer,
|
||||
.client_name = talloc_strdup(ao, opts->audio_client_name),
|
||||
};
|
||||
talloc_free(opts);
|
||||
ao->priv = m_config_group_from_desc(ao, ao->log, global, &desc, name);
|
||||
if (!ao->priv)
|
||||
goto error;
|
||||
@@ -286,8 +267,8 @@ struct ao *ao_init_best(struct mpv_global *global,
|
||||
struct encode_lavc_context *encode_lavc_ctx,
|
||||
int samplerate, int format, struct mp_chmap channels)
|
||||
{
|
||||
struct MPOpts *opts = global->opts;
|
||||
void *tmp = talloc_new(NULL);
|
||||
struct ao_opts *opts = mp_get_config_group(tmp, global, &ao_conf);
|
||||
struct mp_log *log = mp_log_new(tmp, global->log, "ao");
|
||||
struct ao *ao = NULL;
|
||||
struct m_obj_settings *ao_list = NULL;
|
||||
@@ -402,7 +383,6 @@ void ao_reset(struct ao *ao)
|
||||
{
|
||||
if (ao->api->reset)
|
||||
ao->api->reset(ao);
|
||||
atomic_fetch_and(&ao->events_, ~(unsigned int)AO_EVENT_UNDERRUN);
|
||||
}
|
||||
|
||||
// Pause playback. Keep the current buffer. ao_get_delay() must return the
|
||||
@@ -458,14 +438,6 @@ void ao_hotplug_event(struct ao *ao)
|
||||
ao_add_events(ao, AO_EVENT_HOTPLUG);
|
||||
}
|
||||
|
||||
void ao_underrun_event(struct ao *ao)
|
||||
{
|
||||
// Racy check, but it's just for the message.
|
||||
if (!(atomic_load(&ao->events_) & AO_EVENT_UNDERRUN))
|
||||
MP_WARN(ao, "Device underrun detected.\n");
|
||||
ao_add_events(ao, AO_EVENT_UNDERRUN);
|
||||
}
|
||||
|
||||
bool ao_chmap_sel_adjust(struct ao *ao, const struct mp_chmap_sel *s,
|
||||
struct mp_chmap *map)
|
||||
{
|
||||
@@ -526,11 +498,6 @@ const char *ao_get_description(struct ao *ao)
|
||||
return ao->driver->description;
|
||||
}
|
||||
|
||||
bool ao_get_reports_underruns(struct ao *ao)
|
||||
{
|
||||
return ao->driver->reports_underruns;
|
||||
}
|
||||
|
||||
bool ao_untimed(struct ao *ao)
|
||||
{
|
||||
return ao->untimed;
|
||||
|
||||
+4
-9
@@ -35,6 +35,10 @@ enum aocontrol {
|
||||
AOCONTROL_SET_MUTE,
|
||||
// Has char* as argument, which contains the desired stream title.
|
||||
AOCONTROL_UPDATE_STREAM_TITLE,
|
||||
// the AO does the equivalent of af_volume (return CONTROL_TRUE if yes)
|
||||
AOCONTROL_HAS_SOFT_VOLUME,
|
||||
// like above, but volume persists (per app), mpv won't restore volume
|
||||
AOCONTROL_HAS_PER_APP_VOLUME,
|
||||
};
|
||||
|
||||
// If set, then the queued audio data is the last. Note that after a while, new
|
||||
@@ -45,7 +49,6 @@ enum {
|
||||
AO_EVENT_RELOAD = 1,
|
||||
AO_EVENT_HOTPLUG = 2,
|
||||
AO_EVENT_INITIAL_UNBLOCK = 4,
|
||||
AO_EVENT_UNDERRUN = 8,
|
||||
};
|
||||
|
||||
enum {
|
||||
@@ -80,13 +83,6 @@ struct mpv_global;
|
||||
struct input_ctx;
|
||||
struct encode_lavc_context;
|
||||
|
||||
struct ao_opts {
|
||||
struct m_obj_settings *audio_driver_list;
|
||||
char *audio_device;
|
||||
char *audio_client_name;
|
||||
double audio_buffer;
|
||||
};
|
||||
|
||||
struct ao *ao_init_best(struct mpv_global *global,
|
||||
int init_flags,
|
||||
void (*wakeup_cb)(void *ctx), void *wakeup_ctx,
|
||||
@@ -97,7 +93,6 @@ void ao_get_format(struct ao *ao,
|
||||
int *samplerate, int *format, struct mp_chmap *channels);
|
||||
const char *ao_get_name(struct ao *ao);
|
||||
const char *ao_get_description(struct ao *ao);
|
||||
bool ao_get_reports_underruns(struct ao *ao);
|
||||
bool ao_untimed(struct ao *ao);
|
||||
int ao_play(struct ao *ao, void **data, int samples, int flags);
|
||||
int ao_control(struct ao *ao, enum aocontrol cmd, void *arg);
|
||||
|
||||
+18
-33
@@ -94,7 +94,6 @@ struct priv {
|
||||
snd_pcm_format_t alsa_fmt;
|
||||
bool can_pause;
|
||||
bool paused;
|
||||
bool final_chunk_written;
|
||||
snd_pcm_sframes_t prepause_frames;
|
||||
double delay_before_pause;
|
||||
snd_pcm_uframes_t buffersize;
|
||||
@@ -136,19 +135,6 @@ static bool check_device_present(struct ao *ao, int alsa_err)
|
||||
return false;
|
||||
}
|
||||
|
||||
static void handle_underrun(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
if (!p->final_chunk_written) {
|
||||
ao_underrun_event(ao);
|
||||
|
||||
int err = snd_pcm_prepare(p->alsa);
|
||||
CHECK_ALSA_ERROR("pcm prepare error");
|
||||
alsa_error: ;
|
||||
}
|
||||
}
|
||||
|
||||
static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
@@ -960,7 +946,14 @@ static int get_space(struct ao *ao)
|
||||
return p->buffersize;
|
||||
|
||||
snd_pcm_sframes_t space = snd_pcm_avail(p->alsa);
|
||||
if (space < 0 && space != -EPIPE) {
|
||||
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),
|
||||
@@ -971,10 +964,8 @@ static int get_space(struct ao *ao)
|
||||
check_device_present(ao, space);
|
||||
goto alsa_error;
|
||||
}
|
||||
if (space == -EPIPE)
|
||||
handle_underrun(ao);
|
||||
|
||||
if (space > p->buffersize || space < 0) // Buffer underrun?
|
||||
if (space > p->buffersize) // Buffer underrun?
|
||||
space = p->buffersize;
|
||||
return space / p->outburst * p->outburst;
|
||||
|
||||
@@ -991,12 +982,8 @@ static double get_delay(struct ao *ao)
|
||||
if (p->paused)
|
||||
return p->delay_before_pause;
|
||||
|
||||
int err = snd_pcm_delay(p->alsa, &delay);
|
||||
if (err < 0) {
|
||||
if (err == -EPIPE)
|
||||
handle_underrun(ao);
|
||||
if (snd_pcm_delay(p->alsa, &delay) < 0)
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (delay < 0) {
|
||||
/* underrun - move the application pointer forward to catch up */
|
||||
@@ -1102,7 +1089,6 @@ static void reset(struct ao *ao)
|
||||
p->paused = false;
|
||||
p->prepause_frames = 0;
|
||||
p->delay_before_pause = 0;
|
||||
p->final_chunk_written = false;
|
||||
|
||||
if (ao->stream_silence) {
|
||||
soft_reset(ao);
|
||||
@@ -1120,13 +1106,11 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
snd_pcm_sframes_t res = 0;
|
||||
bool final_chunk = flags & AOPLAY_FINAL_CHUNK;
|
||||
|
||||
if (!final_chunk)
|
||||
if (!(flags & AOPLAY_FINAL_CHUNK))
|
||||
samples = samples / p->outburst * p->outburst;
|
||||
|
||||
if (samples == 0)
|
||||
goto done;
|
||||
return 0;
|
||||
ao_convert_inplace(&p->convert, data, samples);
|
||||
|
||||
do {
|
||||
@@ -1144,11 +1128,15 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
if (res == -ESTRPIPE) { /* suspend */
|
||||
resume_device(ao);
|
||||
} else if (res == -EPIPE) {
|
||||
handle_underrun(ao);
|
||||
// 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));
|
||||
}
|
||||
int err = snd_pcm_prepare(p->alsa);
|
||||
res = snd_pcm_prepare(p->alsa);
|
||||
int err = res;
|
||||
CHECK_ALSA_ERROR("pcm prepare error");
|
||||
res = 0;
|
||||
}
|
||||
@@ -1156,8 +1144,6 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
|
||||
p->paused = false;
|
||||
|
||||
done:
|
||||
p->final_chunk_written = res == samples && final_chunk;
|
||||
return res < 0 ? -1 : res;
|
||||
|
||||
alsa_error:
|
||||
@@ -1264,7 +1250,6 @@ const struct ao_driver audio_out_alsa = {
|
||||
.wait = audio_wait,
|
||||
.wakeup = ao_wakeup_poll,
|
||||
.list_devs = list_devs,
|
||||
.reports_underruns = true,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.global_opts = &ao_alsa_conf,
|
||||
};
|
||||
@@ -49,8 +49,8 @@ static OSStatus render_cb_lpcm(void *ctx, AudioUnitRenderActionFlags *aflags,
|
||||
planes[n] = buffer_list->mBuffers[n].mData;
|
||||
|
||||
int64_t end = mp_time_us();
|
||||
end += ca_frames_to_us(ao, frames);
|
||||
end += p->device_latency * 1e6;
|
||||
end += ca_get_latency(ts) + ca_frames_to_us(ao, frames);
|
||||
ao_read_data(ao, planes, frames, end);
|
||||
return noErr;
|
||||
}
|
||||
@@ -155,7 +155,7 @@ static void start(struct ao *ao)
|
||||
struct priv *p = ao->priv;
|
||||
AVAudioSession *instance = AVAudioSession.sharedInstance;
|
||||
|
||||
p->device_latency = [instance outputLatency] + [instance IOBufferDuration];
|
||||
p->device_latency = [instance outputLatency];
|
||||
|
||||
OSStatus err = AudioOutputUnitStart(p->audio_unit);
|
||||
CHECK_CA_WARN("can't start audio unit");
|
||||
@@ -192,7 +192,7 @@ const struct ao_driver audio_out_audiounit = {
|
||||
.name = "audiounit",
|
||||
.uninit = uninit,
|
||||
.init = init,
|
||||
.reset = stop,
|
||||
.pause = stop,
|
||||
.resume = start,
|
||||
.priv_size = sizeof(struct priv),
|
||||
};
|
||||
@@ -117,6 +117,8 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
return get_volume(ao, arg);
|
||||
case AOCONTROL_SET_VOLUME:
|
||||
return set_volume(ao, arg);
|
||||
case AOCONTROL_HAS_SOFT_VOLUME:
|
||||
return CONTROL_TRUE;
|
||||
}
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
|
||||
@@ -31,8 +31,6 @@
|
||||
#if HAVE_COREAUDIO
|
||||
#include "audio/out/ao_coreaudio_properties.h"
|
||||
#include <CoreAudio/HostTime.h>
|
||||
#else
|
||||
#include <mach/mach_time.h>
|
||||
#endif
|
||||
|
||||
CFStringRef cfstr_from_cstr(char *str)
|
||||
@@ -312,9 +310,9 @@ int64_t ca_frames_to_us(struct ao *ao, uint32_t frames)
|
||||
return frames / (float) ao->samplerate * 1e6;
|
||||
}
|
||||
|
||||
#if HAVE_COREAUDIO
|
||||
int64_t ca_get_latency(const AudioTimeStamp *ts)
|
||||
{
|
||||
#if HAVE_COREAUDIO
|
||||
uint64_t out = AudioConvertHostTimeToNanos(ts->mHostTime);
|
||||
uint64_t now = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());
|
||||
|
||||
@@ -322,22 +320,8 @@ int64_t ca_get_latency(const AudioTimeStamp *ts)
|
||||
return 0;
|
||||
|
||||
return (out - now) * 1e-3;
|
||||
#else
|
||||
static mach_timebase_info_data_t timebase;
|
||||
if (timebase.denom == 0)
|
||||
mach_timebase_info(&timebase);
|
||||
|
||||
uint64_t out = ts->mHostTime;
|
||||
uint64_t now = mach_absolute_time();
|
||||
|
||||
if (now > out)
|
||||
return 0;
|
||||
|
||||
return (out - now) * timebase.numer / timebase.denom / 1e3;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if HAVE_COREAUDIO
|
||||
bool ca_stream_supports_compressed(struct ao *ao, AudioStreamID stream)
|
||||
{
|
||||
AudioStreamRangedDescription *formats = NULL;
|
||||
|
||||
@@ -65,7 +65,9 @@ bool ca_asbd_is_better(AudioStreamBasicDescription *req,
|
||||
AudioStreamBasicDescription *new);
|
||||
|
||||
int64_t ca_frames_to_us(struct ao *ao, uint32_t frames);
|
||||
#if HAVE_COREAUDIO
|
||||
int64_t ca_get_latency(const AudioTimeStamp *ts);
|
||||
#endif
|
||||
|
||||
#if HAVE_COREAUDIO
|
||||
bool ca_stream_supports_compressed(struct ao *ao, AudioStreamID stream);
|
||||
|
||||
@@ -93,6 +93,8 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
return CONTROL_TRUE;
|
||||
}
|
||||
|
||||
case AOCONTROL_HAS_SOFT_VOLUME:
|
||||
return CONTROL_TRUE;
|
||||
}
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
|
||||
+45
-51
@@ -35,13 +35,18 @@ struct priv {
|
||||
SLBufferQueueItf buffer_queue;
|
||||
SLEngineItf engine;
|
||||
SLPlayItf play;
|
||||
void *buf;
|
||||
int bytes_per_enqueue;
|
||||
char *buf;
|
||||
size_t buffer_size;
|
||||
pthread_mutex_t buffer_lock;
|
||||
double audio_latency;
|
||||
|
||||
int frames_per_enqueue;
|
||||
int buffer_size_in_ms;
|
||||
int cfg_frames_per_buffer;
|
||||
};
|
||||
|
||||
static const int fmtmap[][2] = {
|
||||
{ AF_FORMAT_U8, SL_PCMSAMPLEFORMAT_FIXED_8 },
|
||||
{ AF_FORMAT_S16, SL_PCMSAMPLEFORMAT_FIXED_16 },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
#define DESTROY(thing) \
|
||||
@@ -66,6 +71,7 @@ static void uninit(struct ao *ao)
|
||||
|
||||
free(p->buf);
|
||||
p->buf = NULL;
|
||||
p->buffer_size = 0;
|
||||
}
|
||||
|
||||
#undef DESTROY
|
||||
@@ -75,22 +81,26 @@ static void buffer_callback(SLBufferQueueItf buffer_queue, void *context)
|
||||
struct ao *ao = context;
|
||||
struct priv *p = ao->priv;
|
||||
SLresult res;
|
||||
void *data[1];
|
||||
double delay;
|
||||
|
||||
pthread_mutex_lock(&p->buffer_lock);
|
||||
|
||||
delay = p->frames_per_enqueue / (double)ao->samplerate;
|
||||
data[0] = p->buf;
|
||||
delay = 2 * p->buffer_size / (double)ao->bps;
|
||||
delay += p->audio_latency;
|
||||
ao_read_data(ao, &p->buf, p->frames_per_enqueue,
|
||||
ao_read_data(ao, data, p->buffer_size / ao->sstride,
|
||||
mp_time_us() + 1000000LL * delay);
|
||||
|
||||
res = (*buffer_queue)->Enqueue(buffer_queue, p->buf, p->bytes_per_enqueue);
|
||||
res = (*buffer_queue)->Enqueue(buffer_queue, p->buf, p->buffer_size);
|
||||
if (res != SL_RESULT_SUCCESS)
|
||||
MP_ERR(ao, "Failed to Enqueue: %d\n", res);
|
||||
|
||||
pthread_mutex_unlock(&p->buffer_lock);
|
||||
}
|
||||
|
||||
#define DEFAULT_BUFFER_SIZE_MS 250
|
||||
|
||||
#define CHK(stmt) \
|
||||
{ \
|
||||
SLresult res = stmt; \
|
||||
@@ -105,7 +115,7 @@ static int init(struct ao *ao)
|
||||
struct priv *p = ao->priv;
|
||||
SLDataLocator_BufferQueue locator_buffer_queue;
|
||||
SLDataLocator_OutputMix locator_output_mix;
|
||||
SLAndroidDataFormat_PCM_EX pcm;
|
||||
SLDataFormat_PCM pcm;
|
||||
SLDataSource audio_source;
|
||||
SLDataSink audio_sink;
|
||||
|
||||
@@ -119,55 +129,43 @@ static int init(struct ao *ao)
|
||||
CHK((*p->output_mix)->Realize(p->output_mix, SL_BOOLEAN_FALSE));
|
||||
|
||||
locator_buffer_queue.locatorType = SL_DATALOCATOR_BUFFERQUEUE;
|
||||
locator_buffer_queue.numBuffers = 8;
|
||||
locator_buffer_queue.numBuffers = 1;
|
||||
|
||||
if (af_fmt_is_int(ao->format)) {
|
||||
// Be future-proof
|
||||
if (af_fmt_to_bytes(ao->format) > 2)
|
||||
ao->format = AF_FORMAT_S32;
|
||||
else
|
||||
ao->format = af_fmt_from_planar(ao->format);
|
||||
pcm.formatType = SL_DATAFORMAT_PCM;
|
||||
} else {
|
||||
ao->format = AF_FORMAT_FLOAT;
|
||||
pcm.formatType = SL_ANDROID_DATAFORMAT_PCM_EX;
|
||||
pcm.representation = SL_ANDROID_PCM_REPRESENTATION_FLOAT;
|
||||
pcm.formatType = SL_DATAFORMAT_PCM;
|
||||
pcm.numChannels = 2;
|
||||
|
||||
int compatible_formats[AF_FORMAT_COUNT + 1];
|
||||
af_get_best_sample_formats(ao->format, compatible_formats);
|
||||
pcm.bitsPerSample = 0;
|
||||
for (int i = 0; compatible_formats[i] && !pcm.bitsPerSample; ++i)
|
||||
for (int j = 0; fmtmap[j][0]; ++j)
|
||||
if (compatible_formats[i] == fmtmap[j][0]) {
|
||||
ao->format = fmtmap[j][0];
|
||||
pcm.bitsPerSample = fmtmap[j][1];
|
||||
break;
|
||||
}
|
||||
if (!pcm.bitsPerSample) {
|
||||
MP_ERR(ao, "Cannot find compatible audio format\n");
|
||||
goto error;
|
||||
}
|
||||
pcm.numChannels = ao->channels.num;
|
||||
pcm.containerSize = pcm.bitsPerSample = 8 * af_fmt_to_bytes(ao->format);
|
||||
pcm.containerSize = 8 * af_fmt_to_bytes(ao->format);
|
||||
pcm.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
|
||||
pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
|
||||
pcm.sampleRate = ao->samplerate * 1000;
|
||||
|
||||
if (p->buffer_size_in_ms) {
|
||||
ao->device_buffer = ao->samplerate * p->buffer_size_in_ms / 1000;
|
||||
// As the purpose of buffer_size_in_ms is to request a specific
|
||||
// soft buffer size:
|
||||
ao->def_buffer = 0;
|
||||
}
|
||||
// samplesPerSec is misnamed, actually it's samples per ms
|
||||
pcm.samplesPerSec = ao->samplerate * 1000;
|
||||
|
||||
// But it does not make sense if it is smaller than the enqueue size:
|
||||
if (p->frames_per_enqueue) {
|
||||
ao->device_buffer = MPMAX(ao->device_buffer, p->frames_per_enqueue);
|
||||
} else {
|
||||
if (ao->device_buffer) {
|
||||
p->frames_per_enqueue = ao->device_buffer;
|
||||
} else if (ao->def_buffer) {
|
||||
p->frames_per_enqueue = ao->def_buffer * ao->samplerate;
|
||||
} else {
|
||||
MP_ERR(ao, "Enqueue size is not set and can neither be derived\n");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
p->bytes_per_enqueue = p->frames_per_enqueue * ao->channels.num *
|
||||
if (p->cfg_frames_per_buffer)
|
||||
ao->device_buffer = p->cfg_frames_per_buffer;
|
||||
else
|
||||
ao->device_buffer = ao->samplerate * DEFAULT_BUFFER_SIZE_MS / 1000;
|
||||
p->buffer_size = ao->device_buffer * ao->channels.num *
|
||||
af_fmt_to_bytes(ao->format);
|
||||
p->buf = calloc(1, p->bytes_per_enqueue);
|
||||
p->buf = calloc(1, p->buffer_size);
|
||||
if (!p->buf) {
|
||||
MP_ERR(ao, "Failed to allocate device buffer\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
int r = pthread_mutex_init(&p->buffer_lock, NULL);
|
||||
if (r) {
|
||||
MP_ERR(ao, "Failed to initialize the mutex: %d\n", r);
|
||||
@@ -250,12 +248,8 @@ const struct ao_driver audio_out_opensles = {
|
||||
.resume = resume,
|
||||
|
||||
.priv_size = sizeof(struct priv),
|
||||
.priv_defaults = &(const struct priv) {
|
||||
.buffer_size_in_ms = 250,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_INTRANGE("frames-per-enqueue", frames_per_enqueue, 0, 1, 96000),
|
||||
OPT_INTRANGE("buffer-size-in-ms", buffer_size_in_ms, 0, 0, 500),
|
||||
OPT_INTRANGE("frames-per-buffer", cfg_frames_per_buffer, 0, 1, 96000),
|
||||
{0}
|
||||
},
|
||||
.options_prefix = "opensles",
|
||||
|
||||
+11
-11
@@ -204,6 +204,10 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
}
|
||||
return CONTROL_ERROR;
|
||||
}
|
||||
#ifdef SNDCTL_DSP_GETPLAYVOL
|
||||
case AOCONTROL_HAS_SOFT_VOLUME:
|
||||
return CONTROL_TRUE;
|
||||
#endif
|
||||
}
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
@@ -332,23 +336,19 @@ static int reopen_device(struct ao *ao, bool allow_format_changes)
|
||||
mp_chmap_sel_add_map(&sel, &oss_layouts[n]);
|
||||
if (!ao_chmap_sel_adjust(ao, &sel, &channels))
|
||||
goto fail;
|
||||
int c, nchannels, reqchannels;
|
||||
nchannels = reqchannels = channels.num;
|
||||
int reqchannels = channels.num;
|
||||
// We only use SNDCTL_DSP_CHANNELS for >2 channels, in case some drivers don't have it
|
||||
if (reqchannels > 2) {
|
||||
if (ioctl(p->audio_fd, SNDCTL_DSP_CHANNELS, &nchannels) == -1) {
|
||||
int nchannels = reqchannels;
|
||||
if (ioctl(p->audio_fd, SNDCTL_DSP_CHANNELS, &nchannels) == -1 ||
|
||||
nchannels != reqchannels)
|
||||
{
|
||||
MP_ERR(ao, "Failed to set audio device to %d channels.\n",
|
||||
reqchannels);
|
||||
goto fail;
|
||||
}
|
||||
if (nchannels != reqchannels) {
|
||||
// Fallback to stereo
|
||||
nchannels = 2;
|
||||
goto stereo;
|
||||
}
|
||||
} else {
|
||||
stereo:
|
||||
c = nchannels - 1;
|
||||
int c = reqchannels - 1;
|
||||
if (ioctl(p->audio_fd, SNDCTL_DSP_STEREO, &c) == -1) {
|
||||
MP_ERR(ao, "Failed to set audio device to %d channels.\n",
|
||||
reqchannels);
|
||||
@@ -649,7 +649,7 @@ const struct ao_driver audio_out_oss = {
|
||||
.oss_mixer_device = PATH_DEV_MIXER,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_STRING("mixer-device", oss_mixer_device, M_OPT_FILE),
|
||||
OPT_STRING("mixer-device", oss_mixer_device, 0),
|
||||
OPT_STRING("mixer-channel", cfg_oss_mixer_channel, 0),
|
||||
{0}
|
||||
},
|
||||
|
||||
+10
-14
@@ -61,7 +61,6 @@ struct priv {
|
||||
char *cfg_host;
|
||||
int cfg_buffer;
|
||||
int cfg_latency_hacks;
|
||||
int cfg_allow_suspended;
|
||||
};
|
||||
|
||||
#define GENERIC_ERR_MSG(str) \
|
||||
@@ -202,18 +201,13 @@ static const struct format_map {
|
||||
static pa_encoding_t map_digital_format(int format)
|
||||
{
|
||||
switch (format) {
|
||||
case AF_FORMAT_S_AC3: return PA_ENCODING_AC3_IEC61937;
|
||||
case AF_FORMAT_S_EAC3: return PA_ENCODING_EAC3_IEC61937;
|
||||
case AF_FORMAT_S_MP3: return PA_ENCODING_MPEG_IEC61937;
|
||||
case AF_FORMAT_S_DTS: return PA_ENCODING_DTS_IEC61937;
|
||||
#ifdef PA_ENCODING_DTSHD_IEC61937
|
||||
case AF_FORMAT_S_DTSHD: return PA_ENCODING_DTSHD_IEC61937;
|
||||
#endif
|
||||
case AF_FORMAT_S_AC3: return PA_ENCODING_AC3_IEC61937;
|
||||
case AF_FORMAT_S_EAC3: return PA_ENCODING_EAC3_IEC61937;
|
||||
case AF_FORMAT_S_MP3: return PA_ENCODING_MPEG_IEC61937;
|
||||
case AF_FORMAT_S_DTS:
|
||||
case AF_FORMAT_S_DTSHD: return PA_ENCODING_DTS_IEC61937;
|
||||
#ifdef PA_ENCODING_MPEG2_AAC_IEC61937
|
||||
case AF_FORMAT_S_AAC: return PA_ENCODING_MPEG2_AAC_IEC61937;
|
||||
#endif
|
||||
#ifdef PA_ENCODING_TRUEHD_IEC61937
|
||||
case AF_FORMAT_S_TRUEHD: return PA_ENCODING_TRUEHD_IEC61937;
|
||||
case AF_FORMAT_S_AAC: return PA_ENCODING_MPEG2_AAC_IEC61937;
|
||||
#endif
|
||||
default:
|
||||
if (af_fmt_is_spdif(format))
|
||||
@@ -488,7 +482,7 @@ static int init(struct ao *ao)
|
||||
pa_threaded_mainloop_wait(priv->mainloop);
|
||||
}
|
||||
|
||||
if (pa_stream_is_suspended(priv->stream) && !priv->cfg_allow_suspended) {
|
||||
if (pa_stream_is_suspended(priv->stream)) {
|
||||
MP_ERR(ao, "The stream is suspended. Bailing out.\n");
|
||||
goto unlock_and_fail;
|
||||
}
|
||||
@@ -749,6 +743,9 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
return CONTROL_OK;
|
||||
}
|
||||
|
||||
case AOCONTROL_HAS_PER_APP_VOLUME:
|
||||
return CONTROL_TRUE;
|
||||
|
||||
case AOCONTROL_UPDATE_STREAM_TITLE: {
|
||||
char *title = (char *)arg;
|
||||
pa_threaded_mainloop_lock(priv->mainloop);
|
||||
@@ -840,7 +837,6 @@ const struct ao_driver audio_out_pulse = {
|
||||
OPT_STRING("host", cfg_host, 0),
|
||||
OPT_CHOICE_OR_INT("buffer", cfg_buffer, 0, 1, 2000, ({"native", 0})),
|
||||
OPT_FLAG("latency-hacks", cfg_latency_hacks, 0),
|
||||
OPT_FLAG("allow-suspended", cfg_allow_suspended, 0),
|
||||
{0}
|
||||
},
|
||||
.options_prefix = "pulse",
|
||||
|
||||
@@ -205,7 +205,6 @@ const struct ao_driver audio_out_sdl = {
|
||||
.uninit = uninit,
|
||||
.reset = reset,
|
||||
.resume = resume,
|
||||
.reports_underruns = true,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.priv_defaults = &(const struct priv) {
|
||||
.buflen = 0, // use SDL default
|
||||
|
||||
@@ -61,6 +61,8 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
return CONTROL_FALSE;
|
||||
sio_setvol(p->hdl, vol->left * SIO_MAXVOL / 100);
|
||||
break;
|
||||
case AOCONTROL_HAS_SOFT_VOLUME:
|
||||
return CONTROL_TRUE;
|
||||
default:
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
|
||||
@@ -340,6 +340,8 @@ static int thread_control_exclusive(struct ao *ao, enum aocontrol cmd, void *arg
|
||||
if (!(state->vol_hw_support & ENDPOINT_HARDWARE_SUPPORT_MUTE))
|
||||
return CONTROL_FALSE;
|
||||
break;
|
||||
case AOCONTROL_HAS_PER_APP_VOLUME:
|
||||
return CONTROL_FALSE;
|
||||
}
|
||||
|
||||
float volume;
|
||||
@@ -398,6 +400,8 @@ static int thread_control_shared(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
mute = *(bool *)arg;
|
||||
ISimpleAudioVolume_SetMute(state->pAudioVolume, mute, NULL);
|
||||
return CONTROL_OK;
|
||||
case AOCONTROL_HAS_PER_APP_VOLUME:
|
||||
return CONTROL_TRUE;
|
||||
}
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ struct ao {
|
||||
char *redirect;
|
||||
|
||||
// Internal events (use ao_request_reload(), ao_hotplug_event())
|
||||
atomic_uint events_;
|
||||
atomic_int events_;
|
||||
|
||||
// Float gain multiplicator
|
||||
mp_atomic_float gain;
|
||||
@@ -134,11 +134,6 @@ struct ao_driver {
|
||||
// first play() call is done. Encode mode uses this, and push mode
|
||||
// respects it automatically (don't use with pull mode).
|
||||
bool initially_blocked;
|
||||
// Whether underruns are strictly _always_ reported via ao_underrun_event().
|
||||
// Do not set this to true if underruns may be missed in some way. If the
|
||||
// AO can't guarantee to play silence after underruns, it may be better not
|
||||
// to set this.
|
||||
bool reports_underruns;
|
||||
// Init the device using ao->format/ao->channels/ao->samplerate. If the
|
||||
// device doesn't accept these parameters, you can attempt to negotiate
|
||||
// fallback parameters, and set the ao format fields accordingly.
|
||||
@@ -211,7 +206,6 @@ struct pollfd;
|
||||
int ao_wait_poll(struct ao *ao, struct pollfd *fds, int num_fds,
|
||||
pthread_mutex_t *lock);
|
||||
void ao_wakeup_poll(struct ao *ao);
|
||||
void ao_underrun_event(struct ao *ao);
|
||||
|
||||
bool ao_chmap_sel_adjust(struct ao *ao, const struct mp_chmap_sel *s,
|
||||
struct mp_chmap *map);
|
||||
|
||||
+2
-7
@@ -153,13 +153,8 @@ int ao_read_data(struct ao *ao, void **data, int samples, int64_t out_time_us)
|
||||
int buffered_bytes = mp_ring_buffered(p->buffers[0]);
|
||||
bytes = MPMIN(buffered_bytes, full_bytes);
|
||||
|
||||
if (full_bytes > bytes && !atomic_load(&p->draining)) {
|
||||
if (ao->driver->reports_underruns) {
|
||||
ao_underrun_event(ao);
|
||||
} else {
|
||||
atomic_fetch_add(&p->underflow, (full_bytes - bytes) / ao->sstride);
|
||||
}
|
||||
}
|
||||
if (buffered_bytes < bytes && !atomic_load(&p->draining))
|
||||
atomic_fetch_add(&p->underflow, (bytes - buffered_bytes) / ao->sstride);
|
||||
|
||||
if (bytes > 0)
|
||||
atomic_store(&p->end_time_us, out_time_us);
|
||||
|
||||
@@ -16,10 +16,6 @@ if os.path.exists("waf"):
|
||||
print("Found 'waf', skipping download.")
|
||||
sys.exit(0)
|
||||
|
||||
if "--no-download" in sys.argv[1:]:
|
||||
print("Did not find {} and no download was requested.".format(WAFRELEASE))
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
from urllib.request import urlopen, URLError
|
||||
except:
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
FFMPEG_SYSROOT="${HOME}/deps/sysroot"
|
||||
MPV_INSTALL_PREFIX="${HOME}/out/mpv"
|
||||
MPV_VARIANT="${TRAVIS_OS_NAME}"
|
||||
|
||||
if [[ -d "./build/${MPV_VARIANT}" ]] ; then
|
||||
rm -rf "./build/${MPV_VARIANT}"
|
||||
fi
|
||||
|
||||
if [[ ! -e "./waf" ]] ; then
|
||||
python3 ./bootstrap.py
|
||||
fi
|
||||
|
||||
PKG_CONFIG_PATH="${FFMPEG_SYSROOT}/lib/pkgconfig/" CC="${CC}" CXX="${CXX}" python3 \
|
||||
./waf configure \
|
||||
--variant="${MPV_VARIANT}" \
|
||||
--prefix="${MPV_INSTALL_PREFIX}" \
|
||||
--enable-{gl,iconv,lcms2,libass,libass-osd,libmpv-shared,lua,jpeg,plain-gl,zlib} \
|
||||
--enable-{apple-remote,cocoa,coreaudio,gl-cocoa,macos-cocoa-cb,macos-touchbar,videotoolbox-gl}
|
||||
|
||||
python3 ./waf build --variant="${MPV_VARIANT}" -j4
|
||||
|
||||
python3 ./waf install --variant="${MPV_VARIANT}"
|
||||
@@ -24,6 +24,7 @@ python3 ./waf configure \
|
||||
--enable-libarchive \
|
||||
--enable-libass \
|
||||
--enable-libbluray \
|
||||
--enable-dvdread \
|
||||
--enable-dvdnav \
|
||||
--enable-uchardet \
|
||||
--enable-vulkan \
|
||||
|
||||
@@ -5,10 +5,12 @@ 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-vulkan \
|
||||
--enable-zsh-comp
|
||||
python3 ./waf build --verbose
|
||||
@@ -1,38 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
FFMPEG_SRC_DIR="${HOME}/deps/src/ffmpeg"
|
||||
FFMPEG_BUILD_DIR="${FFMPEG_SRC_DIR}/${TRAVIS_OS_NAME}"
|
||||
FFMPEG_SYSROOT="${HOME}/deps/sysroot"
|
||||
FFMPEG_HASH="18928e2bb4568cbe5e9061c3e6b63559392af3d2"
|
||||
|
||||
# Get the sauce if not around
|
||||
if [[ ! -d "${FFMPEG_SRC_DIR}" ]] ; then
|
||||
git clone "https://git.videolan.org/git/ffmpeg.git" "${FFMPEG_SRC_DIR}"
|
||||
fi
|
||||
|
||||
# pop into FFmpeg's source dir and clean up & check out our wanted revision
|
||||
pushd "${FFMPEG_SRC_DIR}"
|
||||
git reset --hard HEAD && git clean -dfx
|
||||
git checkout "${FFMPEG_HASH}"
|
||||
popd
|
||||
|
||||
# If a build dir of the same type is around, clean it up
|
||||
if [[ -d "${FFMPEG_BUILD_DIR}" ]] ; then
|
||||
rm -rf "${FFMPEG_BUILD_DIR}"
|
||||
fi
|
||||
|
||||
# Create and move into the build dir, configure and build!
|
||||
mkdir -p "${FFMPEG_BUILD_DIR}" && pushd "${FFMPEG_BUILD_DIR}"
|
||||
|
||||
PKG_CONFIG_PATH="${FFMPEG_SYSROOT}/lib/pkgconfig/" ../configure \
|
||||
--disable-{autodetect,stripping} \
|
||||
--cc="${CC}" \
|
||||
--cxx="${CXX}" \
|
||||
--prefix="${FFMPEG_SYSROOT}" \
|
||||
--enable-{zlib,securetransport,videotoolbox}
|
||||
|
||||
make -j4 && make install && popd
|
||||
|
||||
exit 0
|
||||
@@ -292,13 +292,6 @@ const char *mp_codec_from_av_codec_id(int codec_id)
|
||||
return name;
|
||||
}
|
||||
|
||||
bool mp_codec_is_lossless(const char *codec)
|
||||
{
|
||||
const AVCodecDescriptor *desc =
|
||||
avcodec_descriptor_get(mp_codec_to_av_codec_id(codec));
|
||||
return desc && (desc->props & AV_CODEC_PROP_LOSSLESS);
|
||||
}
|
||||
|
||||
// kv is in the format as by OPT_KEYVALUELIST(): kv[0]=key0, kv[1]=val0, ...
|
||||
// Copy them to the dict.
|
||||
void mp_set_avdict(AVDictionary **dict, char **kv)
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
#define MP_AVCOMMON_H
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <libavutil/avutil.h>
|
||||
#include <libavutil/rational.h>
|
||||
@@ -45,7 +44,6 @@ 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);
|
||||
bool mp_codec_is_lossless(const char *codec);
|
||||
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);
|
||||
|
||||
+2
-5
@@ -177,9 +177,7 @@ void uninit_libav(struct mpv_global *global)
|
||||
pthread_mutex_unlock(&log_lock);
|
||||
}
|
||||
|
||||
#define V(x) AV_VERSION_MAJOR(x), \
|
||||
AV_VERSION_MINOR(x), \
|
||||
AV_VERSION_MICRO(x)
|
||||
#define V(x) (x)>>16, (x)>>8 & 255, (x) & 255
|
||||
|
||||
struct lib {
|
||||
const char *name;
|
||||
@@ -210,8 +208,7 @@ bool print_libav_versions(struct mp_log *log, int v)
|
||||
mp_msg(log, v, " %-15s %d.%d.%d", l->name, V(l->buildv));
|
||||
if (l->buildv != l->runv) {
|
||||
mp_msg(log, v, " (runtime %d.%d.%d)", V(l->runv));
|
||||
mismatch = l->buildv > l->runv ||
|
||||
AV_VERSION_MAJOR(l->buildv) != AV_VERSION_MAJOR(l->runv);
|
||||
mismatch = true;
|
||||
}
|
||||
mp_msg(log, v, "\n");
|
||||
}
|
||||
|
||||
@@ -43,16 +43,6 @@
|
||||
// align must be a power of two (align >= 1), x >= 0
|
||||
#define MP_ALIGN_UP(x, align) (((x) + (align) - 1) & ~((align) - 1))
|
||||
#define MP_ALIGN_DOWN(x, align) ((x) & ~((align) - 1))
|
||||
#define MP_IS_ALIGNED(x, align) (!((x) & ((align) - 1)))
|
||||
#define MP_IS_POWER_OF_2(x) ((x) > 0 && !((x) & ((x) - 1)))
|
||||
|
||||
// Return "a", or if that is NOPTS, return "def".
|
||||
#define MP_PTS_OR_DEF(a, def) ((a) == MP_NOPTS_VALUE ? (def) : (a))
|
||||
// If one of the values is NOPTS, always pick the other one.
|
||||
#define MP_PTS_MIN(a, b) MPMIN(MP_PTS_OR_DEF(a, b), MP_PTS_OR_DEF(b, a))
|
||||
#define MP_PTS_MAX(a, b) MPMAX(MP_PTS_OR_DEF(a, b), MP_PTS_OR_DEF(b, a))
|
||||
// Return a+b, unless a is NOPTS. b must not be NOPTS.
|
||||
#define MP_ADD_PTS(a, b) ((a) == MP_NOPTS_VALUE ? (a) : ((a) + (b)))
|
||||
|
||||
#define CONTROL_OK 1
|
||||
#define CONTROL_TRUE 1
|
||||
|
||||
@@ -422,11 +422,6 @@ static struct mux_stream *encode_lavc_add_stream(struct encode_lavc_context *ctx
|
||||
|
||||
dst->encoder_timebase = info->timebase;
|
||||
dst->st->time_base = info->timebase; // lavf will change this on muxer init
|
||||
// Some muxers (e.g. Matroska one) expect the sample_aspect_ratio to be
|
||||
// set on the AVStream.
|
||||
if (info->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
|
||||
dst->st->sample_aspect_ratio = info->codecpar->sample_aspect_ratio;
|
||||
|
||||
if (avcodec_parameters_copy(dst->st->codecpar, info->codecpar) < 0)
|
||||
MP_HANDLE_OOM(0);
|
||||
|
||||
@@ -481,6 +476,8 @@ static void encode_lavc_add_packet(struct mux_stream *dst, AVPacket *pkt)
|
||||
if (av_interleaved_write_frame(p->muxer, pkt) < 0) {
|
||||
MP_ERR(p, "Writing packet failed.\n");
|
||||
p->failed = true;
|
||||
pkt = NULL;
|
||||
goto done;
|
||||
}
|
||||
|
||||
pkt = NULL;
|
||||
|
||||
+4
-1
@@ -8,7 +8,10 @@ struct mpv_global {
|
||||
struct mp_log *log;
|
||||
struct m_config_shadow *config;
|
||||
struct mp_client_api *client_api;
|
||||
char *configdir;
|
||||
|
||||
// Using this is deprecated and should be avoided (missing synchronization).
|
||||
// Use m_config_cache to access mpv_global.config instead.
|
||||
struct MPOpts *opts;
|
||||
};
|
||||
|
||||
#endif
|
||||
+23
-9
@@ -50,6 +50,7 @@ struct mp_log_root {
|
||||
bool use_terminal; // make accesses to stderr/stdout
|
||||
bool module;
|
||||
bool show_time;
|
||||
bool termosd; // use terminal control codes for status line
|
||||
int blank_lines; // number of lines usable by status
|
||||
int status_lines; // number of current status lines
|
||||
bool color;
|
||||
@@ -126,19 +127,19 @@ static void update_loglevel(struct mp_log *log)
|
||||
pthread_mutex_unlock(&mp_msg_lock);
|
||||
}
|
||||
|
||||
// Get the current effective msg level.
|
||||
// Return whether the message at this verbosity level would be actually printed.
|
||||
// Thread-safety: see mp_msg().
|
||||
int mp_msg_level(struct mp_log *log)
|
||||
bool mp_msg_test(struct mp_log *log, int lev)
|
||||
{
|
||||
struct mp_log_root *root = log->root;
|
||||
if (!root)
|
||||
return -1;
|
||||
return false;
|
||||
if (atomic_load_explicit(&log->reload_counter, memory_order_relaxed) !=
|
||||
atomic_load_explicit(&root->reload_counter, memory_order_relaxed))
|
||||
{
|
||||
update_loglevel(log);
|
||||
}
|
||||
return log->level;
|
||||
return lev <= log->level;
|
||||
}
|
||||
|
||||
// Reposition cursor and clear lines for outputting the status line. In certain
|
||||
@@ -360,7 +361,7 @@ void mp_msg_va(struct mp_log *log, int lev, const char *format, va_list va)
|
||||
} else if (lev == MSGL_STATUS && !test_terminal_level(log, lev)) {
|
||||
/* discard */
|
||||
} else {
|
||||
if (lev == MSGL_STATUS)
|
||||
if (lev == MSGL_STATUS && root->termosd)
|
||||
prepare_status_line(root, text);
|
||||
|
||||
// Split away each line. Normally we require full lines; buffer partial
|
||||
@@ -381,7 +382,7 @@ void mp_msg_va(struct mp_log *log, int lev, const char *format, va_list va)
|
||||
|
||||
if (lev == MSGL_STATUS) {
|
||||
if (text[0])
|
||||
print_terminal_line(log, lev, text, "\r");
|
||||
print_terminal_line(log, lev, text, root->termosd ? "\r" : "\n");
|
||||
} else if (text[0]) {
|
||||
int size = strlen(text) + 1;
|
||||
if (talloc_get_size(log->partial) < size)
|
||||
@@ -459,6 +460,8 @@ void mp_msg_init(struct mpv_global *global)
|
||||
struct mp_log *log = mp_log_new(root, &dummy, "");
|
||||
|
||||
global->log = log;
|
||||
|
||||
mp_msg_update_msglevels(global);
|
||||
}
|
||||
|
||||
// If opt is different from *current_path, reopen *file and update *current_path.
|
||||
@@ -498,9 +501,13 @@ static void reopen_file(char *opt, char **current_path, FILE **file,
|
||||
talloc_free(tmp);
|
||||
}
|
||||
|
||||
void mp_msg_update_msglevels(struct mpv_global *global, struct MPOpts *opts)
|
||||
void mp_msg_update_msglevels(struct mpv_global *global)
|
||||
{
|
||||
struct mp_log_root *root = global->log->root;
|
||||
struct MPOpts *opts = global->opts;
|
||||
|
||||
if (!opts)
|
||||
return;
|
||||
|
||||
pthread_mutex_lock(&mp_msg_lock);
|
||||
|
||||
@@ -509,11 +516,14 @@ void mp_msg_update_msglevels(struct mpv_global *global, struct MPOpts *opts)
|
||||
root->module = opts->msg_module;
|
||||
root->use_terminal = opts->use_terminal;
|
||||
root->show_time = opts->msg_time;
|
||||
if (root->use_terminal)
|
||||
if (root->use_terminal) {
|
||||
root->color = opts->msg_color && isatty(STDOUT_FILENO);
|
||||
root->termosd = isatty(STDERR_FILENO);
|
||||
}
|
||||
|
||||
m_option_type_msglevels.free(&root->msg_levels);
|
||||
m_option_type_msglevels.copy(NULL, &root->msg_levels, &opts->msg_levels);
|
||||
m_option_type_msglevels.copy(NULL, &root->msg_levels,
|
||||
&global->opts->msg_levels);
|
||||
|
||||
atomic_fetch_add(&root->reload_counter, 1);
|
||||
pthread_mutex_unlock(&mp_msg_lock);
|
||||
@@ -566,6 +576,10 @@ struct mp_log_buffer *mp_msg_log_buffer_new(struct mpv_global *global,
|
||||
{
|
||||
struct mp_log_root *root = global->log->root;
|
||||
|
||||
#if !HAVE_ATOMICS
|
||||
return NULL;
|
||||
#endif
|
||||
|
||||
pthread_mutex_lock(&mp_msg_lock);
|
||||
|
||||
struct mp_log_buffer *buffer = talloc_ptrtype(NULL, buffer);
|
||||
|
||||
+1
-6
@@ -52,12 +52,7 @@ void mp_msg(struct mp_log *log, int lev, const char *format, ...)
|
||||
PRINTF_ATTRIBUTE(3, 4);
|
||||
void mp_msg_va(struct mp_log *log, int lev, const char *format, va_list va);
|
||||
|
||||
int mp_msg_level(struct mp_log *log);
|
||||
|
||||
static inline bool mp_msg_test(struct mp_log *log, int lev)
|
||||
{
|
||||
return lev <= mp_msg_level(log);
|
||||
}
|
||||
bool mp_msg_test(struct mp_log *log, int lev);
|
||||
|
||||
// Convenience macros.
|
||||
#define mp_fatal(log, ...) mp_msg(log, MSGL_FATAL, __VA_ARGS__)
|
||||
|
||||
@@ -4,10 +4,9 @@
|
||||
#include <stdbool.h>
|
||||
|
||||
struct mpv_global;
|
||||
struct MPOpts;
|
||||
void mp_msg_init(struct mpv_global *global);
|
||||
void mp_msg_uninit(struct mpv_global *global);
|
||||
void mp_msg_update_msglevels(struct mpv_global *global, struct MPOpts *opts);
|
||||
void mp_msg_update_msglevels(struct mpv_global *global);
|
||||
void mp_msg_force_stderr(struct mpv_global *global, bool force_stderr);
|
||||
bool mp_msg_has_status_line(struct mpv_global *global);
|
||||
bool mp_msg_has_log_file(struct mpv_global *global);
|
||||
|
||||
+3
-4
@@ -275,14 +275,13 @@ struct playlist_entry *playlist_entry_from_index(struct playlist *pl, int index)
|
||||
}
|
||||
}
|
||||
|
||||
struct playlist *playlist_parse_file(const char *file, struct mp_cancel *cancel,
|
||||
struct mpv_global *global)
|
||||
struct playlist *playlist_parse_file(const char *file, struct mpv_global *global)
|
||||
{
|
||||
struct mp_log *log = mp_log_new(NULL, global->log, "!playlist_parser");
|
||||
mp_verbose(log, "Parsing playlist file %s...\n", file);
|
||||
|
||||
struct demuxer_params p = {.force_format = "playlist"};
|
||||
struct demuxer *d = demux_open_url(file, &p, cancel, global);
|
||||
struct demuxer *d = demux_open_url(file, &p, NULL, global);
|
||||
if (!d) {
|
||||
talloc_free(log);
|
||||
return NULL;
|
||||
@@ -297,7 +296,7 @@ struct playlist *playlist_parse_file(const char *file, struct mp_cancel *cancel,
|
||||
"pass it to the player\ndirectly. Don't use --playlist.\n");
|
||||
}
|
||||
}
|
||||
demux_free(d);
|
||||
free_demuxer_and_stream(d);
|
||||
|
||||
if (ret) {
|
||||
mp_verbose(log, "Playlist successfully parsed\n");
|
||||
|
||||
+1
-3
@@ -101,10 +101,8 @@ int playlist_entry_to_index(struct playlist *pl, struct playlist_entry *e);
|
||||
int playlist_entry_count(struct playlist *pl);
|
||||
struct playlist_entry *playlist_entry_from_index(struct playlist *pl, int index);
|
||||
|
||||
struct mp_cancel;
|
||||
struct mpv_global;
|
||||
struct playlist *playlist_parse_file(const char *file, struct mp_cancel *cancel,
|
||||
struct mpv_global *global);
|
||||
struct playlist *playlist_parse_file(const char *file, struct mpv_global *global);
|
||||
|
||||
void playlist_entry_unref(struct playlist_entry *e);
|
||||
|
||||
|
||||
+16
-18
@@ -28,6 +28,8 @@
|
||||
|
||||
#include "recorder.h"
|
||||
|
||||
#define PTS_ADD(a, b) ((a) == MP_NOPTS_VALUE ? (a) : ((a) + (b)))
|
||||
|
||||
// Maximum number of packets we buffer at most to attempt to resync streams.
|
||||
// Essentially, this should be higher than the highest supported keyframe
|
||||
// interval.
|
||||
@@ -196,10 +198,10 @@ static void mux_packet(struct mp_recorder_sink *rst,
|
||||
struct demux_packet mpkt = *pkt;
|
||||
|
||||
double diff = priv->rebase_ts - priv->base_ts;
|
||||
mpkt.pts = MP_ADD_PTS(mpkt.pts, diff);
|
||||
mpkt.dts = MP_ADD_PTS(mpkt.dts, diff);
|
||||
mpkt.pts = PTS_ADD(mpkt.pts, diff);
|
||||
mpkt.dts = PTS_ADD(mpkt.dts, diff);
|
||||
|
||||
rst->max_out_pts = MP_PTS_MAX(rst->max_out_pts, pkt->pts);
|
||||
rst->max_out_pts = MPMAX(rst->max_out_pts, pkt->pts);
|
||||
|
||||
AVPacket avpkt;
|
||||
mp_set_av_packet(&avpkt, &mpkt, &rst->av_stream->time_base);
|
||||
@@ -252,13 +254,13 @@ static void check_restart(struct mp_recorder *priv)
|
||||
if (priv->muxing)
|
||||
return;
|
||||
|
||||
double min_ts = MP_NOPTS_VALUE;
|
||||
double min_ts = INFINITY;
|
||||
double rebase_ts = 0;
|
||||
for (int n = 0; n < priv->num_streams; n++) {
|
||||
struct mp_recorder_sink *rst = priv->streams[n];
|
||||
int min_packets = rst->sh->type == STREAM_VIDEO ? QUEUE_MIN_PACKETS : 1;
|
||||
|
||||
rebase_ts = MP_PTS_MAX(rebase_ts, rst->max_out_pts);
|
||||
rebase_ts = MPMAX(rebase_ts, rst->max_out_pts);
|
||||
|
||||
if (rst->num_packets < min_packets) {
|
||||
if (!rst->proper_eof && rst->sh->type != STREAM_SUB)
|
||||
@@ -267,11 +269,11 @@ static void check_restart(struct mp_recorder *priv)
|
||||
}
|
||||
|
||||
for (int i = 0; i < min_packets; i++)
|
||||
min_ts = MP_PTS_MIN(min_ts, rst->packets[i]->pts);
|
||||
min_ts = MPMIN(min_ts, rst->packets[i]->pts);
|
||||
}
|
||||
|
||||
// Subtitle only stream (wait longer) or stream without any PTS (fuck it).
|
||||
if (min_ts == MP_NOPTS_VALUE)
|
||||
if (!isfinite(min_ts))
|
||||
return;
|
||||
|
||||
priv->rebase_ts = rebase_ts;
|
||||
@@ -293,6 +295,8 @@ void mp_recorder_destroy(struct mp_recorder *priv)
|
||||
if (priv->opened) {
|
||||
for (int n = 0; n < priv->num_streams; n++) {
|
||||
struct mp_recorder_sink *rst = priv->streams[n];
|
||||
if (!rst->proper_eof)
|
||||
continue;
|
||||
mux_packets(rst, true);
|
||||
}
|
||||
|
||||
@@ -318,7 +322,6 @@ void mp_recorder_mark_discontinuity(struct mp_recorder *priv)
|
||||
|
||||
for (int n = 0; n < priv->num_streams; n++) {
|
||||
struct mp_recorder_sink *rst = priv->streams[n];
|
||||
mux_packets(rst, true);
|
||||
rst->discont = true;
|
||||
rst->proper_eof = false;
|
||||
}
|
||||
@@ -328,17 +331,12 @@ void mp_recorder_mark_discontinuity(struct mp_recorder *priv)
|
||||
}
|
||||
|
||||
// Get a stream for writing. The pointer is valid until mp_recorder is
|
||||
// destroyed. The stream ptr. is the same as one passed to
|
||||
// mp_recorder_create() (returns NULL if it wasn't).
|
||||
struct mp_recorder_sink *mp_recorder_get_sink(struct mp_recorder *r,
|
||||
struct sh_stream *stream)
|
||||
// destroyed. The stream is the index referencing the stream passed to
|
||||
// mp_recorder_create().
|
||||
struct mp_recorder_sink *mp_recorder_get_sink(struct mp_recorder *r, int stream)
|
||||
{
|
||||
for (int n = 0; n < r->num_streams; n++) {
|
||||
struct mp_recorder_sink *rst = r->streams[n];
|
||||
if (rst->sh == stream)
|
||||
return rst;
|
||||
}
|
||||
return NULL;
|
||||
assert(stream >= 0 && stream < r->num_streams);
|
||||
return r->streams[stream];
|
||||
}
|
||||
|
||||
// Pass a packet to the given stream. The function does not own the packet, but
|
||||
|
||||
+1
-2
@@ -14,8 +14,7 @@ struct mp_recorder *mp_recorder_create(struct mpv_global *global,
|
||||
void mp_recorder_destroy(struct mp_recorder *r);
|
||||
void mp_recorder_mark_discontinuity(struct mp_recorder *r);
|
||||
|
||||
struct mp_recorder_sink *mp_recorder_get_sink(struct mp_recorder *r,
|
||||
struct sh_stream *stream);
|
||||
struct mp_recorder_sink *mp_recorder_get_sink(struct mp_recorder *r, int stream);
|
||||
void mp_recorder_feed_packet(struct mp_recorder_sink *s,
|
||||
struct demux_packet *pkt);
|
||||
|
||||
|
||||
-323
@@ -1,323 +0,0 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "cache.h"
|
||||
#include "common/msg.h"
|
||||
#include "common/av_common.h"
|
||||
#include "demux.h"
|
||||
#include "options/path.h"
|
||||
#include "options/m_config.h"
|
||||
#include "options/m_option.h"
|
||||
#include "osdep/io.h"
|
||||
|
||||
struct demux_cache_opts {
|
||||
char *cache_dir;
|
||||
int unlink_files;
|
||||
};
|
||||
|
||||
#define OPT_BASE_STRUCT struct demux_cache_opts
|
||||
|
||||
const struct m_sub_options demux_cache_conf = {
|
||||
.opts = (const struct m_option[]){
|
||||
OPT_STRING("cache-dir", cache_dir, M_OPT_FILE),
|
||||
OPT_CHOICE("cache-unlink-files", unlink_files, 0,
|
||||
({"immediate", 2}, {"whendone", 1}, {"no", 0})),
|
||||
{0}
|
||||
},
|
||||
.size = sizeof(struct demux_cache_opts),
|
||||
.defaults = &(const struct demux_cache_opts){
|
||||
.unlink_files = 2,
|
||||
},
|
||||
};
|
||||
|
||||
struct demux_cache {
|
||||
struct mp_log *log;
|
||||
struct demux_cache_opts *opts;
|
||||
|
||||
char *filename;
|
||||
bool need_unlink;
|
||||
int fd;
|
||||
int64_t file_pos;
|
||||
uint64_t file_size;
|
||||
};
|
||||
|
||||
struct pkt_header {
|
||||
uint32_t data_len;
|
||||
uint32_t av_flags;
|
||||
uint32_t num_sd;
|
||||
};
|
||||
|
||||
struct sd_header {
|
||||
uint32_t av_type;
|
||||
uint32_t len;
|
||||
};
|
||||
|
||||
static void cache_destroy(void *p)
|
||||
{
|
||||
struct demux_cache *cache = p;
|
||||
|
||||
if (cache->fd >= 0)
|
||||
close(cache->fd);
|
||||
|
||||
if (cache->need_unlink && cache->opts->unlink_files >= 1) {
|
||||
if (unlink(cache->filename))
|
||||
MP_ERR(cache, "Failed to delete cache temporary file.\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Create a cache. This also initializes the cache file from the options. The
|
||||
// log parameter must stay valid until demux_cache is destroyed.
|
||||
// Free with talloc_free().
|
||||
struct demux_cache *demux_cache_create(struct mpv_global *global,
|
||||
struct mp_log *log)
|
||||
{
|
||||
struct demux_cache *cache = talloc_zero(NULL, struct demux_cache);
|
||||
talloc_set_destructor(cache, cache_destroy);
|
||||
cache->opts = mp_get_config_group(cache, global, &demux_cache_conf);
|
||||
cache->log = log;
|
||||
cache->fd = -1;
|
||||
|
||||
char *cache_dir = cache->opts->cache_dir;
|
||||
if (!(cache_dir && cache_dir[0])) {
|
||||
MP_ERR(cache, "No cache data directory supplied.\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
cache->filename = mp_path_join(cache, cache_dir, "mpv-cache-XXXXXX.dat");
|
||||
cache->fd = mp_mkostemps(cache->filename, 4, O_CLOEXEC);
|
||||
if (cache->fd < 0) {
|
||||
MP_ERR(cache, "Failed to create cache temporary file.\n");
|
||||
goto fail;
|
||||
}
|
||||
cache->need_unlink = true;
|
||||
if (cache->opts->unlink_files >= 2) {
|
||||
if (unlink(cache->filename)) {
|
||||
MP_ERR(cache, "Failed to unlink cache temporary file after creation.\n");
|
||||
} else {
|
||||
cache->need_unlink = false;
|
||||
}
|
||||
}
|
||||
|
||||
return cache;
|
||||
fail:
|
||||
talloc_free(cache);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint64_t demux_cache_get_size(struct demux_cache *cache)
|
||||
{
|
||||
return cache->file_size;
|
||||
}
|
||||
|
||||
static bool do_seek(struct demux_cache *cache, uint64_t pos)
|
||||
{
|
||||
if (cache->file_pos == pos)
|
||||
return true;
|
||||
|
||||
off_t res = lseek(cache->fd, pos, SEEK_SET);
|
||||
|
||||
if (res == (off_t)-1) {
|
||||
MP_ERR(cache, "Failed to seek in cache file.\n");
|
||||
cache->file_pos = -1;
|
||||
} else {
|
||||
cache->file_pos = res;
|
||||
}
|
||||
|
||||
return cache->file_pos >= 0;
|
||||
}
|
||||
|
||||
static bool write_raw(struct demux_cache *cache, void *ptr, size_t len)
|
||||
{
|
||||
ssize_t res = write(cache->fd, ptr, len);
|
||||
|
||||
if (res < 0) {
|
||||
MP_ERR(cache, "Failed to write to cache file: %s\n", mp_strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
cache->file_pos += res;
|
||||
cache->file_size = MPMAX(cache->file_size, cache->file_pos);
|
||||
|
||||
// Should never happen, unless the disk is full, or someone succeeded to
|
||||
// trick us to write into a pipe or a socket.
|
||||
if (res != len) {
|
||||
MP_ERR(cache, "Could not write all data.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool read_raw(struct demux_cache *cache, void *ptr, size_t len)
|
||||
{
|
||||
ssize_t res = read(cache->fd, ptr, len);
|
||||
|
||||
if (res < 0) {
|
||||
MP_ERR(cache, "Failed to read cache file: %s\n", mp_strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
cache->file_pos += res;
|
||||
|
||||
// Should never happen, unless the file was cut short, or someone succeeded
|
||||
// to rick us to write into a pipe or a socket.
|
||||
if (res != len) {
|
||||
MP_ERR(cache, "Could not read all data.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Serialize a packet to the cache file. Returns the packet position, which can
|
||||
// be passed to demux_cache_read() to read the packet again.
|
||||
// Returns a negative value on errors, i.e. writing the file failed.
|
||||
int64_t demux_cache_write(struct demux_cache *cache, struct demux_packet *dp)
|
||||
{
|
||||
assert(dp->avpacket);
|
||||
|
||||
// AV_PKT_FLAG_TRUSTED usually means there are embedded pointers and such
|
||||
// in the packet data. The pointer will become invalid if the packet is
|
||||
// unreferenced.
|
||||
if (dp->avpacket->flags & AV_PKT_FLAG_TRUSTED) {
|
||||
MP_ERR(cache, "Cannot serialize this packet to cache file.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
assert(!dp->is_cached);
|
||||
assert(dp->len >= 0 && dp->len <= INT32_MAX);
|
||||
assert(dp->avpacket->flags >= 0 && dp->avpacket->flags <= INT32_MAX);
|
||||
assert(dp->avpacket->side_data_elems >= 0 &&
|
||||
dp->avpacket->side_data_elems <= INT32_MAX);
|
||||
|
||||
if (!do_seek(cache, cache->file_size))
|
||||
return -1;
|
||||
|
||||
uint64_t pos = cache->file_pos;
|
||||
|
||||
struct pkt_header hd = {
|
||||
.data_len = dp->len,
|
||||
.av_flags = dp->avpacket->flags,
|
||||
.num_sd = dp->avpacket->side_data_elems,
|
||||
};
|
||||
|
||||
if (!write_raw(cache, &hd, sizeof(hd)))
|
||||
goto fail;
|
||||
|
||||
if (!write_raw(cache, dp->buffer, dp->len))
|
||||
goto fail;
|
||||
|
||||
// The handling of FFmpeg side data requires an extra long comment to
|
||||
// explain why this code is fragile and insane.
|
||||
// FFmpeg packet side data is per-packet out of band data, that contains
|
||||
// further information for the decoder (extra metadata and such), which is
|
||||
// not part of the codec itself and thus isn't contained in the packet
|
||||
// payload. All types use a flat byte array. The format of this byte array
|
||||
// is non-standard and FFmpeg-specific, and depends on the side data type
|
||||
// field. The side data type is of course a FFmpeg ABI artifact.
|
||||
// In some cases, the format is described as fixed byte layout. In others,
|
||||
// it contains a struct, i.e. is bound to FFmpeg ABI. Some newer types make
|
||||
// the format explicitly internal (and _not_ part of the ABI), and you need
|
||||
// to use separate accessors to turn it into complex data structures.
|
||||
// As of now, FFmpeg fortunately adheres to the idea that side data can not
|
||||
// contain embedded pointers (due to API rules, but also because they forgot
|
||||
// adding a refcount field, and can't change this until they break ABI).
|
||||
// We rely on this. We hope that FFmpeg won't silently change their
|
||||
// semantics, and add refcounting and embedded pointers. This way we can
|
||||
// for example dump the data in a disk cache, even though we can't use the
|
||||
// data from another process or if this process is restarted (unless we're
|
||||
// absolutely sure the FFmpeg internals didn't change). The data has to be
|
||||
// treated as a memory dump.
|
||||
for (int n = 0; n < dp->avpacket->side_data_elems; n++) {
|
||||
AVPacketSideData *sd = &dp->avpacket->side_data[n];
|
||||
|
||||
assert(sd->size >= 0 && sd->size <= INT32_MAX);
|
||||
assert(sd->type >= 0 && sd->type <= INT32_MAX);
|
||||
|
||||
struct sd_header sd_hd = {
|
||||
.av_type = sd->type,
|
||||
.len = sd->size,
|
||||
};
|
||||
|
||||
if (!write_raw(cache, &sd_hd, sizeof(sd_hd)))
|
||||
goto fail;
|
||||
if (!write_raw(cache, sd->data, sd->size))
|
||||
goto fail;
|
||||
}
|
||||
|
||||
return pos;
|
||||
|
||||
fail:
|
||||
// Reset file_size (try not to append crap forever).
|
||||
do_seek(cache, pos);
|
||||
cache->file_size = cache->file_pos;
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct demux_packet *demux_cache_read(struct demux_cache *cache, uint64_t pos)
|
||||
{
|
||||
if (!do_seek(cache, pos))
|
||||
return NULL;
|
||||
|
||||
struct pkt_header hd;
|
||||
|
||||
if (!read_raw(cache, &hd, sizeof(hd)))
|
||||
return NULL;
|
||||
|
||||
if (hd.data_len >= (size_t)-1)
|
||||
return NULL;
|
||||
|
||||
struct demux_packet *dp = new_demux_packet(hd.data_len);
|
||||
if (!dp)
|
||||
goto fail;
|
||||
|
||||
if (!read_raw(cache, dp->buffer, dp->len))
|
||||
goto fail;
|
||||
|
||||
dp->avpacket->flags = hd.av_flags;
|
||||
|
||||
for (uint32_t n = 0; n < hd.num_sd; n++) {
|
||||
struct sd_header sd_hd;
|
||||
|
||||
if (!read_raw(cache, &sd_hd, sizeof(sd_hd)))
|
||||
goto fail;
|
||||
|
||||
if (sd_hd.len > INT_MAX)
|
||||
goto fail;
|
||||
|
||||
uint8_t *sd = av_packet_new_side_data(dp->avpacket, sd_hd.av_type,
|
||||
sd_hd.len);
|
||||
if (!sd)
|
||||
goto fail;
|
||||
|
||||
if (!read_raw(cache, sd, sd_hd.len))
|
||||
goto fail;
|
||||
}
|
||||
|
||||
return dp;
|
||||
|
||||
fail:
|
||||
talloc_free(dp);
|
||||
return NULL;
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
struct demux_packet;
|
||||
struct mp_log;
|
||||
struct mpv_global;
|
||||
|
||||
struct demux_cache;
|
||||
|
||||
struct demux_cache *demux_cache_create(struct mpv_global *global,
|
||||
struct mp_log *log);
|
||||
|
||||
int64_t demux_cache_write(struct demux_cache *cache, struct demux_packet *pkt);
|
||||
struct demux_packet *demux_cache_read(struct demux_cache *cache, uint64_t pos);
|
||||
uint64_t demux_cache_get_size(struct demux_cache *cache);
|
||||
+14
-4
@@ -50,8 +50,14 @@ static const char *lookup_tag(int type, uint32_t tag)
|
||||
return id == AV_CODEC_ID_NONE ? NULL : mp_codec_from_av_codec_id(id);
|
||||
}
|
||||
|
||||
// Corresponds to WMMEDIASUBTYPE_Base.
|
||||
static const unsigned char guid_ext_base[16] =
|
||||
static const unsigned char guid_pcm[16] =
|
||||
{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
|
||||
0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71};
|
||||
static const unsigned char guid_float[16] =
|
||||
{0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
|
||||
0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71};
|
||||
// Corresponds to FF_MEDIASUBTYPE_BASE_GUID (plus 4 bytes of padding).
|
||||
static const unsigned char guid_ffext[16] =
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
|
||||
0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71};
|
||||
|
||||
@@ -68,14 +74,18 @@ static void map_audio_pcm_tag(struct mp_codec_params *c)
|
||||
|
||||
// WAVEFORMATEXTENSIBLE.SubFormat
|
||||
unsigned char *subformat = c->extradata + 6;
|
||||
if (memcmp(subformat + 4, guid_ext_base + 4, 12) == 0) {
|
||||
if (memcmp(subformat + 4, guid_ffext + 4, 12) == 0) {
|
||||
c->codec_tag = AV_RL32(subformat);
|
||||
c->codec = lookup_tag(c->type, c->codec_tag);
|
||||
}
|
||||
if (memcmp(subformat, guid_pcm, 16) == 0)
|
||||
c->codec_tag = 0x0;
|
||||
if (memcmp(subformat, guid_float, 16) == 0)
|
||||
c->codec_tag = 0x3;
|
||||
|
||||
// Compressed formats might use this.
|
||||
c->extradata += 22;
|
||||
c->extradata_size -= 22;
|
||||
c->extradata_size += 22;
|
||||
}
|
||||
|
||||
int bits = c->bits_per_coded_sample;
|
||||
|
||||
+7
-8
@@ -113,17 +113,16 @@ static struct bstr strip_quotes(struct bstr data)
|
||||
return data;
|
||||
}
|
||||
|
||||
// Read an unsigned decimal integer.
|
||||
// Optionally check if it is 2 digit.
|
||||
// Read a 2 digit unsigned decimal integer.
|
||||
// Return -1 on failure.
|
||||
static int read_int(struct bstr *data, bool two_digit)
|
||||
static int read_int_2(struct bstr *data)
|
||||
{
|
||||
*data = bstr_lstrip(*data);
|
||||
if (data->len && data->start[0] == '-')
|
||||
return -1;
|
||||
struct bstr s = *data;
|
||||
int res = (int)bstrtoll(s, &s, 10);
|
||||
if (data->len == s.len || (two_digit && data->len - s.len > 2))
|
||||
if (data->len == s.len || data->len - s.len > 2)
|
||||
return -1;
|
||||
*data = s;
|
||||
return res;
|
||||
@@ -133,11 +132,11 @@ static double read_time(struct bstr *data)
|
||||
{
|
||||
struct bstr s = *data;
|
||||
bool ok = true;
|
||||
double t1 = read_int(&s, false);
|
||||
double t1 = read_int_2(&s);
|
||||
ok = eat_char(&s, ':') && ok;
|
||||
double t2 = read_int(&s, true);
|
||||
double t2 = read_int_2(&s);
|
||||
ok = eat_char(&s, ':') && ok;
|
||||
double t3 = read_int(&s, true);
|
||||
double t3 = read_int_2(&s);
|
||||
ok = ok && t1 >= 0 && t2 >= 0 && t3 >= 0;
|
||||
return ok ? t1 * 60.0 + t2 + t3 * SECS_PER_CUE_FRAME : 0;
|
||||
}
|
||||
@@ -206,7 +205,7 @@ struct cue_file *mp_parse_cue(struct bstr data)
|
||||
break;
|
||||
}
|
||||
case CUE_INDEX: {
|
||||
int type = read_int(¶m, true);
|
||||
int type = read_int_2(¶m);
|
||||
double time = read_time(¶m);
|
||||
if (cur_track) {
|
||||
if (type == 1) {
|
||||
|
||||
+976
-2247
File diff suppressed because it is too large.
Load diff
+48
-44
@@ -30,38 +30,49 @@
|
||||
#include "packet.h"
|
||||
#include "stheader.h"
|
||||
|
||||
enum demux_ctrl {
|
||||
DEMUXER_CTRL_SWITCHED_TRACKS = 1,
|
||||
DEMUXER_CTRL_RESYNC,
|
||||
DEMUXER_CTRL_IDENTIFY_PROGRAM,
|
||||
DEMUXER_CTRL_STREAM_CTRL,
|
||||
DEMUXER_CTRL_GET_READER_STATE,
|
||||
DEMUXER_CTRL_GET_BITRATE_STATS, // double[STREAM_TYPE_COUNT]
|
||||
DEMUXER_CTRL_REPLACE_STREAM,
|
||||
};
|
||||
|
||||
#define MAX_SEEK_RANGES 10
|
||||
|
||||
struct demux_seek_range {
|
||||
double start, end;
|
||||
};
|
||||
|
||||
struct demux_reader_state {
|
||||
struct demux_ctrl_reader_state {
|
||||
bool eof, underrun, idle;
|
||||
bool bof_cached, eof_cached;
|
||||
double ts_duration;
|
||||
double ts_reader; // approx. timerstamp of decoder position
|
||||
double ts_end; // approx. timestamp of end of buffered range
|
||||
int64_t total_bytes;
|
||||
int64_t fw_bytes;
|
||||
int64_t file_cache_bytes;
|
||||
double seeking; // current low level seek target, or NOPTS
|
||||
int low_level_seeks; // number of started low level seeks
|
||||
double ts_last; // approx. timestamp of demuxer position
|
||||
uint64_t bytes_per_second; // low level statistics
|
||||
// Positions that can be seeked to without incurring the latency of a low
|
||||
// level seek.
|
||||
int num_seek_ranges;
|
||||
struct demux_seek_range seek_ranges[MAX_SEEK_RANGES];
|
||||
};
|
||||
|
||||
struct demux_ctrl_stream_ctrl {
|
||||
int ctrl;
|
||||
void *arg;
|
||||
int res;
|
||||
};
|
||||
|
||||
#define SEEK_FACTOR (1 << 1) // argument is in range [0,1]
|
||||
#define SEEK_FORWARD (1 << 2) // prefer later time if not exact
|
||||
// (if unset, prefer earlier time)
|
||||
#define SEEK_CACHED (1 << 3) // allow packet cache seeks only
|
||||
#define SEEK_SATAN (1 << 4) // enable backward demuxing
|
||||
#define SEEK_HR (1 << 5) // hr-seek (this is a weak hint only)
|
||||
#define SEEK_FORCE (1 << 6) // ignore unseekable flag
|
||||
|
||||
// Strictness of the demuxer open format check.
|
||||
// demux.c will try by default: NORMAL, UNSAFE (in this order)
|
||||
@@ -100,14 +111,10 @@ typedef struct demuxer_desc {
|
||||
// Return 0 on success, otherwise -1
|
||||
int (*open)(struct demuxer *demuxer, enum demux_check check);
|
||||
// The following functions are all optional
|
||||
// Try to read a packet. Return false on EOF. If true is returned, the
|
||||
// demuxer may set *pkt to a new packet (the reference goes to the caller).
|
||||
// If *pkt is NULL (the value when this function is called), the call
|
||||
// will be repeated.
|
||||
bool (*read_packet)(struct demuxer *demuxer, struct demux_packet **pkt);
|
||||
int (*fill_buffer)(struct demuxer *demuxer); // 0 on EOF, otherwise 1
|
||||
void (*close)(struct demuxer *demuxer);
|
||||
void (*seek)(struct demuxer *demuxer, double rel_seek_secs, int flags);
|
||||
void (*switched_tracks)(struct demuxer *demuxer);
|
||||
int (*control)(struct demuxer *demuxer, int cmd, void *arg);
|
||||
// See timeline.c
|
||||
void (*load_timeline)(struct timeline *tl);
|
||||
} demuxer_desc_t;
|
||||
@@ -160,7 +167,6 @@ typedef struct demux_attachment
|
||||
} demux_attachment_t;
|
||||
|
||||
struct demuxer_params {
|
||||
bool is_top_level; // if true, it's not a sub-demuxer (enables cache etc.)
|
||||
char *force_format;
|
||||
int matroska_num_wanted_uids;
|
||||
struct matroska_segment_uid *matroska_wanted_uids;
|
||||
@@ -168,11 +174,12 @@ struct demuxer_params {
|
||||
bool *matroska_was_valid;
|
||||
struct timeline *timeline;
|
||||
bool disable_timeline;
|
||||
bool initial_readahead;
|
||||
bstr init_fragment;
|
||||
bool skip_lavf_probing;
|
||||
bool stream_record; // if true, enable stream recording if option is set
|
||||
// -- demux_open_url() only
|
||||
int stream_flags;
|
||||
struct stream *external_stream; // if set, use this, don't open or close streams
|
||||
bool disable_cache;
|
||||
// result
|
||||
bool demuxer_failed;
|
||||
};
|
||||
@@ -181,7 +188,6 @@ typedef struct demuxer {
|
||||
const demuxer_desc_t *desc; ///< Demuxer description structure
|
||||
const char *filetype; // format name when not identified by demuxer (libavformat)
|
||||
int64_t filepos; // input stream current pos.
|
||||
int64_t filesize;
|
||||
char *filename; // same as stream->url
|
||||
bool seekable;
|
||||
bool partially_seekable; // true if _maybe_ seekable; implies seekable=true
|
||||
@@ -195,7 +201,6 @@ typedef struct demuxer {
|
||||
// Typical examples: text subtitles, playlists
|
||||
bool fully_read;
|
||||
bool is_network; // opened directly from a network stream
|
||||
bool is_streaming; // implies a "slow" input, such as network or FUSE
|
||||
bool access_references; // allow opening other files/URLs
|
||||
|
||||
// Bitmask of DEMUX_EVENT_*
|
||||
@@ -225,28 +230,31 @@ typedef struct demuxer {
|
||||
|
||||
// internal to demux.c
|
||||
struct demux_internal *in;
|
||||
|
||||
// Triggered when ending demuxing forcefully. Usually bound to the stream too.
|
||||
struct mp_cancel *cancel;
|
||||
struct mp_tags **update_stream_tags;
|
||||
int num_update_stream_tags;
|
||||
|
||||
// Since the demuxer can run in its own thread, and the stream is not
|
||||
// thread-safe, only the demuxer is allowed to access the stream directly.
|
||||
// You can freely use demux_stream_control() to send STREAM_CTRLs.
|
||||
// Also note that the stream can get replaced if fully_read is set.
|
||||
struct stream *stream;
|
||||
} demuxer_t;
|
||||
|
||||
void demux_free(struct demuxer *demuxer);
|
||||
void demux_cancel_and_free(struct demuxer *demuxer);
|
||||
typedef struct {
|
||||
int progid; //program id
|
||||
int aid, vid, sid; //audio, video and subtitle id
|
||||
} demux_program_t;
|
||||
|
||||
struct demux_free_async_state;
|
||||
struct demux_free_async_state *demux_free_async(struct demuxer *demuxer);
|
||||
void demux_free_async_force(struct demux_free_async_state *state);
|
||||
bool demux_free_async_finish(struct demux_free_async_state *state);
|
||||
void free_demuxer(struct demuxer *demuxer);
|
||||
void free_demuxer_and_stream(struct demuxer *demuxer);
|
||||
|
||||
void demux_add_packet(struct sh_stream *stream, demux_packet_t *dp);
|
||||
void demuxer_feed_caption(struct sh_stream *stream, demux_packet_t *dp);
|
||||
|
||||
struct demux_packet *demux_read_packet(struct sh_stream *sh);
|
||||
int demux_read_packet_async(struct sh_stream *sh, struct demux_packet **out_pkt);
|
||||
bool demux_stream_is_selected(struct sh_stream *stream);
|
||||
bool demux_has_packet(struct sh_stream *sh);
|
||||
void demux_set_stream_wakeup_cb(struct sh_stream *sh,
|
||||
void (*cb)(void *ctx), void *ctx);
|
||||
struct demux_packet *demux_read_any_packet(struct demuxer *demuxer);
|
||||
@@ -257,6 +265,9 @@ int demux_get_num_stream(struct demuxer *demuxer);
|
||||
struct sh_stream *demux_alloc_sh_stream(enum stream_type type);
|
||||
void demux_add_sh_stream(struct demuxer *demuxer, struct sh_stream *sh);
|
||||
|
||||
struct demuxer *demux_open(struct stream *stream, struct demuxer_params *params,
|
||||
struct mpv_global *global);
|
||||
|
||||
struct mp_cancel;
|
||||
struct demuxer *demux_open_url(const char *url,
|
||||
struct demuxer_params *params,
|
||||
@@ -266,7 +277,6 @@ struct demuxer *demux_open_url(const char *url,
|
||||
void demux_start_thread(struct demuxer *demuxer);
|
||||
void demux_stop_thread(struct demuxer *demuxer);
|
||||
void demux_set_wakeup_cb(struct demuxer *demuxer, void (*cb)(void *ctx), void *ctx);
|
||||
void demux_start_prefetch(struct demuxer *demuxer);
|
||||
|
||||
bool demux_cancel_test(struct demuxer *demuxer);
|
||||
|
||||
@@ -274,13 +284,13 @@ void demux_flush(struct demuxer *demuxer);
|
||||
int demux_seek(struct demuxer *demuxer, double rel_seek_secs, int flags);
|
||||
void demux_set_ts_offset(struct demuxer *demuxer, double offset);
|
||||
|
||||
void demux_get_bitrate_stats(struct demuxer *demuxer, double *rates);
|
||||
void demux_get_reader_state(struct demuxer *demuxer, struct demux_reader_state *r);
|
||||
int demux_control(struct demuxer *demuxer, int cmd, void *arg);
|
||||
|
||||
void demux_block_reading(struct demuxer *demuxer, bool block);
|
||||
|
||||
void demuxer_select_track(struct demuxer *demuxer, struct sh_stream *stream,
|
||||
double ref_pts, bool selected);
|
||||
void demux_set_stream_autoselect(struct demuxer *demuxer, bool autoselect);
|
||||
|
||||
void demuxer_help(struct mp_log *log);
|
||||
|
||||
@@ -288,24 +298,15 @@ int demuxer_add_attachment(struct demuxer *demuxer, char *name,
|
||||
char *type, void *data, size_t data_size);
|
||||
int demuxer_add_chapter(demuxer_t *demuxer, char *name,
|
||||
double pts, uint64_t demuxer_id);
|
||||
void demux_stream_tags_changed(struct demuxer *demuxer, struct sh_stream *sh,
|
||||
struct mp_tags *tags, double pts);
|
||||
void demux_close_stream(struct demuxer *demuxer);
|
||||
void demux_set_stream_tags(struct demuxer *demuxer, struct sh_stream *sh,
|
||||
struct mp_tags *tags);
|
||||
|
||||
int demux_stream_control(demuxer_t *demuxer, int ctrl, void *arg);
|
||||
|
||||
void demux_metadata_changed(demuxer_t *demuxer);
|
||||
void demux_update(demuxer_t *demuxer, double playback_pts);
|
||||
void demux_update(demuxer_t *demuxer);
|
||||
|
||||
bool demux_cache_dump_set(struct demuxer *demuxer, double start, double end,
|
||||
char *file);
|
||||
int demux_cache_dump_get_status(struct demuxer *demuxer);
|
||||
|
||||
double demux_probe_cache_dump_target(struct demuxer *demuxer, double pts,
|
||||
bool for_end);
|
||||
|
||||
bool demux_is_network_cached(demuxer_t *demuxer);
|
||||
|
||||
void demux_report_unbuffered_read_bytes(struct demuxer *demuxer, int64_t new);
|
||||
int64_t demux_get_bytes_read_hack(struct demuxer *demuxer);
|
||||
void demux_disable_cache(demuxer_t *demuxer);
|
||||
|
||||
struct sh_stream *demuxer_stream_by_demuxer_id(struct demuxer *d,
|
||||
enum stream_type t, int id);
|
||||
@@ -317,4 +318,7 @@ bool demux_matroska_uid_cmp(struct matroska_segment_uid *a,
|
||||
|
||||
const char *stream_type_name(enum stream_type type);
|
||||
|
||||
void mp_packet_tags_unref(struct mp_packet_tags *tags);
|
||||
void mp_packet_tags_setref(struct mp_packet_tags **dst, struct mp_packet_tags *src);
|
||||
|
||||
#endif /* MPLAYER_DEMUXER_H */
|
||||
+11
-43
@@ -28,11 +28,8 @@
|
||||
#include "mpv_talloc.h"
|
||||
|
||||
#include "misc/bstr.h"
|
||||
#include "misc/charset_conv.h"
|
||||
#include "common/msg.h"
|
||||
#include "demux/demux.h"
|
||||
#include "options/m_config.h"
|
||||
#include "options/m_option.h"
|
||||
#include "options/path.h"
|
||||
#include "common/common.h"
|
||||
#include "stream/stream.h"
|
||||
@@ -42,25 +39,8 @@
|
||||
|
||||
#define PROBE_SIZE 512
|
||||
|
||||
#define OPT_BASE_STRUCT struct demux_cue_opts
|
||||
struct demux_cue_opts {
|
||||
char *cue_cp;
|
||||
};
|
||||
|
||||
const struct m_sub_options demux_cue_conf = {
|
||||
.opts = (const m_option_t[]) {
|
||||
OPT_STRING("codepage", cue_cp, 0),
|
||||
{0}
|
||||
},
|
||||
.size = sizeof(struct demux_cue_opts),
|
||||
.defaults = &(const struct demux_cue_opts) {
|
||||
.cue_cp = "auto"
|
||||
}
|
||||
};
|
||||
|
||||
struct priv {
|
||||
struct cue_file *f;
|
||||
struct demux_cue_opts *opts;
|
||||
};
|
||||
|
||||
static void add_source(struct timeline *tl, struct demuxer *d)
|
||||
@@ -230,7 +210,6 @@ static void build_timeline(struct timeline *tl)
|
||||
}
|
||||
timeline[i] = (struct timeline_part) {
|
||||
.start = starttime,
|
||||
.end = starttime + duration,
|
||||
.source_start = tracks[i].start,
|
||||
.source = source,
|
||||
};
|
||||
@@ -238,21 +217,23 @@ static void build_timeline(struct timeline *tl)
|
||||
.pts = timeline[i].start,
|
||||
.metadata = mp_tags_dup(tl, tracks[i].tags),
|
||||
};
|
||||
starttime = timeline[i].end;
|
||||
starttime += duration;
|
||||
}
|
||||
|
||||
struct timeline_par *par = talloc_ptrtype(tl, par);
|
||||
*par = (struct timeline_par){
|
||||
.parts = timeline,
|
||||
.num_parts = track_count,
|
||||
.track_layout = timeline[0].source,
|
||||
// apparently we need this to give the last part a non-zero length
|
||||
timeline[track_count] = (struct timeline_part) {
|
||||
.start = starttime,
|
||||
// perhaps unused by the timeline code
|
||||
.source_start = 0,
|
||||
.source = timeline[0].source,
|
||||
};
|
||||
|
||||
tl->parts = timeline;
|
||||
// the last part is not included it in the count
|
||||
tl->num_parts = track_count + 1 - 1;
|
||||
tl->chapters = chapters;
|
||||
tl->num_chapters = track_count;
|
||||
MP_TARRAY_APPEND(tl, tl->pars, tl->num_pars, par);
|
||||
tl->meta = par->track_layout;
|
||||
tl->format = "cue";
|
||||
tl->track_layout = tl->parts[0].source;
|
||||
|
||||
out:
|
||||
talloc_free(ctx);
|
||||
@@ -272,21 +253,10 @@ static int try_open_file(struct demuxer *demuxer, enum demux_check check)
|
||||
struct priv *p = talloc_zero(demuxer, struct priv);
|
||||
demuxer->priv = p;
|
||||
demuxer->fully_read = true;
|
||||
p->opts = mp_get_config_group(p, demuxer->global, &demux_cue_conf);
|
||||
struct demux_cue_opts *cue_opts = p->opts;
|
||||
|
||||
bstr data = stream_read_complete(s, p, 1000000);
|
||||
if (data.start == NULL)
|
||||
return -1;
|
||||
const char *charset = mp_charset_guess(p, demuxer->log, data, cue_opts->cue_cp, 0);
|
||||
if (charset && !mp_charset_is_utf8(charset)) {
|
||||
MP_INFO(demuxer, "Using CUE charset: %s\n", charset);
|
||||
bstr utf8 = mp_iconv_to_utf8(demuxer->log, data, charset, MP_ICONV_VERBOSE);
|
||||
if (utf8.start && utf8.start != data.start) {
|
||||
ta_steal(data.start, utf8.start);
|
||||
data = utf8;
|
||||
}
|
||||
}
|
||||
p->f = mp_parse_cue(data);
|
||||
talloc_steal(p, p->f);
|
||||
if (!p->f) {
|
||||
@@ -294,8 +264,6 @@ static int try_open_file(struct demuxer *demuxer, enum demux_check check)
|
||||
return -1;
|
||||
}
|
||||
|
||||
demux_close_stream(demuxer);
|
||||
|
||||
mp_tags_merge(demuxer->metadata, p->f->tags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
+47
-29
@@ -177,15 +177,9 @@ static void d_seek(demuxer_t *demuxer, double seek_pts, int flags)
|
||||
|
||||
MP_VERBOSE(demuxer, "seek to: %f\n", seek_pts);
|
||||
|
||||
// Supposed to induce a seek reset. Does it even work? I don't know.
|
||||
// It will log some bogus error messages, since the demuxer will try a
|
||||
// low level seek, which will obviously not work. But it will probably
|
||||
// clear its internal buffers.
|
||||
demux_seek(p->slave, 0, SEEK_FACTOR | SEEK_FORCE);
|
||||
stream_drop_buffers(demuxer->stream);
|
||||
|
||||
double seek_arg[] = {seek_pts, flags};
|
||||
stream_control(demuxer->stream, STREAM_CTRL_SEEK_TO_TIME, seek_arg);
|
||||
demux_control(p->slave, DEMUXER_CTRL_RESYNC, NULL);
|
||||
|
||||
p->seek_reinit = true;
|
||||
}
|
||||
@@ -205,15 +199,15 @@ static void reset_pts(demuxer_t *demuxer)
|
||||
p->seek_reinit = false;
|
||||
}
|
||||
|
||||
static bool d_read_packet(struct demuxer *demuxer, struct demux_packet **out_pkt)
|
||||
static int d_fill_buffer(demuxer_t *demuxer)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
|
||||
struct demux_packet *pkt = demux_read_any_packet(p->slave);
|
||||
if (!pkt)
|
||||
return false;
|
||||
return 0;
|
||||
|
||||
demux_update(p->slave, MP_NOPTS_VALUE);
|
||||
demux_update(p->slave);
|
||||
|
||||
if (p->seek_reinit)
|
||||
reset_pts(demuxer);
|
||||
@@ -221,20 +215,18 @@ static bool d_read_packet(struct demuxer *demuxer, struct demux_packet **out_pkt
|
||||
add_streams(demuxer);
|
||||
if (pkt->stream >= p->num_streams) { // out of memory?
|
||||
talloc_free(pkt);
|
||||
return true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct sh_stream *sh = p->streams[pkt->stream];
|
||||
if (!demux_stream_is_selected(sh)) {
|
||||
talloc_free(pkt);
|
||||
return true;
|
||||
return 1;
|
||||
}
|
||||
|
||||
pkt->stream = sh->index;
|
||||
|
||||
if (p->is_cdda) {
|
||||
*out_pkt = pkt;
|
||||
return true;
|
||||
demux_add_packet(sh, pkt);
|
||||
return 1;
|
||||
}
|
||||
|
||||
MP_TRACE(demuxer, "ipts: %d %f %f\n", sh->type, pkt->pts, pkt->dts);
|
||||
@@ -269,7 +261,7 @@ static bool d_read_packet(struct demuxer *demuxer, struct demux_packet **out_pkt
|
||||
|
||||
MP_TRACE(demuxer, "opts: %d %f %f\n", sh->type, pkt->pts, pkt->dts);
|
||||
|
||||
*out_pkt = pkt;
|
||||
demux_add_packet(sh, pkt);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -293,15 +285,15 @@ static int d_open(demuxer_t *demuxer, enum demux_check check)
|
||||
if (check != DEMUX_CHECK_FORCE)
|
||||
return -1;
|
||||
|
||||
struct demuxer_params params = {
|
||||
.force_format = "+lavf",
|
||||
.external_stream = demuxer->stream,
|
||||
};
|
||||
struct demuxer_params params = {.force_format = "+lavf"};
|
||||
|
||||
struct stream *cur = demuxer->stream;
|
||||
const char *sname = "";
|
||||
if (cur->info)
|
||||
sname = cur->info->name;
|
||||
while (cur) {
|
||||
if (cur->info)
|
||||
sname = cur->info->name;
|
||||
cur = cur->underlying; // down the caching chain
|
||||
}
|
||||
|
||||
p->is_cdda = strcmp(sname, "cdda") == 0;
|
||||
p->is_dvd = strcmp(sname, "dvd") == 0 ||
|
||||
@@ -324,12 +316,23 @@ static int d_open(demuxer_t *demuxer, enum demux_check check)
|
||||
stream_peek(demuxer->stream, 1);
|
||||
reset_pts(demuxer);
|
||||
|
||||
p->slave = demux_open_url("-", ¶ms, demuxer->cancel, demuxer->global);
|
||||
p->slave = demux_open(demuxer->stream, ¶ms, demuxer->global);
|
||||
if (!p->slave)
|
||||
return -1;
|
||||
|
||||
// Can be seekable even if the stream isn't.
|
||||
demuxer->seekable = true;
|
||||
// So that we don't miss initial packets of delayed subtitle streams.
|
||||
demux_set_stream_autoselect(p->slave, true);
|
||||
|
||||
// With cache enabled, the stream can be seekable. This causes demux_lavf.c
|
||||
// (actually libavformat/mpegts.c) to seek sometimes when reading a packet.
|
||||
// It does this to seek back a bit in case the current file position points
|
||||
// into the middle of a packet.
|
||||
if (!p->is_cdda) {
|
||||
demuxer->stream->seekable = false;
|
||||
|
||||
// Can be seekable even if the stream isn't.
|
||||
demuxer->seekable = true;
|
||||
}
|
||||
|
||||
add_dvd_streams(demuxer);
|
||||
add_streams(demuxer);
|
||||
@@ -345,15 +348,30 @@ static int d_open(demuxer_t *demuxer, enum demux_check check)
|
||||
static void d_close(demuxer_t *demuxer)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
demux_free(p->slave);
|
||||
free_demuxer(p->slave);
|
||||
}
|
||||
|
||||
static int d_control(demuxer_t *demuxer, int cmd, void *arg)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
|
||||
switch (cmd) {
|
||||
case DEMUXER_CTRL_RESYNC:
|
||||
demux_flush(p->slave);
|
||||
break; // relay to slave demuxer
|
||||
case DEMUXER_CTRL_SWITCHED_TRACKS:
|
||||
reselect_streams(demuxer);
|
||||
return CONTROL_OK;
|
||||
}
|
||||
return demux_control(p->slave, cmd, arg);
|
||||
}
|
||||
|
||||
const demuxer_desc_t demuxer_desc_disc = {
|
||||
.name = "disc",
|
||||
.desc = "CD/DVD/BD wrapper",
|
||||
.read_packet = d_read_packet,
|
||||
.fill_buffer = d_fill_buffer,
|
||||
.open = d_open,
|
||||
.close = d_close,
|
||||
.seek = d_seek,
|
||||
.switched_tracks = reselect_streams,
|
||||
.control = d_control,
|
||||
};
|
||||
+91
-177
@@ -40,23 +40,14 @@ struct tl_part {
|
||||
double offset; // offset into the source file
|
||||
bool offset_set;
|
||||
bool chapter_ts;
|
||||
bool is_layout;
|
||||
double length; // length of the part (-1 if rest of the file)
|
||||
char *title;
|
||||
};
|
||||
|
||||
struct tl_parts {
|
||||
bool disable_chapters;
|
||||
bool dash, no_clip;
|
||||
bool dash;
|
||||
char *init_fragment_url;
|
||||
struct tl_part *parts;
|
||||
int num_parts;
|
||||
struct tl_parts *next;
|
||||
};
|
||||
|
||||
struct tl_root {
|
||||
struct tl_parts **pars;
|
||||
int num_pars;
|
||||
};
|
||||
|
||||
struct priv {
|
||||
@@ -76,12 +67,7 @@ static bool parse_time(bstr str, double *out_time)
|
||||
return true;
|
||||
}
|
||||
|
||||
static struct tl_parts *add_part(struct tl_root *root)
|
||||
{
|
||||
struct tl_parts *tl = talloc_zero(root, struct tl_parts);
|
||||
MP_TARRAY_APPEND(root, root->pars, root->num_pars, tl);
|
||||
return tl;
|
||||
}
|
||||
#define MAX_PARAMS 10
|
||||
|
||||
/* Returns a list of parts, or NULL on parse error.
|
||||
* Syntax (without file header or URI prefix):
|
||||
@@ -89,21 +75,18 @@ static struct tl_parts *add_part(struct tl_root *root)
|
||||
* entry ::= <param> ( <param> ',' )*
|
||||
* param ::= [<string> '='] (<string> | '%' <number> '%' <bytes>)
|
||||
*/
|
||||
static struct tl_root *parse_edl(bstr str)
|
||||
static struct tl_parts *parse_edl(bstr str)
|
||||
{
|
||||
struct tl_root *root = talloc_zero(NULL, struct tl_root);
|
||||
struct tl_parts *tl = add_part(root);
|
||||
struct tl_parts *tl = talloc_zero(NULL, struct tl_parts);
|
||||
while (str.len) {
|
||||
if (bstr_eatstart0(&str, "#")) {
|
||||
if (bstr_eatstart0(&str, "#"))
|
||||
bstr_split_tok(str, "\n", &(bstr){0}, &str);
|
||||
continue;
|
||||
}
|
||||
if (bstr_eatstart0(&str, "\n") || bstr_eatstart0(&str, ";"))
|
||||
continue;
|
||||
bool is_header = bstr_eatstart0(&str, "!");
|
||||
bstr f_type = {0};
|
||||
bstr f_init = {0};
|
||||
struct tl_part p = { .length = -1 };
|
||||
bstr param_names[MAX_PARAMS];
|
||||
bstr param_vals[MAX_PARAMS];
|
||||
int nparam = 0;
|
||||
while (1) {
|
||||
bstr name, val;
|
||||
@@ -112,9 +95,6 @@ static struct tl_root *parse_edl(bstr str)
|
||||
if (next > 0 && next < str.len && str.start[next] == '=') {
|
||||
name = bstr_splice(str, 0, next);
|
||||
str = bstr_cut(str, next + 1);
|
||||
} else if (is_header) {
|
||||
const char *names[] = {"type"}; // implied name
|
||||
name = bstr0(nparam < 1 ? names[nparam] : "-");
|
||||
} else {
|
||||
const char *names[] = {"file", "start", "length"}; // implied name
|
||||
name = bstr0(nparam < 3 ? names[nparam] : "-");
|
||||
@@ -131,85 +111,65 @@ static struct tl_root *parse_edl(bstr str)
|
||||
str = bstr_cut(str, next);
|
||||
}
|
||||
// Interpret parameters. Explicitly ignore unknown ones.
|
||||
if (is_header) {
|
||||
if (bstr_equals0(name, "type")) {
|
||||
f_type = val;
|
||||
} else if (bstr_equals0(name, "init")) {
|
||||
f_init = val;
|
||||
}
|
||||
} else {
|
||||
if (bstr_equals0(name, "file")) {
|
||||
p.filename = bstrto0(tl, val);
|
||||
} else if (bstr_equals0(name, "start")) {
|
||||
if (!parse_time(val, &p.offset))
|
||||
goto error;
|
||||
p.offset_set = true;
|
||||
} else if (bstr_equals0(name, "length")) {
|
||||
if (!parse_time(val, &p.length))
|
||||
goto error;
|
||||
} else if (bstr_equals0(name, "timestamps")) {
|
||||
if (bstr_equals0(val, "chapters"))
|
||||
p.chapter_ts = true;
|
||||
} else if (bstr_equals0(name, "title")) {
|
||||
p.title = bstrto0(tl, val);
|
||||
} else if (bstr_equals0(name, "layout")) {
|
||||
if (bstr_equals0(val, "this"))
|
||||
p.is_layout = true;
|
||||
}
|
||||
if (bstr_equals0(name, "file")) {
|
||||
p.filename = bstrto0(tl, val);
|
||||
} else if (bstr_equals0(name, "start")) {
|
||||
if (!parse_time(val, &p.offset))
|
||||
goto error;
|
||||
p.offset_set = true;
|
||||
} else if (bstr_equals0(name, "length")) {
|
||||
if (!parse_time(val, &p.length))
|
||||
goto error;
|
||||
} else if (bstr_equals0(name, "timestamps")) {
|
||||
if (bstr_equals0(val, "chapters"))
|
||||
p.chapter_ts = true;
|
||||
}
|
||||
if (nparam >= MAX_PARAMS)
|
||||
goto error;
|
||||
param_names[nparam] = name;
|
||||
param_vals[nparam] = val;
|
||||
nparam++;
|
||||
if (!bstr_eatstart0(&str, ","))
|
||||
break;
|
||||
}
|
||||
if (is_header) {
|
||||
if (bstr_equals0(f_type, "mp4_dash")) {
|
||||
if (tl->num_parts)
|
||||
goto error; // can't have header once an entry was defined
|
||||
bstr type = param_vals[0]; // value, because no "="
|
||||
if (bstr_equals0(type, "mp4_dash")) {
|
||||
tl->dash = true;
|
||||
if (f_init.len)
|
||||
tl->init_fragment_url = bstrto0(tl, f_init);
|
||||
} else if (bstr_equals0(f_type, "no_clip")) {
|
||||
tl->no_clip = true;
|
||||
} else if (bstr_equals0(f_type, "new_stream")) {
|
||||
tl = add_part(root);
|
||||
} else if (bstr_equals0(f_type, "no_chapters")) {
|
||||
tl->disable_chapters = true;
|
||||
} else {
|
||||
goto error;
|
||||
if (nparam > 1 && bstr_equals0(param_names[1], "init"))
|
||||
tl->init_fragment_url = bstrto0(tl, param_vals[1]);
|
||||
}
|
||||
} else {
|
||||
if (!p.filename)
|
||||
goto error;
|
||||
MP_TARRAY_APPEND(tl, tl->parts, tl->num_parts, p);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (!root->num_pars)
|
||||
goto error;
|
||||
for (int n = 0; n < root->num_pars; n++) {
|
||||
if (root->pars[n]->num_parts < 1)
|
||||
if (!p.filename)
|
||||
goto error;
|
||||
MP_TARRAY_APPEND(tl, tl->parts, tl->num_parts, p);
|
||||
}
|
||||
return root;
|
||||
if (!tl->num_parts)
|
||||
goto error;
|
||||
return tl;
|
||||
error:
|
||||
talloc_free(root);
|
||||
talloc_free(tl);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct demuxer *open_source(struct timeline *root,
|
||||
struct timeline_par *tl, char *filename)
|
||||
static struct demuxer *open_source(struct timeline *tl, char *filename)
|
||||
{
|
||||
for (int n = 0; n < tl->num_parts; n++) {
|
||||
struct demuxer *d = tl->parts[n].source;
|
||||
if (d && strcmp(d->stream->url, filename) == 0)
|
||||
for (int n = 0; n < tl->num_sources; n++) {
|
||||
struct demuxer *d = tl->sources[n];
|
||||
if (strcmp(d->stream->url, filename) == 0)
|
||||
return d;
|
||||
}
|
||||
struct demuxer_params params = {
|
||||
.init_fragment = tl->init_fragment,
|
||||
};
|
||||
struct demuxer *d = demux_open_url(filename, ¶ms, root->cancel,
|
||||
root->global);
|
||||
struct demuxer *d = demux_open_url(filename, ¶ms, tl->cancel, tl->global);
|
||||
if (d) {
|
||||
MP_TARRAY_APPEND(root, root->sources, root->num_sources, d);
|
||||
MP_TARRAY_APPEND(tl, tl->sources, tl->num_sources, d);
|
||||
} else {
|
||||
MP_ERR(root, "EDL: Could not open source file '%s'.\n", filename);
|
||||
MP_ERR(tl, "EDL: Could not open source file '%s'.\n", filename);
|
||||
}
|
||||
return d;
|
||||
}
|
||||
@@ -256,43 +216,32 @@ static void resolve_timestamps(struct tl_part *part, struct demuxer *demuxer)
|
||||
part->offset = demuxer->start_time;
|
||||
}
|
||||
|
||||
static struct timeline_par *build_timeline(struct timeline *root,
|
||||
struct tl_parts *parts)
|
||||
static void build_timeline(struct timeline *tl, struct tl_parts *parts)
|
||||
{
|
||||
struct timeline_par *tl = talloc_zero(root, struct timeline_par);
|
||||
MP_TARRAY_APPEND(root, root->pars, root->num_pars, tl);
|
||||
|
||||
tl->track_layout = NULL;
|
||||
tl->dash = parts->dash;
|
||||
tl->no_clip = parts->no_clip;
|
||||
|
||||
if (parts->init_fragment_url && parts->init_fragment_url[0]) {
|
||||
MP_VERBOSE(root, "Opening init fragment...\n");
|
||||
MP_VERBOSE(tl, "Opening init fragment...\n");
|
||||
stream_t *s = stream_create(parts->init_fragment_url, STREAM_READ,
|
||||
root->cancel, root->global);
|
||||
if (s) {
|
||||
root->is_network |= s->is_network;
|
||||
root->is_streaming |= s->streaming;
|
||||
tl->cancel, tl->global);
|
||||
if (s)
|
||||
tl->init_fragment = stream_read_complete(s, tl, 1000000);
|
||||
}
|
||||
free_stream(s);
|
||||
if (!tl->init_fragment.len) {
|
||||
MP_ERR(root, "Could not read init fragment.\n");
|
||||
MP_ERR(tl, "Could not read init fragment.\n");
|
||||
goto error;
|
||||
}
|
||||
struct demuxer_params params = {
|
||||
.init_fragment = tl->init_fragment,
|
||||
};
|
||||
tl->track_layout = demux_open_url("memory://", ¶ms, root->cancel,
|
||||
root->global);
|
||||
s = open_memory_stream(tl->init_fragment.start, tl->init_fragment.len);
|
||||
tl->track_layout = demux_open(s, NULL, tl->global);
|
||||
if (!tl->track_layout) {
|
||||
MP_ERR(root, "Could not demux init fragment.\n");
|
||||
free_stream(s);
|
||||
MP_ERR(tl, "Could not demux init fragment.\n");
|
||||
goto error;
|
||||
}
|
||||
MP_TARRAY_APPEND(root, root->sources, root->num_sources, tl->track_layout);
|
||||
}
|
||||
|
||||
tl->parts = talloc_array_ptrtype(tl, tl->parts, parts->num_parts);
|
||||
tl->parts = talloc_array_ptrtype(tl, tl->parts, parts->num_parts + 1);
|
||||
double starttime = 0;
|
||||
for (int n = 0; n < parts->num_parts; n++) {
|
||||
struct tl_part *part = &parts->parts[n];
|
||||
@@ -301,16 +250,20 @@ static struct timeline_par *build_timeline(struct timeline *root,
|
||||
if (tl->dash) {
|
||||
part->offset = starttime;
|
||||
if (part->length <= 0)
|
||||
MP_WARN(root, "Segment %d has unknown duration.\n", n);
|
||||
MP_WARN(tl, "Segment %d has unknown duration.\n", n);
|
||||
if (part->offset_set)
|
||||
MP_WARN(root, "Offsets are ignored.\n");
|
||||
MP_WARN(tl, "Offsets are ignored.\n");
|
||||
tl->demuxer->is_network = true;
|
||||
|
||||
if (!tl->track_layout)
|
||||
tl->track_layout = open_source(root, tl, part->filename);
|
||||
if (!tl->track_layout) {
|
||||
source = open_source(tl, part->filename);
|
||||
if (!source)
|
||||
goto error;
|
||||
}
|
||||
} else {
|
||||
MP_VERBOSE(root, "Opening segment %d...\n", n);
|
||||
MP_VERBOSE(tl, "Opening segment %d...\n", n);
|
||||
|
||||
source = open_source(root, tl, part->filename);
|
||||
source = open_source(tl, part->filename);
|
||||
if (!source)
|
||||
goto error;
|
||||
|
||||
@@ -324,7 +277,7 @@ static struct timeline_par *build_timeline(struct timeline *root,
|
||||
// something up.
|
||||
if (part->length < 0) {
|
||||
if (end_time < 0) {
|
||||
MP_WARN(root, "EDL: source file '%s' has unknown duration.\n",
|
||||
MP_WARN(tl, "EDL: source file '%s' has unknown duration.\n",
|
||||
part->filename);
|
||||
end_time = 1;
|
||||
}
|
||||
@@ -332,65 +285,48 @@ static struct timeline_par *build_timeline(struct timeline *root,
|
||||
} else if (end_time >= 0) {
|
||||
double end_part = part->offset + part->length;
|
||||
if (end_part > end_time) {
|
||||
MP_WARN(root, "EDL: entry %d uses %f "
|
||||
MP_WARN(tl, "EDL: entry %d uses %f "
|
||||
"seconds, but file has only %f seconds.\n",
|
||||
n, end_part, end_time);
|
||||
}
|
||||
}
|
||||
|
||||
if (!parts->disable_chapters) {
|
||||
// Add a chapter between each file.
|
||||
struct demux_chapter ch = {
|
||||
.pts = starttime,
|
||||
.metadata = talloc_zero(tl, struct mp_tags),
|
||||
};
|
||||
mp_tags_set_str(ch.metadata, "title",
|
||||
part->title ? part->title : part->filename);
|
||||
MP_TARRAY_APPEND(root, root->chapters, root->num_chapters, ch);
|
||||
// Add a chapter between each file.
|
||||
struct demux_chapter ch = {
|
||||
.pts = starttime,
|
||||
.metadata = talloc_zero(tl, struct mp_tags),
|
||||
};
|
||||
mp_tags_set_str(ch.metadata, "title", part->filename);
|
||||
MP_TARRAY_APPEND(tl, tl->chapters, tl->num_chapters, ch);
|
||||
|
||||
// Also copy the source file's chapters for the relevant parts
|
||||
copy_chapters(&root->chapters, &root->num_chapters, source,
|
||||
part->offset, part->length, starttime);
|
||||
}
|
||||
// Also copy the source file's chapters for the relevant parts
|
||||
copy_chapters(&tl->chapters, &tl->num_chapters, source, part->offset,
|
||||
part->length, starttime);
|
||||
}
|
||||
|
||||
tl->parts[n] = (struct timeline_part) {
|
||||
.start = starttime,
|
||||
.end = starttime + part->length,
|
||||
.source_start = part->offset,
|
||||
.source = source,
|
||||
.url = talloc_strdup(tl, part->filename),
|
||||
};
|
||||
|
||||
starttime = tl->parts[n].end;
|
||||
starttime += part->length;
|
||||
|
||||
if (source && !tl->track_layout && part->is_layout)
|
||||
tl->track_layout = source;
|
||||
}
|
||||
if (source) {
|
||||
tl->demuxer->is_network |= source->is_network;
|
||||
|
||||
if (!tl->track_layout) {
|
||||
// Use a heuristic to select the "broadest" part as layout.
|
||||
for (int n = 0; n < parts->num_parts; n++) {
|
||||
struct demuxer *s = tl->parts[n].source;
|
||||
if (!s)
|
||||
continue;
|
||||
if (!tl->track_layout ||
|
||||
demux_get_num_stream(s) > demux_get_num_stream(tl->track_layout))
|
||||
tl->track_layout = s;
|
||||
if (!tl->track_layout)
|
||||
tl->track_layout = source;
|
||||
}
|
||||
}
|
||||
|
||||
if (!tl->track_layout)
|
||||
goto error;
|
||||
if (!root->meta)
|
||||
root->meta = tl->track_layout;
|
||||
|
||||
tl->parts[parts->num_parts] = (struct timeline_part) {.start = starttime};
|
||||
tl->num_parts = parts->num_parts;
|
||||
return tl;
|
||||
return;
|
||||
|
||||
error:
|
||||
root->num_pars = 0;
|
||||
return NULL;
|
||||
tl->num_parts = 0;
|
||||
tl->num_chapters = 0;
|
||||
}
|
||||
|
||||
// For security, don't allow relative or absolute paths, only plain filenames.
|
||||
@@ -409,37 +345,16 @@ static void build_mpv_edl_timeline(struct timeline *tl)
|
||||
{
|
||||
struct priv *p = tl->demuxer->priv;
|
||||
|
||||
struct tl_root *root = parse_edl(p->data);
|
||||
if (!root) {
|
||||
struct tl_parts *parts = parse_edl(p->data);
|
||||
if (!parts) {
|
||||
MP_ERR(tl, "Error in EDL.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
bool all_dash = true;
|
||||
bool all_no_clip = true;
|
||||
bool all_single = true;
|
||||
|
||||
for (int n = 0; n < root->num_pars; n++) {
|
||||
struct tl_parts *parts = root->pars[n];
|
||||
if (!p->allow_any)
|
||||
fix_filenames(parts, tl->demuxer->filename);
|
||||
struct timeline_par *par = build_timeline(tl, parts);
|
||||
if (!par)
|
||||
break;
|
||||
all_dash &= par->dash;
|
||||
all_no_clip &= par->no_clip;
|
||||
all_single &= par->num_parts == 1;
|
||||
}
|
||||
|
||||
if (all_dash) {
|
||||
tl->format = "dash";
|
||||
} else if (all_no_clip && all_single) {
|
||||
tl->format = "multi";
|
||||
} else {
|
||||
tl->format = "edl";
|
||||
}
|
||||
|
||||
talloc_free(root);
|
||||
MP_TARRAY_APPEND(tl, tl->sources, tl->num_sources, tl->demuxer);
|
||||
if (!p->allow_any)
|
||||
fix_filenames(parts, tl->demuxer->filename);
|
||||
build_timeline(tl, parts);
|
||||
talloc_free(parts);
|
||||
}
|
||||
|
||||
static int try_open_file(struct demuxer *demuxer, enum demux_check check)
|
||||
@@ -466,7 +381,6 @@ static int try_open_file(struct demuxer *demuxer, enum demux_check check)
|
||||
if (p->data.start == NULL)
|
||||
return -1;
|
||||
bstr_eatstart0(&p->data, HEADER);
|
||||
demux_close_stream(demuxer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
+185
-293
@@ -33,6 +33,7 @@
|
||||
#include <libavutil/avstring.h>
|
||||
#include <libavutil/mathematics.h>
|
||||
#include <libavutil/replaygain.h>
|
||||
#include <libavutil/spherical.h>
|
||||
#include <libavutil/display.h>
|
||||
#include <libavutil/opt.h>
|
||||
|
||||
@@ -41,7 +42,6 @@
|
||||
#include "common/av_common.h"
|
||||
#include "misc/bstr.h"
|
||||
#include "misc/charset_conv.h"
|
||||
#include "misc/thread_tools.h"
|
||||
|
||||
#include "stream/stream.h"
|
||||
#include "demux.h"
|
||||
@@ -58,7 +58,7 @@
|
||||
#endif
|
||||
|
||||
#define INITIAL_PROBE_SIZE STREAM_BUFFER_SIZE
|
||||
#define PROBE_BUF_SIZE (10 * 1024 * 1024)
|
||||
#define PROBE_BUF_SIZE FFMIN(STREAM_MAX_BUFFER_SIZE, 2 * 1024 * 1024)
|
||||
|
||||
|
||||
// Should correspond to IO_BUFFER_SIZE in libavformat/aviobuf.c (not public)
|
||||
@@ -78,7 +78,6 @@ struct demux_lavf_opts {
|
||||
int hacks;
|
||||
char *sub_cp;
|
||||
int rtsp_transport;
|
||||
int linearize_ts;
|
||||
};
|
||||
|
||||
const struct m_sub_options demux_lavf_conf = {
|
||||
@@ -102,8 +101,6 @@ const struct m_sub_options demux_lavf_conf = {
|
||||
{"udp", 1},
|
||||
{"tcp", 2},
|
||||
{"http", 3})),
|
||||
OPT_CHOICE("demuxer-lavf-linearize-timestamps", linearize_ts, 0,
|
||||
({"no", 0}, {"auto", -1}, {"yes", 1})),
|
||||
{0}
|
||||
},
|
||||
.size = sizeof(struct demux_lavf_opts),
|
||||
@@ -117,7 +114,6 @@ const struct m_sub_options demux_lavf_conf = {
|
||||
.probescore = AVPROBE_SCORE_MAX/4 + 1,
|
||||
.sub_cp = "auto",
|
||||
.rtsp_transport = 2,
|
||||
.linearize_ts = -1,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -138,11 +134,10 @@ struct format_hack {
|
||||
// Do not confuse player's position estimation (position is into external
|
||||
// segment, with e.g. HLS, player knows about the playlist main file only).
|
||||
bool clear_filepos : 1;
|
||||
bool linearize_audio_ts : 1;// compensate timestamp resets (audio only)
|
||||
bool ignore_start : 1;
|
||||
bool fix_editlists : 1;
|
||||
bool is_network : 1;
|
||||
bool no_seek : 1;
|
||||
bool no_pcm_seek : 1;
|
||||
};
|
||||
|
||||
#define BLACKLIST(fmt) {fmt, .ignore = true}
|
||||
@@ -164,8 +159,8 @@ static const struct format_hack format_hacks[] = {
|
||||
{"mpeg", .use_stream_ids = true},
|
||||
{"mpegts", .use_stream_ids = true},
|
||||
|
||||
{"mp4", .skipinfo = true, .fix_editlists = true, .no_pcm_seek = true},
|
||||
{"matroska", .skipinfo = true, .no_pcm_seek = true},
|
||||
{"mp4", .skipinfo = true, .fix_editlists = true},
|
||||
{"matroska", .skipinfo = true},
|
||||
|
||||
{"v4l2", .no_seek = true},
|
||||
|
||||
@@ -173,9 +168,8 @@ static const struct format_hack format_hacks[] = {
|
||||
{"h264", .if_flags = AVFMT_NOTIMESTAMPS },
|
||||
{"hevc", .if_flags = AVFMT_NOTIMESTAMPS },
|
||||
|
||||
// Some Ogg shoutcast streams are essentially concatenated OGG files. They
|
||||
// reset timestamps, which causes all sorts of problems.
|
||||
{"ogg", .linearize_audio_ts = true},
|
||||
// Rebasing start time to 0 is very weird with ogg shoutcast streams.
|
||||
{"ogg", .ignore_start = true},
|
||||
|
||||
TEXTSUB("aqtitle"), TEXTSUB("jacosub"), TEXTSUB("microdvd"),
|
||||
TEXTSUB("mpl2"), TEXTSUB("mpsub"), TEXTSUB("pjs"), TEXTSUB("realtext"),
|
||||
@@ -196,18 +190,6 @@ static const struct format_hack format_hacks[] = {
|
||||
{0}
|
||||
};
|
||||
|
||||
struct nested_stream {
|
||||
AVIOContext *id;
|
||||
int64_t last_bytes;
|
||||
};
|
||||
|
||||
struct stream_info {
|
||||
struct sh_stream *sh;
|
||||
double last_key_pts;
|
||||
double highest_pts;
|
||||
double ts_offset;
|
||||
};
|
||||
|
||||
typedef struct lavf_priv {
|
||||
struct stream *stream;
|
||||
bool own_stream;
|
||||
@@ -216,50 +198,19 @@ typedef struct lavf_priv {
|
||||
AVInputFormat *avif;
|
||||
int avif_flags;
|
||||
AVFormatContext *avfc;
|
||||
bstr init_fragment;
|
||||
int64_t stream_pos;
|
||||
AVIOContext *pb;
|
||||
struct stream_info **streams; // NULL for unknown streams
|
||||
struct sh_stream **streams; // NULL for unknown streams
|
||||
int num_streams;
|
||||
int cur_program;
|
||||
char *mime_type;
|
||||
double seek_delay;
|
||||
|
||||
struct demux_lavf_opts *opts;
|
||||
double mf_fps;
|
||||
|
||||
bool pcm_seek_hack_disabled;
|
||||
AVStream *pcm_seek_hack;
|
||||
int pcm_seek_hack_packet_size;
|
||||
|
||||
int linearize_ts;
|
||||
bool any_ts_fixed;
|
||||
|
||||
// Proxying nested streams.
|
||||
struct nested_stream *nested;
|
||||
int num_nested;
|
||||
int (*default_io_open)(struct AVFormatContext *s, AVIOContext **pb,
|
||||
const char *url, int flags, AVDictionary **options);
|
||||
void (*default_io_close)(struct AVFormatContext *s, AVIOContext *pb);
|
||||
} lavf_priv_t;
|
||||
|
||||
static void update_read_stats(struct demuxer *demuxer)
|
||||
{
|
||||
lavf_priv_t *priv = demuxer->priv;
|
||||
|
||||
for (int n = 0; n < priv->num_nested; n++) {
|
||||
struct nested_stream *nest = &priv->nested[n];
|
||||
|
||||
#if !HAVE_FFMPEG_STRICT_ABI
|
||||
// Note: accessing the bytes_read field is not allowed by FFmpeg's API.
|
||||
// This is fully intentional - there is no other way to get this
|
||||
// information (not even by custom I/O, because the connection reuse
|
||||
// mechanism by the HLS demuxer would get disabled).
|
||||
int64_t cur = nest->id->bytes_read;
|
||||
int64_t new = cur - nest->last_bytes;
|
||||
nest->last_bytes = cur;
|
||||
demux_report_unbuffered_read_bytes(demuxer, new);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// At least mp4 has name="mov,mp4,m4a,3gp,3g2,mj2", so we split the name
|
||||
// on "," in general.
|
||||
static bool matches_avinputformat_name(struct lavf_priv *priv,
|
||||
@@ -282,8 +233,16 @@ static int mp_read(void *opaque, uint8_t *buf, int size)
|
||||
struct demuxer *demuxer = opaque;
|
||||
lavf_priv_t *priv = demuxer->priv;
|
||||
struct stream *stream = priv->stream;
|
||||
int ret;
|
||||
|
||||
int ret = stream_read_partial(stream, buf, size);
|
||||
if (priv->stream_pos < priv->init_fragment.len) {
|
||||
ret = MPMIN(size, priv->init_fragment.len - priv->stream_pos);
|
||||
memcpy(buf, priv->init_fragment.start + priv->stream_pos, ret);
|
||||
priv->stream_pos += ret;
|
||||
} else {
|
||||
ret = stream_read_partial(stream, buf, size);
|
||||
priv->stream_pos = priv->init_fragment.len + stream_tell(stream);
|
||||
}
|
||||
|
||||
MP_TRACE(demuxer, "%d=mp_read(%p, %p, %d), pos: %"PRId64", eof:%d\n",
|
||||
ret, stream, buf, size, stream_tell(stream), stream->eof);
|
||||
@@ -304,11 +263,12 @@ static int64_t mp_seek(void *opaque, int64_t pos, int whence)
|
||||
int64_t end = stream_get_size(stream);
|
||||
if (end < 0)
|
||||
return -1;
|
||||
end += priv->init_fragment.len;
|
||||
if (whence == AVSEEK_SIZE)
|
||||
return end;
|
||||
pos += end;
|
||||
} else if (whence == SEEK_CUR) {
|
||||
pos += stream_tell(stream);
|
||||
pos += priv->stream_pos;
|
||||
} else if (whence != SEEK_SET) {
|
||||
return -1;
|
||||
}
|
||||
@@ -316,12 +276,23 @@ static int64_t mp_seek(void *opaque, int64_t pos, int whence)
|
||||
if (pos < 0)
|
||||
return -1;
|
||||
|
||||
int64_t stream_target = pos - priv->init_fragment.len;
|
||||
bool seek_before = stream_target < 0;
|
||||
if (seek_before)
|
||||
stream_target = 0; // within init segment - seek real stream to 0
|
||||
|
||||
int64_t current_pos = stream_tell(stream);
|
||||
if (stream_seek(stream, pos) == 0) {
|
||||
if (stream_seek(stream, stream_target) == 0) {
|
||||
stream_seek(stream, current_pos);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (seek_before) {
|
||||
priv->stream_pos = pos;
|
||||
} else {
|
||||
priv->stream_pos = priv->init_fragment.len + stream_tell(stream);
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
@@ -377,7 +348,7 @@ static void convert_charset(struct demuxer *demuxer)
|
||||
data = conv;
|
||||
}
|
||||
if (data.start) {
|
||||
priv->stream = stream_memory_open(demuxer->global, data.start, data.len);
|
||||
priv->stream = open_memory_stream(data.start, data.len);
|
||||
priv->own_stream = true;
|
||||
}
|
||||
talloc_free(alloc);
|
||||
@@ -459,6 +430,10 @@ static int lavf_check_file(demuxer_t *demuxer, enum demux_check check)
|
||||
int nsize = av_clip(avpd.buf_size * 2, INITIAL_PROBE_SIZE,
|
||||
PROBE_BUF_SIZE);
|
||||
bstr buf = stream_peek(s, nsize);
|
||||
if (demuxer->params && demuxer->params->init_fragment.len) {
|
||||
buf = demuxer->params->init_fragment;
|
||||
buf.len = MPMIN(buf.len, nsize);
|
||||
}
|
||||
if (buf.len <= avpd.buf_size)
|
||||
final_probe = true;
|
||||
memcpy(avpd.buf, buf.start, buf.len);
|
||||
@@ -515,10 +490,6 @@ static int lavf_check_file(demuxer_t *demuxer, enum demux_check check)
|
||||
if (lavfdopts->hacks)
|
||||
priv->avif_flags = priv->avif->flags | priv->format_hack.if_flags;
|
||||
|
||||
priv->linearize_ts = lavfdopts->linearize_ts;
|
||||
if (priv->linearize_ts < 0 && !priv->format_hack.linearize_audio_ts)
|
||||
priv->linearize_ts = 0;
|
||||
|
||||
demuxer->filetype = priv->avif->name;
|
||||
|
||||
if (priv->format_hack.detect_charset)
|
||||
@@ -568,7 +539,7 @@ static void select_tracks(struct demuxer *demuxer, int start)
|
||||
{
|
||||
lavf_priv_t *priv = demuxer->priv;
|
||||
for (int n = start; n < priv->num_streams; n++) {
|
||||
struct sh_stream *stream = priv->streams[n]->sh;
|
||||
struct sh_stream *stream = priv->streams[n];
|
||||
AVStream *st = priv->avfc->streams[n];
|
||||
bool selected = stream && demux_stream_is_selected(stream) &&
|
||||
!stream->attached_picture;
|
||||
@@ -590,27 +561,17 @@ static void export_replaygain(demuxer_t *demuxer, struct sh_stream *sh,
|
||||
av_rgain = (AVReplayGain*)src_sd->data;
|
||||
rgain = talloc_ptrtype(demuxer, rgain);
|
||||
|
||||
// Set values in *rgain, using track gain as a fallback for album gain
|
||||
// if the latter is not present. This behavior matches that in
|
||||
// demux/demux.c's decode_rgain; if you change this, please make
|
||||
// equivalent changes there too.
|
||||
if (av_rgain->track_gain != INT32_MIN && av_rgain->track_peak != 0.0) {
|
||||
// Track gain is defined.
|
||||
rgain->track_gain = av_rgain->track_gain / 100000.0f;
|
||||
rgain->track_peak = av_rgain->track_peak / 100000.0f;
|
||||
rgain->track_gain = (av_rgain->track_gain != INT32_MIN) ?
|
||||
av_rgain->track_gain / 100000.0f : 0.0;
|
||||
|
||||
if (av_rgain->album_gain != INT32_MIN &&
|
||||
av_rgain->album_peak != 0.0)
|
||||
{
|
||||
// Album gain is also defined.
|
||||
rgain->album_gain = av_rgain->album_gain / 100000.0f;
|
||||
rgain->album_peak = av_rgain->album_peak / 100000.0f;
|
||||
} else {
|
||||
// Album gain is undefined; fall back to track gain.
|
||||
rgain->album_gain = rgain->track_gain;
|
||||
rgain->album_peak = rgain->track_peak;
|
||||
}
|
||||
}
|
||||
rgain->track_peak = (av_rgain->track_peak != 0.0) ?
|
||||
av_rgain->track_peak / 100000.0f : 1.0;
|
||||
|
||||
rgain->album_gain = (av_rgain->album_gain != INT32_MIN) ?
|
||||
av_rgain->album_gain / 100000.0f : 0.0;
|
||||
|
||||
rgain->album_peak = (av_rgain->album_peak != 0.0) ?
|
||||
av_rgain->album_peak / 100000.0f : 1.0;
|
||||
|
||||
// This must be run only before the stream was added, otherwise there
|
||||
// will be race conditions with accesses from the user thread.
|
||||
@@ -659,8 +620,6 @@ static void handle_new_stream(demuxer_t *demuxer, int i)
|
||||
|
||||
export_replaygain(demuxer, sh, st);
|
||||
|
||||
sh->seek_preroll = delay;
|
||||
|
||||
break;
|
||||
}
|
||||
case AVMEDIA_TYPE_VIDEO: {
|
||||
@@ -678,15 +637,6 @@ static void handle_new_stream(demuxer_t *demuxer, int i)
|
||||
}
|
||||
}
|
||||
|
||||
if (!sh->attached_picture) {
|
||||
// A real video stream probably means it's a packet based format.
|
||||
priv->pcm_seek_hack_disabled = true;
|
||||
priv->pcm_seek_hack = NULL;
|
||||
// Also, we don't want to do this shit for ogv videos.
|
||||
if (priv->linearize_ts < 0)
|
||||
priv->linearize_ts = 0;
|
||||
}
|
||||
|
||||
sh->codec->disp_w = codec->width;
|
||||
sh->codec->disp_h = codec->height;
|
||||
if (st->avg_frame_rate.num)
|
||||
@@ -703,6 +653,17 @@ static void handle_new_stream(demuxer_t *demuxer, int i)
|
||||
sh->codec->rotate = (((int)(-r) % 360) + 360) % 360;
|
||||
}
|
||||
|
||||
sd = av_stream_get_side_data(st, AV_PKT_DATA_SPHERICAL, NULL);
|
||||
if (sd) {
|
||||
AVSphericalMapping *sp = (void *)sd;
|
||||
struct mp_spherical_params *mpsp = &sh->codec->spherical;
|
||||
mpsp->type = sp->projection == AV_SPHERICAL_EQUIRECTANGULAR ?
|
||||
MP_SPHERICAL_EQUIRECTANGULAR : MP_SPHERICAL_UNKNOWN;
|
||||
mpsp->ref_angles[0] = sp->yaw / (float)(1 << 16);
|
||||
mpsp->ref_angles[1] = sp->pitch / (float)(1 << 16);
|
||||
mpsp->ref_angles[2] = sp->roll / (float)(1 << 16);
|
||||
}
|
||||
|
||||
// This also applies to vfw-muxed mkv, but we can't detect these easily.
|
||||
sh->codec->avi_dts = matches_avinputformat_name(priv, "avi");
|
||||
|
||||
@@ -741,14 +702,8 @@ static void handle_new_stream(demuxer_t *demuxer, int i)
|
||||
default: ;
|
||||
}
|
||||
|
||||
struct stream_info *info = talloc_zero(priv, struct stream_info);
|
||||
*info = (struct stream_info){
|
||||
.sh = sh,
|
||||
.last_key_pts = MP_NOPTS_VALUE,
|
||||
.highest_pts = MP_NOPTS_VALUE,
|
||||
};
|
||||
assert(priv->num_streams == i); // directly mapped
|
||||
MP_TARRAY_APPEND(priv, priv->streams, priv->num_streams, info);
|
||||
MP_TARRAY_APPEND(priv, priv->streams, priv->num_streams, sh);
|
||||
|
||||
if (sh) {
|
||||
sh->ff_index = st->index;
|
||||
@@ -766,10 +721,6 @@ static void handle_new_stream(demuxer_t *demuxer, int i)
|
||||
sh->forced_track = true;
|
||||
if (st->disposition & AV_DISPOSITION_DEPENDENT)
|
||||
sh->dependent_track = true;
|
||||
if (st->disposition & AV_DISPOSITION_VISUAL_IMPAIRED)
|
||||
sh->visual_impaired_track = true;
|
||||
if (st->disposition & AV_DISPOSITION_HEARING_IMPAIRED)
|
||||
sh->hearing_impaired_track = true;
|
||||
if (st->disposition & AV_DISPOSITION_STILL_IMAGE)
|
||||
sh->still_image = true;
|
||||
if (priv->format_hack.use_stream_ids)
|
||||
@@ -790,25 +741,6 @@ static void handle_new_stream(demuxer_t *demuxer, int i)
|
||||
sh->missing_timestamps = !!(priv->avif_flags & AVFMT_NOTIMESTAMPS);
|
||||
mp_tags_copy_from_av_dictionary(sh->tags, st->metadata);
|
||||
demux_add_sh_stream(demuxer, sh);
|
||||
|
||||
// Unfortunately, there is no better way to detect PCM codecs, other
|
||||
// than listing them all manually. (Or other "frameless" codecs. Or
|
||||
// rather, codecs with frames so small libavformat will put multiple of
|
||||
// them into a single packet, but not preserve these artificial packet
|
||||
// boundaries on seeking.)
|
||||
if (sh->codec->codec && strncmp(sh->codec->codec, "pcm_", 4) == 0 &&
|
||||
codec->block_align && !priv->pcm_seek_hack_disabled &&
|
||||
priv->opts->hacks && !priv->format_hack.no_pcm_seek &&
|
||||
st->time_base.num == 1 && st->time_base.den == codec->sample_rate)
|
||||
{
|
||||
if (priv->pcm_seek_hack) {
|
||||
// More than 1 audio stream => usually doesn't apply.
|
||||
priv->pcm_seek_hack_disabled = true;
|
||||
priv->pcm_seek_hack = NULL;
|
||||
} else {
|
||||
priv->pcm_seek_hack = st;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
select_tracks(demuxer, i);
|
||||
@@ -830,12 +762,23 @@ static void update_metadata(demuxer_t *demuxer)
|
||||
priv->avfc->event_flags = 0;
|
||||
demux_metadata_changed(demuxer);
|
||||
}
|
||||
|
||||
for (int n = 0; n < priv->num_streams; n++) {
|
||||
AVStream *st = priv->streams[n] ? priv->avfc->streams[n] : NULL;
|
||||
if (st && st->event_flags & AVSTREAM_EVENT_FLAG_METADATA_UPDATED) {
|
||||
st->event_flags = 0;
|
||||
struct mp_tags *tags = talloc_zero(NULL, struct mp_tags);
|
||||
mp_tags_copy_from_av_dictionary(tags, st->metadata);
|
||||
demux_set_stream_tags(demuxer, priv->streams[n], tags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int interrupt_cb(void *ctx)
|
||||
{
|
||||
struct demuxer *demuxer = ctx;
|
||||
return mp_cancel_test(demuxer->cancel);
|
||||
lavf_priv_t *priv = demuxer->priv;
|
||||
return mp_cancel_test(priv->stream->cancel);
|
||||
}
|
||||
|
||||
static int block_io_open(struct AVFormatContext *s, AVIOContext **pb,
|
||||
@@ -846,38 +789,6 @@ static int block_io_open(struct AVFormatContext *s, AVIOContext **pb,
|
||||
return AVERROR(EACCES);
|
||||
}
|
||||
|
||||
static int nested_io_open(struct AVFormatContext *s, AVIOContext **pb,
|
||||
const char *url, int flags, AVDictionary **options)
|
||||
{
|
||||
struct demuxer *demuxer = s->opaque;
|
||||
lavf_priv_t *priv = demuxer->priv;
|
||||
|
||||
int r = priv->default_io_open(s, pb, url, flags, options);
|
||||
if (r >= 0) {
|
||||
struct nested_stream nest = {
|
||||
.id = *pb,
|
||||
};
|
||||
MP_TARRAY_APPEND(priv, priv->nested, priv->num_nested, nest);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
static void nested_io_close(struct AVFormatContext *s, AVIOContext *pb)
|
||||
{
|
||||
struct demuxer *demuxer = s->opaque;
|
||||
lavf_priv_t *priv = demuxer->priv;
|
||||
|
||||
for (int n = 0; n < priv->num_nested; n++) {
|
||||
if (priv->nested[n].id == pb) {
|
||||
MP_TARRAY_REMOVE_AT(priv->nested, priv->num_nested, n);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
priv->default_io_close(s, pb);
|
||||
}
|
||||
|
||||
static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
{
|
||||
AVFormatContext *avfc;
|
||||
@@ -899,6 +810,9 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
if (lavf_check_file(demuxer, check) < 0)
|
||||
return -1;
|
||||
|
||||
if (demuxer->params)
|
||||
priv->init_fragment = bstrdup(priv, demuxer->params->init_fragment);
|
||||
|
||||
avfc = avformat_alloc_context();
|
||||
if (!avfc)
|
||||
return -1;
|
||||
@@ -969,16 +883,8 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
};
|
||||
|
||||
avfc->opaque = demuxer;
|
||||
if (demuxer->access_references) {
|
||||
priv->default_io_open = avfc->io_open;
|
||||
priv->default_io_close = avfc->io_close;
|
||||
#if !HAVE_FFMPEG_STRICT_ABI
|
||||
avfc->io_open = nested_io_open;
|
||||
avfc->io_close = nested_io_close;
|
||||
#endif
|
||||
} else {
|
||||
if (!demuxer->access_references)
|
||||
avfc->io_open = block_io_open;
|
||||
}
|
||||
|
||||
mp_set_avdict(&dopts, lavfdopts->avopts);
|
||||
|
||||
@@ -1025,7 +931,7 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
demuxer->ts_resets_possible =
|
||||
priv->avif_flags & (AVFMT_TS_DISCONT | AVFMT_NOTIMESTAMPS);
|
||||
|
||||
if (avfc->start_time != AV_NOPTS_VALUE)
|
||||
if (avfc->start_time != AV_NOPTS_VALUE && !priv->format_hack.ignore_start)
|
||||
demuxer->start_time = avfc->start_time / (double)AV_TIME_BASE;
|
||||
|
||||
demuxer->fully_read = priv->format_hack.fully_read;
|
||||
@@ -1071,59 +977,47 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
"broken as well.\n");
|
||||
}
|
||||
|
||||
if (demuxer->fully_read) {
|
||||
demux_close_stream(demuxer);
|
||||
if (priv->own_stream)
|
||||
free_stream(priv->stream);
|
||||
priv->own_stream = false;
|
||||
priv->stream = demuxer->stream;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool demux_lavf_read_packet(struct demuxer *demux,
|
||||
struct demux_packet **mp_pkt)
|
||||
static int demux_lavf_fill_buffer(demuxer_t *demux)
|
||||
{
|
||||
lavf_priv_t *priv = demux->priv;
|
||||
|
||||
AVPacket *pkt = &(AVPacket){0};
|
||||
int r = av_read_frame(priv->avfc, pkt);
|
||||
update_read_stats(demux);
|
||||
if (r < 0) {
|
||||
av_packet_unref(pkt);
|
||||
if (r == AVERROR(EAGAIN))
|
||||
return true;
|
||||
return 1;
|
||||
if (r == AVERROR_EOF)
|
||||
return false;
|
||||
return 0;
|
||||
MP_WARN(demux, "error reading packet.\n");
|
||||
return false;
|
||||
return -1;
|
||||
}
|
||||
|
||||
add_new_streams(demux);
|
||||
update_metadata(demux);
|
||||
|
||||
assert(pkt->stream_index >= 0 && pkt->stream_index < priv->num_streams);
|
||||
struct stream_info *info = priv->streams[pkt->stream_index];
|
||||
struct sh_stream *stream = info->sh;
|
||||
struct sh_stream *stream = priv->streams[pkt->stream_index];
|
||||
AVStream *st = priv->avfc->streams[pkt->stream_index];
|
||||
|
||||
if (!demux_stream_is_selected(stream)) {
|
||||
av_packet_unref(pkt);
|
||||
return true; // don't signal EOF if skipping a packet
|
||||
return 1; // don't signal EOF if skipping a packet
|
||||
}
|
||||
|
||||
struct demux_packet *dp = new_demux_packet_from_avpacket(pkt);
|
||||
if (!dp) {
|
||||
av_packet_unref(pkt);
|
||||
return true;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (priv->pcm_seek_hack == st && !priv->pcm_seek_hack_packet_size)
|
||||
priv->pcm_seek_hack_packet_size = pkt->size;
|
||||
|
||||
dp->pts = mp_pts_from_av(pkt->pts, &st->time_base);
|
||||
dp->dts = mp_pts_from_av(pkt->dts, &st->time_base);
|
||||
if (pkt->pts != AV_NOPTS_VALUE)
|
||||
dp->pts = pkt->pts * av_q2d(st->time_base);
|
||||
if (pkt->dts != AV_NOPTS_VALUE)
|
||||
dp->dts = pkt->dts * av_q2d(st->time_base);
|
||||
dp->duration = pkt->duration * av_q2d(st->time_base);
|
||||
dp->pos = pkt->pos;
|
||||
dp->keyframe = pkt->flags & AV_PKT_FLAG_KEY;
|
||||
@@ -1136,42 +1030,8 @@ static bool demux_lavf_read_packet(struct demuxer *demux,
|
||||
if (priv->format_hack.clear_filepos)
|
||||
dp->pos = -1;
|
||||
|
||||
dp->stream = stream->index;
|
||||
|
||||
if (priv->linearize_ts) {
|
||||
dp->pts = MP_ADD_PTS(dp->pts, info->ts_offset);
|
||||
dp->dts = MP_ADD_PTS(dp->dts, info->ts_offset);
|
||||
|
||||
double pts = MP_PTS_OR_DEF(dp->pts, dp->dts);
|
||||
if (pts != MP_NOPTS_VALUE) {
|
||||
if (dp->keyframe) {
|
||||
if (pts < info->highest_pts) {
|
||||
MP_WARN(demux, "Linearizing discontinuity: %f -> %f\n",
|
||||
pts, info->highest_pts);
|
||||
// Note: introduces a small discontinuity by a frame size.
|
||||
double diff = info->highest_pts - pts;
|
||||
dp->pts = MP_ADD_PTS(dp->pts, diff);
|
||||
dp->dts = MP_ADD_PTS(dp->dts, diff);
|
||||
pts += diff;
|
||||
info->ts_offset += diff;
|
||||
priv->any_ts_fixed = true;
|
||||
}
|
||||
info->last_key_pts = pts;
|
||||
}
|
||||
info->highest_pts = MP_PTS_MAX(info->highest_pts, pts);
|
||||
}
|
||||
}
|
||||
|
||||
if (st->event_flags & AVSTREAM_EVENT_FLAG_METADATA_UPDATED) {
|
||||
st->event_flags = 0;
|
||||
struct mp_tags *tags = talloc_zero(NULL, struct mp_tags);
|
||||
mp_tags_copy_from_av_dictionary(tags, st->metadata);
|
||||
double pts = MP_PTS_OR_DEF(dp->pts, dp->dts);
|
||||
demux_stream_tags_changed(demux, stream, tags, pts);
|
||||
}
|
||||
|
||||
*mp_pkt = dp;
|
||||
return true;
|
||||
demux_add_packet(stream, dp);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void demux_seek_lavf(demuxer_t *demuxer, double seek_pts, int flags)
|
||||
@@ -1179,18 +1039,6 @@ static void demux_seek_lavf(demuxer_t *demuxer, double seek_pts, int flags)
|
||||
lavf_priv_t *priv = demuxer->priv;
|
||||
int avsflags = 0;
|
||||
int64_t seek_pts_av = 0;
|
||||
int seek_stream = -1;
|
||||
|
||||
if (priv->any_ts_fixed) {
|
||||
// helpful message to piss of users
|
||||
MP_WARN(demuxer, "Some timestamps returned by the demuxer were linearized. "
|
||||
"A low level seek was requested; this won't work due to "
|
||||
"restrictions in libavformat's API. You may have more "
|
||||
"luck by enabling or enlarging the mpv cache.\n");
|
||||
}
|
||||
|
||||
if (priv->linearize_ts < 0)
|
||||
priv->linearize_ts = 0;
|
||||
|
||||
if (!(flags & SEEK_FORWARD))
|
||||
avsflags = AVSEEK_FLAG_BACKWARD;
|
||||
@@ -1214,39 +1062,13 @@ static void demux_seek_lavf(demuxer_t *demuxer, double seek_pts, int flags)
|
||||
seek_pts_av = seek_pts * AV_TIME_BASE;
|
||||
}
|
||||
|
||||
// Hack to make wav seeking "deterministic". Without this, features like
|
||||
// backward playback won't work.
|
||||
if (priv->pcm_seek_hack && !priv->pcm_seek_hack_packet_size) {
|
||||
// This might for example be the initial seek. Fuck it up like the
|
||||
// bullshit it is.
|
||||
AVPacket pkt = {0};
|
||||
if (av_read_frame(priv->avfc, &pkt) >= 0)
|
||||
priv->pcm_seek_hack_packet_size = pkt.size;
|
||||
av_packet_unref(&pkt);
|
||||
add_new_streams(demuxer);
|
||||
}
|
||||
if (priv->pcm_seek_hack && priv->pcm_seek_hack_packet_size &&
|
||||
!(avsflags & AVSEEK_FLAG_BYTE))
|
||||
{
|
||||
int samples = priv->pcm_seek_hack_packet_size /
|
||||
priv->pcm_seek_hack->codecpar->block_align;
|
||||
if (samples > 0) {
|
||||
MP_VERBOSE(demuxer, "using bullshit libavformat PCM seek hack\n");
|
||||
double pts = seek_pts_av / (double)AV_TIME_BASE;
|
||||
seek_pts_av = pts / av_q2d(priv->pcm_seek_hack->time_base);
|
||||
int64_t align = seek_pts_av % samples;
|
||||
seek_pts_av -= align;
|
||||
seek_stream = priv->pcm_seek_hack->index;
|
||||
}
|
||||
}
|
||||
|
||||
int r = av_seek_frame(priv->avfc, seek_stream, seek_pts_av, avsflags);
|
||||
int r = av_seek_frame(priv->avfc, -1, seek_pts_av, avsflags);
|
||||
if (r < 0 && (avsflags & AVSEEK_FLAG_BACKWARD)) {
|
||||
// When seeking before the beginning of the file, and seeking fails,
|
||||
// try again without the backwards flag to make it seek to the
|
||||
// beginning.
|
||||
avsflags &= ~AVSEEK_FLAG_BACKWARD;
|
||||
r = av_seek_frame(priv->avfc, seek_stream, seek_pts_av, avsflags);
|
||||
r = av_seek_frame(priv->avfc, -1, seek_pts_av, avsflags);
|
||||
}
|
||||
|
||||
if (r < 0) {
|
||||
@@ -1254,39 +1076,109 @@ static void demux_seek_lavf(demuxer_t *demuxer, double seek_pts, int flags)
|
||||
av_strerror(r, buf, sizeof(buf));
|
||||
MP_VERBOSE(demuxer, "Seek failed (%s)\n", buf);
|
||||
}
|
||||
|
||||
update_read_stats(demuxer);
|
||||
}
|
||||
|
||||
static void demux_lavf_switched_tracks(struct demuxer *demuxer)
|
||||
static int demux_lavf_control(demuxer_t *demuxer, int cmd, void *arg)
|
||||
{
|
||||
select_tracks(demuxer, 0);
|
||||
lavf_priv_t *priv = demuxer->priv;
|
||||
|
||||
switch (cmd) {
|
||||
case DEMUXER_CTRL_SWITCHED_TRACKS:
|
||||
{
|
||||
select_tracks(demuxer, 0);
|
||||
return CONTROL_OK;
|
||||
}
|
||||
case DEMUXER_CTRL_IDENTIFY_PROGRAM:
|
||||
{
|
||||
demux_program_t *prog = arg;
|
||||
AVProgram *program;
|
||||
int p, i;
|
||||
int start;
|
||||
|
||||
add_new_streams(demuxer);
|
||||
|
||||
prog->vid = prog->aid = prog->sid = -2;
|
||||
if (priv->avfc->nb_programs < 1)
|
||||
return CONTROL_FALSE;
|
||||
|
||||
if (prog->progid == -1) {
|
||||
p = 0;
|
||||
while (p < priv->avfc->nb_programs && priv->avfc->programs[p]->id != priv->cur_program)
|
||||
p++;
|
||||
p = (p + 1) % priv->avfc->nb_programs;
|
||||
} else {
|
||||
for (i = 0; i < priv->avfc->nb_programs; i++)
|
||||
if (priv->avfc->programs[i]->id == prog->progid)
|
||||
break;
|
||||
if (i == priv->avfc->nb_programs)
|
||||
return CONTROL_FALSE;
|
||||
p = i;
|
||||
}
|
||||
start = p;
|
||||
redo:
|
||||
prog->vid = prog->aid = prog->sid = -2;
|
||||
program = priv->avfc->programs[p];
|
||||
for (i = 0; i < program->nb_stream_indexes; i++) {
|
||||
struct sh_stream *stream = priv->streams[program->stream_index[i]];
|
||||
if (stream) {
|
||||
switch (stream->type) {
|
||||
case STREAM_VIDEO:
|
||||
if (prog->vid == -2)
|
||||
prog->vid = stream->demuxer_id;
|
||||
break;
|
||||
case STREAM_AUDIO:
|
||||
if (prog->aid == -2)
|
||||
prog->aid = stream->demuxer_id;
|
||||
break;
|
||||
case STREAM_SUB:
|
||||
if (prog->sid == -2)
|
||||
prog->sid = stream->demuxer_id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (prog->progid == -1 && prog->vid == -2 && prog->aid == -2) {
|
||||
p = (p + 1) % priv->avfc->nb_programs;
|
||||
if (p == start)
|
||||
return CONTROL_FALSE;
|
||||
goto redo;
|
||||
}
|
||||
priv->cur_program = prog->progid = program->id;
|
||||
|
||||
mp_tags_copy_from_av_dictionary(demuxer->metadata, priv->avfc->programs[p]->metadata);
|
||||
update_metadata(demuxer);
|
||||
// Enforce metadata update even if no explicit METADATA_UPDATED since we switched program.
|
||||
demux_metadata_changed(demuxer);
|
||||
|
||||
return CONTROL_OK;
|
||||
}
|
||||
case DEMUXER_CTRL_RESYNC:
|
||||
stream_drop_buffers(priv->stream);
|
||||
avio_flush(priv->avfc->pb);
|
||||
avformat_flush(priv->avfc);
|
||||
return CONTROL_OK;
|
||||
case DEMUXER_CTRL_REPLACE_STREAM:
|
||||
if (priv->own_stream)
|
||||
free_stream(priv->stream);
|
||||
priv->own_stream = false;
|
||||
priv->stream = demuxer->stream;
|
||||
return CONTROL_OK;
|
||||
default:
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
static void demux_close_lavf(demuxer_t *demuxer)
|
||||
{
|
||||
lavf_priv_t *priv = demuxer->priv;
|
||||
if (priv) {
|
||||
// This will be a dangling pointer; but see below.
|
||||
AVIOContext *leaking = priv->avfc ? priv->avfc->pb : NULL;
|
||||
avformat_close_input(&priv->avfc);
|
||||
// The ffmpeg garbage breaks its own API yet again: hls.c will call
|
||||
// io_open on the main playlist, but never calls io_close. This happens
|
||||
// to work out for us (since we don't really use custom I/O), but it's
|
||||
// still weird. Compensate.
|
||||
if (priv->num_nested == 1 && priv->nested[0].id == leaking)
|
||||
priv->num_nested = 0;
|
||||
if (priv->num_nested) {
|
||||
MP_WARN(demuxer, "Leaking %d nested connections (FFmpeg bug).\n",
|
||||
priv->num_nested);
|
||||
}
|
||||
if (priv->pb)
|
||||
av_freep(&priv->pb->buffer);
|
||||
av_freep(&priv->pb);
|
||||
for (int n = 0; n < priv->num_streams; n++) {
|
||||
struct stream_info *info = priv->streams[n];
|
||||
if (info->sh)
|
||||
avcodec_parameters_free(&info->sh->codec->lav_codecpar);
|
||||
if (priv->streams[n])
|
||||
avcodec_parameters_free(&priv->streams[n]->codec->lav_codecpar);
|
||||
}
|
||||
if (priv->own_stream)
|
||||
free_stream(priv->stream);
|
||||
@@ -1299,9 +1191,9 @@ static void demux_close_lavf(demuxer_t *demuxer)
|
||||
const demuxer_desc_t demuxer_desc_lavf = {
|
||||
.name = "lavf",
|
||||
.desc = "libavformat",
|
||||
.read_packet = demux_lavf_read_packet,
|
||||
.fill_buffer = demux_lavf_fill_buffer,
|
||||
.open = demux_open_lavf,
|
||||
.close = demux_close_lavf,
|
||||
.seek = demux_seek_lavf,
|
||||
.switched_tracks = demux_lavf_switched_tracks,
|
||||
.control = demux_lavf_control,
|
||||
};
|
||||
@@ -21,14 +21,13 @@
|
||||
#include "common/common.h"
|
||||
#include "common/playlist.h"
|
||||
#include "stream/stream.h"
|
||||
#include "misc/natural_sort.h"
|
||||
#include "demux.h"
|
||||
|
||||
#include "stream/stream_libarchive.h"
|
||||
|
||||
static int cmp_filename(const void *a, const void *b)
|
||||
{
|
||||
return mp_natural_sort_cmp(*(char **)a, *(char **)b);
|
||||
return strcmp(*(char **)a, *(char **)b);
|
||||
}
|
||||
|
||||
static int open_file(struct demuxer *demuxer, enum demux_check check)
|
||||
@@ -46,8 +45,7 @@ static int open_file(struct demuxer *demuxer, enum demux_check check)
|
||||
bstr probe = stream_peek(demuxer->stream, probe_size);
|
||||
if (probe.len == 0)
|
||||
return -1;
|
||||
struct stream *probe_stream =
|
||||
stream_memory_open(demuxer->global, probe.start, probe.len);
|
||||
struct stream *probe_stream = open_memory_stream(probe.start, probe.len);
|
||||
struct mp_archive *mpa = mp_archive_new(mp_null_log, probe_stream, flags);
|
||||
bool ok = !!mpa;
|
||||
free_stream(probe_stream);
|
||||
@@ -87,7 +85,6 @@ static int open_file(struct demuxer *demuxer, enum demux_check check)
|
||||
demuxer->fully_read = true;
|
||||
|
||||
mp_archive_free(mpa);
|
||||
demux_close_stream(demuxer);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
+8
-7
@@ -173,12 +173,14 @@ static void demux_seek_mf(demuxer_t *demuxer, double seek_pts, int flags)
|
||||
mf->curr_frame = newpos;
|
||||
}
|
||||
|
||||
static bool demux_mf_read_packet(struct demuxer *demuxer,
|
||||
struct demux_packet **pkt)
|
||||
// return value:
|
||||
// 0 = EOF or no stream found
|
||||
// 1 = successfully read a packet
|
||||
static int demux_mf_fill_buffer(demuxer_t *demuxer)
|
||||
{
|
||||
mf_t *mf = demuxer->priv;
|
||||
if (mf->curr_frame >= mf->nr_of_files)
|
||||
return false;
|
||||
return 0;
|
||||
|
||||
struct stream *entry_stream = NULL;
|
||||
if (mf->streams)
|
||||
@@ -199,8 +201,7 @@ static bool demux_mf_read_packet(struct demuxer *demuxer,
|
||||
memcpy(dp->buffer, data.start, data.len);
|
||||
dp->pts = mf->curr_frame / mf->sh->codec->fps;
|
||||
dp->keyframe = true;
|
||||
dp->stream = mf->sh->index;
|
||||
*pkt = dp;
|
||||
demux_add_packet(mf->sh, dp);
|
||||
}
|
||||
}
|
||||
talloc_free(data.start);
|
||||
@@ -210,7 +211,7 @@ static bool demux_mf_read_packet(struct demuxer *demuxer,
|
||||
free_stream(stream);
|
||||
|
||||
mf->curr_frame++;
|
||||
return true;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// map file extension/type to a codec name
|
||||
@@ -349,7 +350,7 @@ static void demux_close_mf(demuxer_t *demuxer)
|
||||
const demuxer_desc_t demuxer_desc_mf = {
|
||||
.name = "mf",
|
||||
.desc = "image files (mf)",
|
||||
.read_packet = demux_mf_read_packet,
|
||||
.fill_buffer = demux_mf_fill_buffer,
|
||||
.open = demux_open_mf,
|
||||
.close = demux_close_mf,
|
||||
.seek = demux_seek_mf,
|
||||
|
||||
+85
-86
@@ -107,6 +107,7 @@ typedef struct mkv_track {
|
||||
uint32_t colorspace;
|
||||
int stereo_mode;
|
||||
struct mp_colorspace color;
|
||||
struct mp_spherical_params spherical;
|
||||
|
||||
uint32_t a_channels, a_bps;
|
||||
float a_sfreq;
|
||||
@@ -209,12 +210,6 @@ typedef struct mkv_demuxer {
|
||||
// temporary data, and not normally larger than 0 or 1 elements.
|
||||
struct block_info *blocks;
|
||||
int num_blocks;
|
||||
|
||||
// Packets to return.
|
||||
struct demux_packet **packets;
|
||||
int num_packets;
|
||||
|
||||
bool probably_webm_dash_init;
|
||||
} mkv_demuxer_t;
|
||||
|
||||
#define OPT_BASE_STRUCT struct demux_mkv_opts
|
||||
@@ -261,17 +256,6 @@ static void probe_first_timestamp(struct demuxer *demuxer);
|
||||
static int read_next_block_into_queue(demuxer_t *demuxer);
|
||||
static void free_block(struct block_info *block);
|
||||
|
||||
static void add_packet(struct demuxer *demuxer, struct sh_stream *stream,
|
||||
struct demux_packet *pkt)
|
||||
{
|
||||
mkv_demuxer_t *mkv_d = demuxer->priv;
|
||||
if (!pkt)
|
||||
return;
|
||||
|
||||
pkt->stream = stream->index;
|
||||
MP_TARRAY_APPEND(mkv_d, mkv_d->packets, mkv_d->num_packets, pkt);
|
||||
}
|
||||
|
||||
#define AAC_SYNC_EXTENSION_TYPE 0x02b7
|
||||
static int aac_get_sample_rate_index(uint32_t sample_rate)
|
||||
{
|
||||
@@ -602,6 +586,49 @@ static void parse_trackcolour(struct demuxer *demuxer, struct mkv_track *track,
|
||||
}
|
||||
}
|
||||
|
||||
static void parse_trackprojection(struct demuxer *demuxer, struct mkv_track *track,
|
||||
struct ebml_projection *projection)
|
||||
{
|
||||
if (projection->n_projection_type) {
|
||||
const char *name;
|
||||
switch (projection->projection_type) {
|
||||
case 0:
|
||||
name = "rectangular";
|
||||
track->spherical.type = MP_SPHERICAL_NONE;
|
||||
break;
|
||||
case 1:
|
||||
name = "equirectangular";
|
||||
track->spherical.type = MP_SPHERICAL_EQUIRECTANGULAR;
|
||||
break;
|
||||
default:
|
||||
name = "unknown";
|
||||
track->spherical.type = MP_SPHERICAL_UNKNOWN;
|
||||
}
|
||||
MP_VERBOSE(demuxer, "| + ProjectionType: %s (%"PRIu64")\n", name,
|
||||
projection->projection_type);
|
||||
}
|
||||
if (projection->n_projection_private) {
|
||||
MP_VERBOSE(demuxer, "| + ProjectionPrivate: %zd bytes\n",
|
||||
projection->projection_private.len);
|
||||
MP_WARN(demuxer, "Unknown ProjectionPrivate element.\n");
|
||||
}
|
||||
if (projection->n_projection_pose_yaw) {
|
||||
track->spherical.ref_angles[0] = projection->projection_pose_yaw;
|
||||
MP_VERBOSE(demuxer, "| + ProjectionPoseYaw: %f\n",
|
||||
projection->projection_pose_yaw);
|
||||
}
|
||||
if (projection->n_projection_pose_pitch) {
|
||||
track->spherical.ref_angles[1] = projection->projection_pose_pitch;
|
||||
MP_VERBOSE(demuxer, "| + ProjectionPosePitch: %f\n",
|
||||
projection->projection_pose_pitch);
|
||||
}
|
||||
if (projection->n_projection_pose_roll) {
|
||||
track->spherical.ref_angles[2] = projection->projection_pose_roll;
|
||||
MP_VERBOSE(demuxer, "| + ProjectionPoseRoll: %f\n",
|
||||
projection->projection_pose_roll);
|
||||
}
|
||||
}
|
||||
|
||||
static void parse_trackvideo(struct demuxer *demuxer, struct mkv_track *track,
|
||||
struct ebml_video *video)
|
||||
{
|
||||
@@ -646,6 +673,8 @@ static void parse_trackvideo(struct demuxer *demuxer, struct mkv_track *track,
|
||||
}
|
||||
if (video->n_colour)
|
||||
parse_trackcolour(demuxer, track, &video->colour);
|
||||
if (video->n_projection)
|
||||
parse_trackprojection(demuxer, track, &video->projection);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1347,7 +1376,6 @@ static const char *const mkv_video_tags[][2] = {
|
||||
{"V_MPEG4/ISO/ASP", "mpeg4"},
|
||||
{"V_MPEG4/ISO/AP", "mpeg4"},
|
||||
{"V_MPEG4/ISO/AVC", "h264"},
|
||||
{"V_MPEG4/MS/V3", "msmpeg4v3"},
|
||||
{"V_THEORA", "theora"},
|
||||
{"V_VP8", "vp8"},
|
||||
{"V_VP9", "vp9"},
|
||||
@@ -1441,10 +1469,8 @@ static int demux_mkv_open_video(demuxer_t *demuxer, mkv_track_t *track)
|
||||
}
|
||||
|
||||
const char *codec = sh_v->codec ? sh_v->codec : "";
|
||||
if (!strcmp(codec, "mjpeg")) {
|
||||
if (!strcmp(codec, "mjpeg"))
|
||||
sh_v->codec_tag = MKTAG('m', 'j', 'p', 'g');
|
||||
track->require_keyframes = true;
|
||||
}
|
||||
|
||||
if (extradata_size > 0x1000000) {
|
||||
MP_WARN(demuxer, "Invalid CodecPrivate\n");
|
||||
@@ -1471,6 +1497,7 @@ static int demux_mkv_open_video(demuxer_t *demuxer, mkv_track_t *track)
|
||||
|
||||
sh_v->stereo_mode = track->stereo_mode;
|
||||
sh_v->color = track->color;
|
||||
sh_v->spherical = track->spherical;
|
||||
|
||||
done:
|
||||
demux_add_sh_stream(demuxer, sh);
|
||||
@@ -1792,8 +1819,6 @@ static int demux_mkv_open_audio(demuxer_t *demuxer, mkv_track_t *track)
|
||||
sh_a->extradata = extradata;
|
||||
sh_a->extradata_size = extradata_len;
|
||||
|
||||
sh->seek_preroll = track->codec_delay;
|
||||
|
||||
demux_add_sh_stream(demuxer, sh);
|
||||
|
||||
return 0;
|
||||
@@ -1901,7 +1926,6 @@ static void probe_x264_garbage(demuxer_t *demuxer)
|
||||
|
||||
static int read_ebml_header(demuxer_t *demuxer)
|
||||
{
|
||||
mkv_demuxer_t *mkv_d = demuxer->priv;
|
||||
stream_t *s = demuxer->stream;
|
||||
|
||||
if (ebml_read_id(s) != EBML_ID_EBML)
|
||||
@@ -1910,22 +1934,15 @@ static int read_ebml_header(demuxer_t *demuxer)
|
||||
struct ebml_parse_ctx parse_ctx = { demuxer->log, .no_error_messages = true };
|
||||
if (ebml_read_element(s, &parse_ctx, &ebml_master, &ebml_ebml_desc) < 0)
|
||||
return 0;
|
||||
bool is_matroska = false, is_webm = false;
|
||||
if (!ebml_master.doc_type) {
|
||||
MP_VERBOSE(demuxer, "File has EBML header but no doctype. "
|
||||
"Assuming \"matroska\".\n");
|
||||
is_matroska = true;
|
||||
} else if (strcmp(ebml_master.doc_type, "matroska") == 0) {
|
||||
is_matroska = true;
|
||||
} else if (strcmp(ebml_master.doc_type, "webm") == 0) {
|
||||
is_webm = true;
|
||||
}
|
||||
if (!is_matroska && !is_webm) {
|
||||
} else if (strcmp(ebml_master.doc_type, "matroska") != 0
|
||||
&& strcmp(ebml_master.doc_type, "webm") != 0) {
|
||||
MP_TRACE(demuxer, "no head found\n");
|
||||
talloc_free(parse_ctx.talloc_ctx);
|
||||
return 0;
|
||||
}
|
||||
mkv_d->probably_webm_dash_init &= is_webm;
|
||||
if (ebml_master.doc_type_read_version > 2) {
|
||||
MP_WARN(demuxer, "This looks like a Matroska file, "
|
||||
"but we don't support format version %"PRIu64"\n",
|
||||
@@ -1993,15 +2010,6 @@ static int demux_mkv_open(demuxer_t *demuxer, enum demux_check check)
|
||||
int64_t start_pos;
|
||||
int64_t end_pos;
|
||||
|
||||
mkv_d = talloc_zero(demuxer, struct mkv_demuxer);
|
||||
demuxer->priv = mkv_d;
|
||||
mkv_d->tc_scale = 1000000;
|
||||
mkv_d->a_skip_preroll = 1;
|
||||
mkv_d->skip_to_timecode = INT64_MIN;
|
||||
|
||||
if (demuxer->params)
|
||||
mkv_d->probably_webm_dash_init = demuxer->params->init_fragment.len > 0;
|
||||
|
||||
bstr start = stream_peek(s, 4);
|
||||
uint32_t start_id = 0;
|
||||
for (int n = 0; n < start.len; n++)
|
||||
@@ -2016,8 +2024,13 @@ static int demux_mkv_open(demuxer_t *demuxer, enum demux_check check)
|
||||
if (!read_mkv_segment_header(demuxer, &end_pos))
|
||||
return -1;
|
||||
|
||||
mkv_d = talloc_zero(demuxer, struct mkv_demuxer);
|
||||
demuxer->priv = mkv_d;
|
||||
mkv_d->tc_scale = 1000000;
|
||||
mkv_d->segment_start = stream_tell(s);
|
||||
mkv_d->segment_end = end_pos;
|
||||
mkv_d->a_skip_preroll = 1;
|
||||
mkv_d->skip_to_timecode = INT64_MIN;
|
||||
|
||||
mp_read_option_raw(demuxer->global, "index", &m_option_type_choice,
|
||||
&mkv_d->index_mode);
|
||||
@@ -2033,8 +2046,7 @@ static int demux_mkv_open(demuxer_t *demuxer, enum demux_check check)
|
||||
stream_peek(s, 4); // make sure we can always seek back
|
||||
uint32_t id = ebml_read_id(s);
|
||||
if (s->eof) {
|
||||
if (!mkv_d->probably_webm_dash_init)
|
||||
MP_WARN(demuxer, "Unexpected end of file (no clusters found)\n");
|
||||
MP_WARN(demuxer, "Unexpected end of file (no clusters found)\n");
|
||||
break;
|
||||
}
|
||||
if (id == MATROSKA_ID_CLUSTER) {
|
||||
@@ -2058,16 +2070,11 @@ static int demux_mkv_open(demuxer_t *demuxer, enum demux_check check)
|
||||
if (elem->parsed)
|
||||
continue;
|
||||
// Warn against incomplete files and skip headers outside of range.
|
||||
if (elem->pos >= end || !s->seekable) {
|
||||
if (elem->pos >= end) {
|
||||
elem->parsed = true; // don't bother if file is incomplete
|
||||
if (end < 0 || !s->seekable) {
|
||||
MP_WARN(demuxer, "Stream is not seekable or unknown size, "
|
||||
"not reading mkv metadata at end of file.\n");
|
||||
} else if (!mkv_d->eof_warning &&
|
||||
!(mkv_d->probably_webm_dash_init && elem->pos == end))
|
||||
{
|
||||
MP_WARN(demuxer, "mkv metadata beyond end of file - incomplete "
|
||||
"file?\n");
|
||||
if (!mkv_d->eof_warning) {
|
||||
MP_WARN(demuxer, "SeekHead position beyond "
|
||||
"end of file - incomplete file?\n");
|
||||
mkv_d->eof_warning = true;
|
||||
}
|
||||
continue;
|
||||
@@ -2305,7 +2312,7 @@ static bool handle_realaudio(demuxer_t *demuxer, mkv_track_t *track,
|
||||
track->audio_timestamp[x * apk_usize / w];
|
||||
dp->pos = orig->pos + x;
|
||||
dp->keyframe = !x; // Mark first packet as keyframe
|
||||
add_packet(demuxer, track->stream, dp);
|
||||
demux_add_packet(track->stream, dp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2330,10 +2337,6 @@ static void mkv_seek_reset(demuxer_t *demuxer)
|
||||
free_block(&mkv_d->blocks[n]);
|
||||
mkv_d->num_blocks = 0;
|
||||
|
||||
for (int n = 0; n < mkv_d->num_packets; n++)
|
||||
talloc_free(mkv_d->packets[n]);
|
||||
mkv_d->num_packets = 0;
|
||||
|
||||
mkv_d->skip_to_timecode = INT64_MIN;
|
||||
}
|
||||
|
||||
@@ -2437,7 +2440,7 @@ static void mkv_parse_and_add_packet(demuxer_t *demuxer, mkv_track_t *track,
|
||||
if (new) {
|
||||
demux_packet_copy_attribs(new, dp);
|
||||
talloc_free(dp);
|
||||
add_packet(demuxer, stream, new);
|
||||
demux_add_packet(stream, new);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -2452,7 +2455,7 @@ static void mkv_parse_and_add_packet(demuxer_t *demuxer, mkv_track_t *track,
|
||||
memcpy(new->buffer + 8, dp->buffer, dp->len);
|
||||
demux_packet_copy_attribs(new, dp);
|
||||
talloc_free(dp);
|
||||
add_packet(demuxer, stream, new);
|
||||
demux_add_packet(stream, new);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -2466,7 +2469,7 @@ static void mkv_parse_and_add_packet(demuxer_t *demuxer, mkv_track_t *track,
|
||||
}
|
||||
|
||||
if (!track->parse || !track->av_parser || !track->av_parser_codec) {
|
||||
add_packet(demuxer, stream, dp);
|
||||
demux_add_packet(stream, dp);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2500,13 +2503,13 @@ static void mkv_parse_and_add_packet(demuxer_t *demuxer, mkv_track_t *track,
|
||||
new->dts = track->av_parser->dts == AV_NOPTS_VALUE
|
||||
? MP_NOPTS_VALUE : track->av_parser->dts / tb;
|
||||
}
|
||||
add_packet(demuxer, stream, new);
|
||||
demux_add_packet(stream, new);
|
||||
}
|
||||
pts = dts = AV_NOPTS_VALUE;
|
||||
}
|
||||
|
||||
if (dp->len) {
|
||||
add_packet(demuxer, stream, dp);
|
||||
demux_add_packet(stream, dp);
|
||||
} else {
|
||||
talloc_free(dp);
|
||||
}
|
||||
@@ -2626,13 +2629,14 @@ static int handle_block(demuxer_t *demuxer, struct block_info *block_info)
|
||||
if (!block_info->duration)
|
||||
end_time = INT64_MAX;
|
||||
use_this_block = end_time > mkv_d->skip_to_timecode;
|
||||
if (use_this_block) {
|
||||
if (mkv_d->subtitle_preroll) {
|
||||
mkv_d->subtitle_preroll--;
|
||||
} else {
|
||||
// This could overflow the demuxer queue.
|
||||
}
|
||||
if (use_this_block) {
|
||||
if (mkv_d->subtitle_preroll) {
|
||||
mkv_d->subtitle_preroll--;
|
||||
} else {
|
||||
// This could overflow the demuxer queue.
|
||||
if (mkv_d->a_skip_to_keyframe || mkv_d->v_skip_to_keyframe)
|
||||
use_this_block = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (use_this_block) {
|
||||
@@ -2666,22 +2670,22 @@ static int handle_block(demuxer_t *demuxer, struct block_info *block_info)
|
||||
if (!dp)
|
||||
break;
|
||||
|
||||
dp->keyframe = keyframe;
|
||||
dp->pos = filepos;
|
||||
/* If default_duration is 0, assume no pts value is known
|
||||
* for packets after the first one (rather than all pts
|
||||
* values being the same). Also, don't use it for extra
|
||||
* packets resulting from parsing. */
|
||||
if (i == 0 || track->default_duration) {
|
||||
if (i == 0 || track->default_duration)
|
||||
dp->pts = current_pts + i * track->default_duration;
|
||||
dp->keyframe = keyframe;
|
||||
}
|
||||
if (stream->codec->avi_dts)
|
||||
MPSWAP(double, dp->pts, dp->dts);
|
||||
if (i == 0 && block_info->duration_known)
|
||||
dp->duration = block_duration / 1e9;
|
||||
if (stream->type == STREAM_AUDIO) {
|
||||
unsigned int srate = stream->codec->samplerate;
|
||||
demux_packet_set_padding(dp, 0,
|
||||
demux_packet_set_padding(dp,
|
||||
mkv_d->a_skip_preroll ? track->codec_delay * srate : 0,
|
||||
block_info->discardpadding / 1e9 * srate);
|
||||
mkv_d->a_skip_preroll = 0;
|
||||
}
|
||||
@@ -2893,26 +2897,19 @@ static int read_next_block(demuxer_t *demuxer, struct block_info *block)
|
||||
return 1;
|
||||
}
|
||||
|
||||
static bool demux_mkv_read_packet(struct demuxer *demuxer,
|
||||
struct demux_packet **pkt)
|
||||
static int demux_mkv_fill_buffer(demuxer_t *demuxer)
|
||||
{
|
||||
struct mkv_demuxer *mkv_d = demuxer->priv;
|
||||
|
||||
for (;;) {
|
||||
if (mkv_d->num_packets) {
|
||||
*pkt = mkv_d->packets[0];
|
||||
MP_TARRAY_REMOVE_AT(mkv_d->packets, mkv_d->num_packets, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
int res;
|
||||
struct block_info block;
|
||||
res = read_next_block(demuxer, &block);
|
||||
if (res < 0)
|
||||
return false;
|
||||
return 0;
|
||||
if (res > 0) {
|
||||
handle_block(demuxer, &block);
|
||||
res = handle_block(demuxer, &block);
|
||||
free_block(&block);
|
||||
if (res > 0)
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3133,6 +3130,8 @@ static void demux_mkv_seek(demuxer_t *demuxer, double seek_pts, int flags)
|
||||
mkv_d->v_skip_to_keyframe = st_active[STREAM_VIDEO];
|
||||
mkv_d->a_skip_to_keyframe = st_active[STREAM_AUDIO];
|
||||
mkv_d->a_skip_preroll = mkv_d->a_skip_to_keyframe;
|
||||
|
||||
demux_mkv_fill_buffer(demuxer);
|
||||
}
|
||||
|
||||
static void probe_last_timestamp(struct demuxer *demuxer, int64_t start_pos)
|
||||
@@ -3239,7 +3238,7 @@ const demuxer_desc_t demuxer_desc_matroska = {
|
||||
.name = "mkv",
|
||||
.desc = "Matroska",
|
||||
.open = demux_mkv_open,
|
||||
.read_packet = demux_mkv_read_packet,
|
||||
.fill_buffer = demux_mkv_fill_buffer,
|
||||
.close = mkv_free,
|
||||
.seek = demux_mkv_seek,
|
||||
.load_timeline = build_ordered_chapter_timeline,
|
||||
|
||||
+32
-41
@@ -39,7 +39,6 @@
|
||||
#include "options/options.h"
|
||||
#include "options/path.h"
|
||||
#include "misc/bstr.h"
|
||||
#include "misc/thread_tools.h"
|
||||
#include "common/common.h"
|
||||
#include "common/playlist.h"
|
||||
#include "stream/stream.h"
|
||||
@@ -172,6 +171,7 @@ static bool check_file_seg(struct tl_ctx *ctx, char *filename, int segment)
|
||||
.matroska_wanted_segment = segment,
|
||||
.matroska_was_valid = &was_valid,
|
||||
.disable_timeline = true,
|
||||
.disable_cache = true,
|
||||
};
|
||||
struct mp_cancel *cancel = ctx->tl->cancel;
|
||||
if (mp_cancel_test(cancel))
|
||||
@@ -215,12 +215,21 @@ static bool check_file_seg(struct tl_ctx *ctx, char *filename, int segment)
|
||||
}
|
||||
}
|
||||
|
||||
if (stream_wants_cache(d->stream, ctx->opts->stream_cache)) {
|
||||
free_demuxer_and_stream(d);
|
||||
params.disable_cache = false;
|
||||
params.matroska_wanted_uids = ctx->uids; // potentially reallocated, same data
|
||||
d = demux_open_url(filename, ¶ms, cancel, ctx->global);
|
||||
if (!d)
|
||||
return false;
|
||||
}
|
||||
|
||||
ctx->sources[i] = d;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
demux_free(d);
|
||||
free_demuxer_and_stream(d);
|
||||
return was_valid;
|
||||
}
|
||||
|
||||
@@ -254,8 +263,7 @@ static void find_ordered_chapter_sources(struct tl_ctx *ctx)
|
||||
MP_INFO(ctx, "Loading references from '%s'.\n",
|
||||
opts->ordered_chapters_files);
|
||||
struct playlist *pl =
|
||||
playlist_parse_file(opts->ordered_chapters_files,
|
||||
ctx->tl->cancel, ctx->global);
|
||||
playlist_parse_file(opts->ordered_chapters_files, ctx->global);
|
||||
talloc_steal(tmp, pl);
|
||||
for (struct playlist_entry *e = pl ? pl->first : NULL; e; e = e->next)
|
||||
MP_TARRAY_APPEND(tmp, filenames, num_filenames, e->filename);
|
||||
@@ -297,16 +305,6 @@ static void find_ordered_chapter_sources(struct tl_ctx *ctx)
|
||||
ctx->num_sources = j;
|
||||
}
|
||||
|
||||
// Copy attachments from referenced sources so fonts are loaded for sub
|
||||
// rendering.
|
||||
for (int i = 1; i < ctx->num_sources; i++) {
|
||||
for (int j = 0; j < ctx->sources[i]->num_attachments; j++) {
|
||||
struct demux_attachment *att = &ctx->sources[i]->attachments[j];
|
||||
demuxer_add_attachment(ctx->demuxer, att->name, att->type,
|
||||
att->data, att->data_size);
|
||||
}
|
||||
}
|
||||
|
||||
talloc_free(tmp);
|
||||
}
|
||||
|
||||
@@ -455,10 +453,12 @@ static void build_timeline_loop(struct tl_ctx *ctx,
|
||||
ctx->missing_time += info->limit - local_starttime;
|
||||
}
|
||||
|
||||
static void check_track_compatibility(struct tl_ctx *tl, struct demuxer *mainsrc)
|
||||
static void check_track_compatibility(struct timeline *tl)
|
||||
{
|
||||
struct demuxer *mainsrc = tl->track_layout;
|
||||
|
||||
for (int n = 0; n < tl->num_parts; n++) {
|
||||
struct timeline_part *p = &tl->timeline[n];
|
||||
struct timeline_part *p = &tl->parts[n];
|
||||
if (p->source == mainsrc)
|
||||
continue;
|
||||
|
||||
@@ -515,7 +515,7 @@ void build_ordered_chapter_timeline(struct timeline *tl)
|
||||
.global = tl->global,
|
||||
.tl = tl,
|
||||
.demuxer = demuxer,
|
||||
.opts = mp_get_config_group(ctx, tl->global, GLOBAL_CONFIG),
|
||||
.opts = mp_get_config_group(ctx, tl->global, NULL),
|
||||
};
|
||||
|
||||
if (!ctx->opts->ordered_chapters || !demuxer->access_references) {
|
||||
@@ -591,11 +591,10 @@ void build_ordered_chapter_timeline(struct timeline *tl)
|
||||
MP_TARRAY_APPEND(NULL, ctx->timeline, ctx->num_parts, new);
|
||||
}
|
||||
|
||||
for (int n = 0; n < ctx->num_parts; n++) {
|
||||
ctx->timeline[n].end = n == ctx->num_parts - 1
|
||||
? ctx->start_time / 1e9
|
||||
: ctx->timeline[n + 1].start;
|
||||
struct timeline_part new = {
|
||||
.start = ctx->start_time / 1e9,
|
||||
};
|
||||
MP_TARRAY_APPEND(NULL, ctx->timeline, ctx->num_parts, new);
|
||||
|
||||
/* Ignore anything less than a millisecond when reporting missing time. If
|
||||
* users really notice less than a millisecond missing, maybe this can be
|
||||
@@ -605,30 +604,22 @@ void build_ordered_chapter_timeline(struct timeline *tl)
|
||||
ctx->missing_time / 1e9);
|
||||
}
|
||||
|
||||
tl->sources = ctx->sources;
|
||||
tl->num_sources = ctx->num_sources;
|
||||
tl->parts = ctx->timeline;
|
||||
tl->num_parts = ctx->num_parts - 1; // minus termination
|
||||
tl->chapters = chapters;
|
||||
tl->num_chapters = m->num_ordered_chapters;
|
||||
|
||||
// With Matroska, the "master" file usually dictates track layout etc.,
|
||||
// except maybe with playlist-like files.
|
||||
struct demuxer *track_layout = ctx->timeline[0].source;
|
||||
for (int n = 0; n < ctx->num_parts; n++) {
|
||||
if (ctx->timeline[n].source == ctx->demuxer) {
|
||||
track_layout = ctx->demuxer;
|
||||
tl->track_layout = tl->parts[0].source;
|
||||
for (int n = 0; n < tl->num_parts; n++) {
|
||||
if (tl->parts[n].source == ctx->demuxer) {
|
||||
tl->track_layout = ctx->demuxer;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
check_track_compatibility(ctx, track_layout);
|
||||
|
||||
tl->sources = ctx->sources;
|
||||
tl->num_sources = ctx->num_sources;
|
||||
|
||||
struct timeline_par *par = talloc_ptrtype(tl, par);
|
||||
*par = (struct timeline_par){
|
||||
.parts = ctx->timeline,
|
||||
.num_parts = ctx->num_parts,
|
||||
.track_layout = track_layout,
|
||||
};
|
||||
MP_TARRAY_APPEND(tl, tl->pars, tl->num_pars, par);
|
||||
tl->chapters = chapters;
|
||||
tl->num_chapters = m->num_ordered_chapters;
|
||||
tl->meta = track_layout;
|
||||
tl->format = "mkv_oc";
|
||||
check_track_compatibility(tl);
|
||||
}
|
||||
@@ -25,11 +25,9 @@
|
||||
#include "options/options.h"
|
||||
#include "common/msg.h"
|
||||
#include "common/playlist.h"
|
||||
#include "misc/thread_tools.h"
|
||||
#include "options/path.h"
|
||||
#include "stream/stream.h"
|
||||
#include "osdep/io.h"
|
||||
#include "misc/natural_sort.h"
|
||||
#include "demux.h"
|
||||
|
||||
#define PROBE_SIZE (8 * 1024)
|
||||
@@ -48,7 +46,7 @@ static bool check_mimetype(struct stream *s, const char *const *list)
|
||||
struct pl_parser {
|
||||
struct mp_log *log;
|
||||
struct stream *s;
|
||||
char buffer[512 * 1024];
|
||||
char buffer[8 * 1024];
|
||||
int utf16;
|
||||
struct playlist *pl;
|
||||
bool error;
|
||||
@@ -285,7 +283,7 @@ static bool scan_dir(struct pl_parser *p, char *path,
|
||||
|
||||
static int cmp_filename(const void *a, const void *b)
|
||||
{
|
||||
return mp_natural_sort_cmp(*(char **)a, *(char **)b);
|
||||
return strcmp(*(char **)a, *(char **)b);
|
||||
}
|
||||
|
||||
static int parse_dir(struct pl_parser *p)
|
||||
@@ -367,7 +365,7 @@ static int open_file(struct demuxer *demuxer, enum demux_check check)
|
||||
p->add_base = true;
|
||||
|
||||
bstr probe_buf = stream_peek(demuxer->stream, PROBE_SIZE);
|
||||
p->s = stream_memory_open(demuxer->global, probe_buf.start, probe_buf.len);
|
||||
p->s = open_memory_stream(probe_buf.start, probe_buf.len);
|
||||
p->s->mime_type = demuxer->stream->mime_type;
|
||||
p->utf16 = stream_skip_bom(p->s);
|
||||
p->force = force;
|
||||
@@ -392,8 +390,6 @@ static int open_file(struct demuxer *demuxer, enum demux_check check)
|
||||
demuxer->filetype = p->format ? p->format : fmt->name;
|
||||
demuxer->fully_read = true;
|
||||
talloc_free(p);
|
||||
if (ok)
|
||||
demux_close_stream(demuxer);
|
||||
return ok ? 0 : -1;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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 "common/common.h"
|
||||
#include "common/playlist.h"
|
||||
#include "stream/stream.h"
|
||||
#include "stream/rar.h"
|
||||
#include "demux.h"
|
||||
|
||||
static int open_file(struct demuxer *demuxer, enum demux_check check)
|
||||
{
|
||||
if (!demuxer->access_references)
|
||||
return -1;
|
||||
|
||||
if (RarProbe(demuxer->stream))
|
||||
return -1;
|
||||
|
||||
int count;
|
||||
rar_file_t **files;
|
||||
if (RarParse(demuxer->stream, &count, &files))
|
||||
return -1;
|
||||
|
||||
void *tmp = talloc_new(NULL);
|
||||
talloc_steal(tmp, files);
|
||||
|
||||
struct playlist *pl = talloc_zero(demuxer, struct playlist);
|
||||
demuxer->playlist = pl;
|
||||
|
||||
// make it load rar://
|
||||
pl->disable_safety = true;
|
||||
|
||||
char *prefix = mp_url_escape(tmp, demuxer->stream->url, "~|");
|
||||
for (int n = 0; n < count; n++) {
|
||||
// stream_rar.c does the real work
|
||||
playlist_add_file(pl,
|
||||
talloc_asprintf(tmp, "rar://%s|%s", prefix, files[n]->name));
|
||||
RarFileDelete(files[n]);
|
||||
}
|
||||
|
||||
demuxer->filetype = "rar";
|
||||
demuxer->fully_read = true;
|
||||
|
||||
talloc_free(tmp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct demuxer_desc demuxer_desc_rar = {
|
||||
.name = "rar",
|
||||
.desc = "Rar archive file",
|
||||
.open = open_file,
|
||||
};
|
||||
+8
-13
@@ -269,30 +269,27 @@ static int demux_rawvideo_open(demuxer_t *demuxer, enum demux_check check)
|
||||
return generic_open(demuxer);
|
||||
}
|
||||
|
||||
static bool raw_read_packet(struct demuxer *demuxer, struct demux_packet **pkt)
|
||||
static int raw_fill_buffer(demuxer_t *demuxer)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
|
||||
if (demuxer->stream->eof)
|
||||
return false;
|
||||
return 0;
|
||||
|
||||
struct demux_packet *dp = new_demux_packet(p->frame_size * p->read_frames);
|
||||
if (!dp) {
|
||||
MP_ERR(demuxer, "Can't read packet.\n");
|
||||
return true;
|
||||
return 1;
|
||||
}
|
||||
|
||||
dp->keyframe = true;
|
||||
dp->pos = stream_tell(demuxer->stream);
|
||||
dp->pts = (dp->pos / p->frame_size) / p->frame_rate;
|
||||
|
||||
int len = stream_read(demuxer->stream, dp->buffer, dp->len);
|
||||
demux_packet_shorten(dp, len);
|
||||
demux_add_packet(p->sh, dp);
|
||||
|
||||
dp->stream = p->sh->index;
|
||||
*pkt = dp;
|
||||
|
||||
return true;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void raw_seek(demuxer_t *demuxer, double seek_pts, int flags)
|
||||
@@ -301,9 +298,7 @@ static void raw_seek(demuxer_t *demuxer, double seek_pts, int flags)
|
||||
stream_t *s = demuxer->stream;
|
||||
int64_t end = 0;
|
||||
stream_control(s, STREAM_CTRL_GET_SIZE, &end);
|
||||
int64_t frame_nr = seek_pts * p->frame_rate;
|
||||
frame_nr = frame_nr - (frame_nr % p->read_frames);
|
||||
int64_t pos = frame_nr * p->frame_size;
|
||||
int64_t pos = seek_pts * p->frame_rate * p->frame_size;
|
||||
if (flags & SEEK_FACTOR)
|
||||
pos = end * seek_pts;
|
||||
if (pos < 0)
|
||||
@@ -317,7 +312,7 @@ const demuxer_desc_t demuxer_desc_rawaudio = {
|
||||
.name = "rawaudio",
|
||||
.desc = "Uncompressed audio",
|
||||
.open = demux_rawaudio_open,
|
||||
.read_packet = raw_read_packet,
|
||||
.fill_buffer = raw_fill_buffer,
|
||||
.seek = raw_seek,
|
||||
};
|
||||
|
||||
@@ -325,6 +320,6 @@ const demuxer_desc_t demuxer_desc_rawvideo = {
|
||||
.name = "rawvideo",
|
||||
.desc = "Uncompressed video",
|
||||
.open = demux_rawvideo_open,
|
||||
.read_packet = raw_read_packet,
|
||||
.fill_buffer = raw_fill_buffer,
|
||||
.seek = raw_seek,
|
||||
};
|
||||
+166
-364
@@ -27,18 +27,17 @@
|
||||
#include "stream/stream.h"
|
||||
|
||||
struct segment {
|
||||
int index; // index into virtual_source.segments[] (and timeline.parts[])
|
||||
int index;
|
||||
double start, end;
|
||||
double d_start;
|
||||
char *url;
|
||||
bool lazy;
|
||||
struct demuxer *d;
|
||||
// stream_map[sh_stream.index] = virtual_stream, where sh_stream is a stream
|
||||
// from the source d, and virtual_stream is a streamexported by the
|
||||
// timeline demuxer (virtual_stream.sh). It's used to map the streams of the
|
||||
// stream_map[sh_stream.index] = index into priv.streams, where sh_stream
|
||||
// is a stream from the source d. It's used to map the streams of the
|
||||
// source onto the set of streams of the virtual timeline.
|
||||
// Uses NULL for streams that do not appear in the virtual timeline.
|
||||
struct virtual_stream **stream_map;
|
||||
// Uses -1 for streams that do not appear in the virtual timeline.
|
||||
int *stream_map;
|
||||
int num_stream_map;
|
||||
};
|
||||
|
||||
@@ -48,96 +47,68 @@ struct virtual_stream {
|
||||
struct sh_stream *sh; // stream exported by demux_timeline
|
||||
bool selected; // ==demux_stream_is_selected(sh)
|
||||
int eos_packets; // deal with b-frame delay
|
||||
struct virtual_source *src; // group this stream is part of
|
||||
};
|
||||
|
||||
// This represents a single timeline source. (See timeline.next. For each
|
||||
// timeline struct there is a virtual_source.)
|
||||
struct virtual_source {
|
||||
struct timeline_par *tl;
|
||||
|
||||
bool dash, no_clip;
|
||||
|
||||
struct segment **segments;
|
||||
int num_segments;
|
||||
struct segment *current;
|
||||
|
||||
struct virtual_stream **streams;
|
||||
int num_streams;
|
||||
|
||||
// Total number of packets received past end of segment. Used
|
||||
// to be clever about determining when to switch segments.
|
||||
int eos_packets;
|
||||
|
||||
bool eof_reached;
|
||||
double dts; // highest read DTS (or PTS if no DTS available)
|
||||
bool any_selected; // at least one stream is actually selected
|
||||
|
||||
struct demux_packet *next;
|
||||
};
|
||||
|
||||
struct priv {
|
||||
struct timeline *tl;
|
||||
|
||||
double duration;
|
||||
bool dash;
|
||||
|
||||
struct segment **segments;
|
||||
int num_segments;
|
||||
struct segment *current;
|
||||
|
||||
// As the demuxer user sees it.
|
||||
struct virtual_stream **streams;
|
||||
int num_streams;
|
||||
|
||||
struct virtual_source **sources;
|
||||
int num_sources;
|
||||
// Total number of packets received past end of segment. Used
|
||||
// to be clever about determining when to switch segments.
|
||||
int eos_packets;
|
||||
};
|
||||
|
||||
static bool add_tl(struct demuxer *demuxer, struct timeline_par *par);
|
||||
static bool do_read_next_packet(struct demuxer *demuxer,
|
||||
struct virtual_source *src);
|
||||
|
||||
static void update_slave_stats(struct demuxer *demuxer, struct demuxer *slave)
|
||||
{
|
||||
demux_report_unbuffered_read_bytes(demuxer, demux_get_bytes_read_hack(slave));
|
||||
}
|
||||
|
||||
static bool target_stream_used(struct segment *seg, struct virtual_stream *vs)
|
||||
static bool target_stream_used(struct segment *seg, int target_index)
|
||||
{
|
||||
for (int n = 0; n < seg->num_stream_map; n++) {
|
||||
if (seg->stream_map[n] == vs)
|
||||
if (seg->stream_map[n] == target_index)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create mapping from segment streams to virtual timeline streams.
|
||||
static void associate_streams(struct demuxer *demuxer,
|
||||
struct virtual_source *src,
|
||||
struct segment *seg)
|
||||
static void associate_streams(struct demuxer *demuxer, struct segment *seg)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
|
||||
if (!seg->d || seg->stream_map)
|
||||
return;
|
||||
|
||||
int counts[STREAM_TYPE_COUNT] = {0};
|
||||
|
||||
int num_streams = demux_get_num_stream(seg->d);
|
||||
for (int n = 0; n < num_streams; n++) {
|
||||
struct sh_stream *sh = demux_get_stream(seg->d, n);
|
||||
struct virtual_stream *other = NULL;
|
||||
for (int i = 0; i < src->num_streams; i++) {
|
||||
struct virtual_stream *vs = src->streams[i];
|
||||
|
||||
// The stream must always have the same media type. Also, a stream
|
||||
// can't be assigned multiple times.
|
||||
if (sh->type != vs->sh->type || target_stream_used(seg, vs))
|
||||
continue;
|
||||
|
||||
// By default pick the first matching stream.
|
||||
if (!other)
|
||||
other = vs;
|
||||
|
||||
// Matching by demuxer ID is supposedly useful and preferable for
|
||||
// ordered chapters.
|
||||
if (sh->demuxer_id == vs->sh->demuxer_id)
|
||||
other = vs;
|
||||
// Try associating by demuxer ID (supposedly useful for ordered chapters).
|
||||
struct sh_stream *other =
|
||||
demuxer_stream_by_demuxer_id(demuxer, sh->type, sh->demuxer_id);
|
||||
if (!other || !target_stream_used(seg, other->index)) {
|
||||
// Try to associate the first unused stream with matching media type.
|
||||
for (int i = 0; i < p->num_streams; i++) {
|
||||
struct sh_stream *cur = p->streams[i]->sh;
|
||||
if (cur->type == sh->type && !target_stream_used(seg, cur->index))
|
||||
{
|
||||
other = cur;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MP_TARRAY_APPEND(seg, seg->stream_map, seg->num_stream_map, other);
|
||||
MP_TARRAY_APPEND(seg, seg->stream_map, seg->num_stream_map,
|
||||
other ? other->index : -1);
|
||||
|
||||
counts[sh->type] += 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,239 +121,130 @@ static void reselect_streams(struct demuxer *demuxer)
|
||||
vs->selected = demux_stream_is_selected(vs->sh);
|
||||
}
|
||||
|
||||
for (int x = 0; x < p->num_sources; x++) {
|
||||
struct virtual_source *src = p->sources[x];
|
||||
|
||||
for (int n = 0; n < src->num_segments; n++) {
|
||||
struct segment *seg = src->segments[n];
|
||||
|
||||
for (int n = 0; n < p->num_segments; n++) {
|
||||
struct segment *seg = p->segments[n];
|
||||
for (int i = 0; i < seg->num_stream_map; i++) {
|
||||
if (!seg->d)
|
||||
continue;
|
||||
|
||||
for (int i = 0; i < seg->num_stream_map; i++) {
|
||||
bool selected =
|
||||
seg->stream_map[i] && seg->stream_map[i]->selected;
|
||||
|
||||
// This stops demuxer readahead for inactive segments.
|
||||
if (!src->current || seg->d != src->current->d)
|
||||
selected = false;
|
||||
struct sh_stream *sh = demux_get_stream(seg->d, i);
|
||||
demuxer_select_track(seg->d, sh, MP_NOPTS_VALUE, selected);
|
||||
|
||||
update_slave_stats(demuxer, seg->d);
|
||||
}
|
||||
}
|
||||
|
||||
bool was_selected = src->any_selected;
|
||||
src->any_selected = false;
|
||||
|
||||
for (int n = 0; n < src->num_streams; n++)
|
||||
src->any_selected |= src->streams[n]->selected;
|
||||
|
||||
if (!was_selected && src->any_selected) {
|
||||
src->eof_reached = false;
|
||||
src->dts = MP_NOPTS_VALUE;
|
||||
TA_FREEP(&src->next);
|
||||
struct sh_stream *sh = demux_get_stream(seg->d, i);
|
||||
bool selected = false;
|
||||
if (seg->stream_map[i] >= 0)
|
||||
selected = p->streams[seg->stream_map[i]]->selected;
|
||||
// This stops demuxer readahead for inactive segments.
|
||||
if (!p->current || seg->d != p->current->d)
|
||||
selected = false;
|
||||
demuxer_select_track(seg->d, sh, MP_NOPTS_VALUE, selected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void close_lazy_segments(struct demuxer *demuxer,
|
||||
struct virtual_source *src)
|
||||
static void close_lazy_segments(struct demuxer *demuxer)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
|
||||
// unload previous segment
|
||||
for (int n = 0; n < src->num_segments; n++) {
|
||||
struct segment *seg = src->segments[n];
|
||||
if (seg != src->current && seg->d && seg->lazy) {
|
||||
TA_FREEP(&src->next); // might depend on one of the sub-demuxers
|
||||
demux_free(seg->d);
|
||||
for (int n = 0; n < p->num_segments; n++) {
|
||||
struct segment *seg = p->segments[n];
|
||||
if (seg != p->current && seg->d && seg->lazy) {
|
||||
free_demuxer_and_stream(seg->d);
|
||||
seg->d = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void reopen_lazy_segments(struct demuxer *demuxer,
|
||||
struct virtual_source *src)
|
||||
static void reopen_lazy_segments(struct demuxer *demuxer)
|
||||
{
|
||||
if (src->current->d)
|
||||
struct priv *p = demuxer->priv;
|
||||
|
||||
if (p->current->d)
|
||||
return;
|
||||
|
||||
close_lazy_segments(demuxer, src);
|
||||
close_lazy_segments(demuxer);
|
||||
|
||||
struct demuxer_params params = {
|
||||
.init_fragment = src->tl->init_fragment,
|
||||
.init_fragment = p->tl->init_fragment,
|
||||
.skip_lavf_probing = true,
|
||||
};
|
||||
src->current->d = demux_open_url(src->current->url, ¶ms,
|
||||
demuxer->cancel, demuxer->global);
|
||||
if (!src->current->d && !demux_cancel_test(demuxer))
|
||||
p->current->d = demux_open_url(p->current->url, ¶ms,
|
||||
demuxer->stream->cancel, demuxer->global);
|
||||
if (!p->current->d && !demux_cancel_test(demuxer))
|
||||
MP_ERR(demuxer, "failed to load segment\n");
|
||||
if (src->current->d)
|
||||
update_slave_stats(demuxer, src->current->d);
|
||||
associate_streams(demuxer, src, src->current);
|
||||
if (p->current->d)
|
||||
demux_disable_cache(p->current->d);
|
||||
associate_streams(demuxer, p->current);
|
||||
}
|
||||
|
||||
static void switch_segment(struct demuxer *demuxer, struct virtual_source *src,
|
||||
struct segment *new, double start_pts, int flags,
|
||||
bool init)
|
||||
static void switch_segment(struct demuxer *demuxer, struct segment *new,
|
||||
double start_pts, int flags, bool init)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
|
||||
if (!(flags & SEEK_FORWARD))
|
||||
flags |= SEEK_HR;
|
||||
|
||||
MP_VERBOSE(demuxer, "switch to segment %d\n", new->index);
|
||||
|
||||
if (src->current && src->current->d)
|
||||
update_slave_stats(demuxer, src->current->d);
|
||||
|
||||
src->current = new;
|
||||
reopen_lazy_segments(demuxer, src);
|
||||
p->current = new;
|
||||
reopen_lazy_segments(demuxer);
|
||||
if (!new->d)
|
||||
return;
|
||||
reselect_streams(demuxer);
|
||||
if (!src->no_clip)
|
||||
if (!p->dash)
|
||||
demux_set_ts_offset(new->d, new->start - new->d_start);
|
||||
if (!src->no_clip || !init)
|
||||
if (!p->dash || !init)
|
||||
demux_seek(new->d, start_pts, flags);
|
||||
|
||||
for (int n = 0; n < src->num_streams; n++) {
|
||||
struct virtual_stream *vs = src->streams[n];
|
||||
for (int n = 0; n < p->num_streams; n++) {
|
||||
struct virtual_stream *vs = p->streams[n];
|
||||
vs->eos_packets = 0;
|
||||
}
|
||||
|
||||
src->eof_reached = false;
|
||||
src->eos_packets = 0;
|
||||
}
|
||||
|
||||
static void seek_source(struct demuxer *demuxer, struct virtual_source *src,
|
||||
double pts, int flags)
|
||||
{
|
||||
struct segment *new = src->segments[src->num_segments - 1];
|
||||
for (int n = 0; n < src->num_segments; n++) {
|
||||
if (pts < src->segments[n]->end) {
|
||||
new = src->segments[n];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch_segment(demuxer, src, new, pts, flags, false);
|
||||
|
||||
src->dts = MP_NOPTS_VALUE;
|
||||
TA_FREEP(&src->next);
|
||||
p->eos_packets = 0;
|
||||
}
|
||||
|
||||
static void d_seek(struct demuxer *demuxer, double seek_pts, int flags)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
|
||||
seek_pts = seek_pts * ((flags & SEEK_FACTOR) ? p->duration : 1);
|
||||
double pts = seek_pts * ((flags & SEEK_FACTOR) ? p->duration : 1);
|
||||
|
||||
flags &= SEEK_FORWARD | SEEK_HR;
|
||||
|
||||
// The intention is to seek audio streams to the same target as video
|
||||
// streams if they are separate streams. Video streams usually have more
|
||||
// coarse keyframe snapping, which could leave video without audio.
|
||||
struct virtual_source *master = NULL;
|
||||
bool has_slaves = false;
|
||||
for (int x = 0; x < p->num_sources; x++) {
|
||||
struct virtual_source *src = p->sources[x];
|
||||
|
||||
bool any_audio = false, any_video = false;
|
||||
for (int i = 0; i < src->num_streams; i++) {
|
||||
struct virtual_stream *str = src->streams[i];
|
||||
if (str->selected) {
|
||||
if (str->sh->type == STREAM_VIDEO)
|
||||
any_video = true;
|
||||
if (str->sh->type == STREAM_AUDIO)
|
||||
any_audio = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (any_video)
|
||||
master = src;
|
||||
// A true slave stream is audio-only; this also prevents that the master
|
||||
// stream is considered a slave stream.
|
||||
if (any_audio && !any_video)
|
||||
has_slaves = true;
|
||||
}
|
||||
|
||||
if (!has_slaves)
|
||||
master = NULL;
|
||||
|
||||
if (master) {
|
||||
seek_source(demuxer, master, seek_pts, flags);
|
||||
do_read_next_packet(demuxer, master);
|
||||
if (master->next && master->next->pts != MP_NOPTS_VALUE) {
|
||||
// Assume we got a seek target. Actually apply the heuristic.
|
||||
MP_VERBOSE(demuxer, "adjust seek target from %f to %f\n", seek_pts,
|
||||
master->next->pts);
|
||||
seek_pts = master->next->pts;
|
||||
flags &= ~(unsigned)SEEK_FORWARD;
|
||||
struct segment *new = p->segments[p->num_segments - 1];
|
||||
for (int n = 0; n < p->num_segments; n++) {
|
||||
if (pts < p->segments[n]->end) {
|
||||
new = p->segments[n];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int x = 0; x < p->num_sources; x++) {
|
||||
struct virtual_source *src = p->sources[x];
|
||||
if (src != master)
|
||||
seek_source(demuxer, src, seek_pts, flags);
|
||||
}
|
||||
switch_segment(demuxer, new, pts, flags, false);
|
||||
}
|
||||
|
||||
static bool d_read_packet(struct demuxer *demuxer, struct demux_packet **out_pkt)
|
||||
static int d_fill_buffer(struct demuxer *demuxer)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
|
||||
struct virtual_source *src = NULL;
|
||||
if (!p->current)
|
||||
switch_segment(demuxer, p->segments[0], 0, 0, true);
|
||||
|
||||
for (int x = 0; x < p->num_sources; x++) {
|
||||
struct virtual_source *cur = p->sources[x];
|
||||
|
||||
if (!cur->any_selected || cur->eof_reached)
|
||||
continue;
|
||||
|
||||
if (!cur->current)
|
||||
switch_segment(demuxer, cur, cur->segments[0], 0, 0, true);
|
||||
|
||||
if (!cur->any_selected || !cur->current || !cur->current->d)
|
||||
continue;
|
||||
|
||||
if (!src || cur->dts == MP_NOPTS_VALUE ||
|
||||
(src->dts != MP_NOPTS_VALUE && cur->dts < src->dts))
|
||||
src = cur;
|
||||
}
|
||||
|
||||
if (!src)
|
||||
return false;
|
||||
|
||||
if (!do_read_next_packet(demuxer, src))
|
||||
return false;
|
||||
*out_pkt = src->next;
|
||||
src->next = NULL;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool do_read_next_packet(struct demuxer *demuxer,
|
||||
struct virtual_source *src)
|
||||
{
|
||||
if (src->next)
|
||||
return 1;
|
||||
|
||||
struct segment *seg = src->current;
|
||||
struct segment *seg = p->current;
|
||||
if (!seg || !seg->d)
|
||||
return 0;
|
||||
|
||||
struct demux_packet *pkt = demux_read_any_packet(seg->d);
|
||||
if (!pkt || (!src->no_clip && pkt->pts >= seg->end))
|
||||
src->eos_packets += 1;
|
||||
|
||||
update_slave_stats(demuxer, seg->d);
|
||||
if (!pkt || pkt->pts >= seg->end)
|
||||
p->eos_packets += 1;
|
||||
|
||||
// Test for EOF. Do this here to properly run into EOF even if other
|
||||
// streams are disabled etc. If it somehow doesn't manage to reach the end
|
||||
// after demuxing a high (bit arbitrary) number of packets, assume one of
|
||||
// the streams went EOF early.
|
||||
bool eos_reached = src->eos_packets > 0;
|
||||
if (eos_reached && src->eos_packets < 100) {
|
||||
for (int n = 0; n < src->num_streams; n++) {
|
||||
struct virtual_stream *vs = src->streams[n];
|
||||
bool eos_reached = p->eos_packets > 0;
|
||||
if (eos_reached && p->eos_packets < 100) {
|
||||
for (int n = 0; n < p->num_streams; n++) {
|
||||
struct virtual_stream *vs = p->streams[n];
|
||||
if (vs->selected) {
|
||||
int max_packets = 0;
|
||||
if (vs->sh->type == STREAM_AUDIO)
|
||||
@@ -394,30 +256,26 @@ static bool do_read_next_packet(struct demuxer *demuxer,
|
||||
}
|
||||
}
|
||||
|
||||
src->eof_reached = false;
|
||||
|
||||
if (eos_reached || !pkt) {
|
||||
talloc_free(pkt);
|
||||
|
||||
struct segment *next = NULL;
|
||||
for (int n = 0; n < src->num_segments - 1; n++) {
|
||||
if (src->segments[n] == seg) {
|
||||
next = src->segments[n + 1];
|
||||
for (int n = 0; n < p->num_segments - 1; n++) {
|
||||
if (p->segments[n] == seg) {
|
||||
next = p->segments[n + 1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!next) {
|
||||
src->eof_reached = true;
|
||||
return false;
|
||||
}
|
||||
switch_segment(demuxer, src, next, next->start, 0, true);
|
||||
return true; // reader will retry
|
||||
if (!next)
|
||||
return 0;
|
||||
switch_segment(demuxer, next, next->start, 0, true);
|
||||
return 1; // reader will retry
|
||||
}
|
||||
|
||||
if (pkt->stream < 0 || pkt->stream >= seg->num_stream_map)
|
||||
if (pkt->stream < 0 || pkt->stream > seg->num_stream_map)
|
||||
goto drop;
|
||||
|
||||
if (!src->no_clip) {
|
||||
if (!p->dash) {
|
||||
pkt->segmented = true;
|
||||
if (!pkt->codec)
|
||||
pkt->codec = demux_get_stream(seg->d, pkt->stream)->codec;
|
||||
@@ -427,8 +285,8 @@ static bool do_read_next_packet(struct demuxer *demuxer,
|
||||
pkt->end = seg->end;
|
||||
}
|
||||
|
||||
struct virtual_stream *vs = seg->stream_map[pkt->stream];
|
||||
if (!vs)
|
||||
pkt->stream = seg->stream_map[pkt->stream];
|
||||
if (pkt->stream < 0)
|
||||
goto drop;
|
||||
|
||||
// for refresh seeks, demux.c prefers monotonically increasing packet pos
|
||||
@@ -436,7 +294,9 @@ static bool do_read_next_packet(struct demuxer *demuxer,
|
||||
if (pkt->pos >= 0)
|
||||
pkt->pos |= (seg->index & 0x7FFFULL) << 48;
|
||||
|
||||
if (pkt->pts != MP_NOPTS_VALUE && !src->no_clip && pkt->pts >= seg->end) {
|
||||
struct virtual_stream *vs = p->streams[pkt->stream];
|
||||
|
||||
if (pkt->pts != MP_NOPTS_VALUE && pkt->pts >= seg->end) {
|
||||
// Trust the keyframe flag. Might not always be a good idea, but will
|
||||
// be sufficient at least with mkv. The problem is that this flag is
|
||||
// not well-defined in libavformat and is container-dependent.
|
||||
@@ -448,17 +308,12 @@ static bool do_read_next_packet(struct demuxer *demuxer,
|
||||
}
|
||||
}
|
||||
|
||||
double dts = pkt->dts != MP_NOPTS_VALUE ? pkt->dts : pkt->pts;
|
||||
if (src->dts == MP_NOPTS_VALUE || (dts != MP_NOPTS_VALUE && dts > src->dts))
|
||||
src->dts = dts;
|
||||
|
||||
pkt->stream = vs->sh->index;
|
||||
src->next = pkt;
|
||||
return true;
|
||||
demux_add_packet(vs->sh, pkt);
|
||||
return 1;
|
||||
|
||||
drop:
|
||||
talloc_free(pkt);
|
||||
return true;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void print_timeline(struct demuxer *demuxer)
|
||||
@@ -466,105 +321,47 @@ static void print_timeline(struct demuxer *demuxer)
|
||||
struct priv *p = demuxer->priv;
|
||||
|
||||
MP_VERBOSE(demuxer, "Timeline segments:\n");
|
||||
for (int x = 0; x < p->num_sources; x++) {
|
||||
struct virtual_source *src = p->sources[x];
|
||||
|
||||
if (x >= 1)
|
||||
MP_VERBOSE(demuxer, " --- new parallel stream ---\n");
|
||||
|
||||
for (int n = 0; n < src->num_segments; n++) {
|
||||
struct segment *seg = src->segments[n];
|
||||
int src_num = n;
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (seg->d && src->segments[i]->d == seg->d) {
|
||||
src_num = i;
|
||||
break;
|
||||
}
|
||||
for (int n = 0; n < p->num_segments; n++) {
|
||||
struct segment *seg = p->segments[n];
|
||||
int src_num = n;
|
||||
for (int i = 0; i < n - 1; i++) {
|
||||
if (seg->d && p->segments[i]->d == seg->d) {
|
||||
src_num = i;
|
||||
break;
|
||||
}
|
||||
MP_VERBOSE(demuxer, " %2d: %12f - %12f [%12f] (",
|
||||
n, seg->start, seg->end, seg->d_start);
|
||||
for (int i = 0; i < seg->num_stream_map; i++) {
|
||||
struct virtual_stream *vs = seg->stream_map[i];
|
||||
MP_VERBOSE(demuxer, "%s%d", i ? " " : "",
|
||||
vs ? vs->sh->index : -1);
|
||||
}
|
||||
MP_VERBOSE(demuxer, ")\n source %d:'%s'\n", src_num, seg->url);
|
||||
}
|
||||
|
||||
if (src->dash)
|
||||
MP_VERBOSE(demuxer, " (Using pseudo-DASH mode.)\n");
|
||||
MP_VERBOSE(demuxer, " %2d: %12f [%12f] (", n, seg->start, seg->d_start);
|
||||
for (int i = 0; i < seg->num_stream_map; i++)
|
||||
MP_VERBOSE(demuxer, "%s%d", i ? " " : "", seg->stream_map[i]);
|
||||
MP_VERBOSE(demuxer, ") %d:'%s'\n", src_num, seg->url);
|
||||
}
|
||||
MP_VERBOSE(demuxer, "Total duration: %f\n", p->duration);
|
||||
|
||||
if (p->dash)
|
||||
MP_VERBOSE(demuxer, "Durations and offsets are non-authoritative.\n");
|
||||
}
|
||||
|
||||
static int d_open(struct demuxer *demuxer, enum demux_check check)
|
||||
{
|
||||
struct priv *p = demuxer->priv = talloc_zero(demuxer, struct priv);
|
||||
p->tl = demuxer->params ? demuxer->params->timeline : NULL;
|
||||
if (!p->tl || p->tl->num_pars < 1)
|
||||
if (!p->tl || p->tl->num_parts < 1)
|
||||
return -1;
|
||||
|
||||
p->duration = p->tl->parts[p->tl->num_parts].start;
|
||||
|
||||
demuxer->chapters = p->tl->chapters;
|
||||
demuxer->num_chapters = p->tl->num_chapters;
|
||||
|
||||
struct demuxer *meta = p->tl->meta;
|
||||
if (!meta)
|
||||
return -1;
|
||||
struct demuxer *meta = p->tl->track_layout;
|
||||
demuxer->metadata = meta->metadata;
|
||||
demuxer->attachments = meta->attachments;
|
||||
demuxer->num_attachments = meta->num_attachments;
|
||||
demuxer->editions = meta->editions;
|
||||
demuxer->num_editions = meta->num_editions;
|
||||
demuxer->edition = meta->edition;
|
||||
|
||||
for (int n = 0; n < p->tl->num_pars; n++) {
|
||||
if (!add_tl(demuxer, p->tl->pars[n]))
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!p->num_sources)
|
||||
return -1;
|
||||
|
||||
demuxer->is_network |= p->tl->is_network;
|
||||
demuxer->is_streaming |= p->tl->is_streaming;
|
||||
|
||||
demuxer->duration = p->duration;
|
||||
|
||||
print_timeline(demuxer);
|
||||
|
||||
demuxer->seekable = true;
|
||||
demuxer->partially_seekable = false;
|
||||
|
||||
demuxer->filetype = talloc_asprintf(p, "%s/%s",
|
||||
p->tl->format,
|
||||
meta->filetype ? meta->filetype : meta->desc->name);
|
||||
|
||||
reselect_streams(demuxer);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool add_tl(struct demuxer *demuxer, struct timeline_par *tl)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
|
||||
struct virtual_source *src = talloc_ptrtype(p, src);
|
||||
*src = (struct virtual_source){
|
||||
.tl = tl,
|
||||
.dash = tl->dash,
|
||||
.no_clip = tl->no_clip || tl->dash,
|
||||
.dts = MP_NOPTS_VALUE,
|
||||
};
|
||||
|
||||
if (!tl->num_parts || !tl->track_layout)
|
||||
return false;
|
||||
|
||||
MP_TARRAY_APPEND(p, p->sources, p->num_sources, src);
|
||||
|
||||
p->duration = MPMAX(p->duration, tl->parts[tl->num_parts - 1].end);
|
||||
|
||||
struct demuxer *meta = tl->track_layout;
|
||||
|
||||
int num_streams = demux_get_num_stream(meta);
|
||||
for (int n = 0; n < num_streams; n++) {
|
||||
struct sh_stream *sh = demux_get_stream(meta, n);
|
||||
@@ -581,73 +378,78 @@ static bool add_tl(struct demuxer *demuxer, struct timeline_par *tl)
|
||||
demux_add_sh_stream(demuxer, new);
|
||||
struct virtual_stream *vs = talloc_ptrtype(p, vs);
|
||||
*vs = (struct virtual_stream){
|
||||
.src = src,
|
||||
.sh = new,
|
||||
};
|
||||
MP_TARRAY_APPEND(p, p->streams, p->num_streams, vs);
|
||||
assert(demux_get_stream(demuxer, p->num_streams - 1) == new);
|
||||
MP_TARRAY_APPEND(src, src->streams, src->num_streams, vs);
|
||||
}
|
||||
|
||||
for (int n = 0; n < tl->num_parts; n++) {
|
||||
struct timeline_part *part = &tl->parts[n];
|
||||
for (int n = 0; n < p->tl->num_parts; n++) {
|
||||
struct timeline_part *part = &p->tl->parts[n];
|
||||
struct timeline_part *next = &p->tl->parts[n + 1];
|
||||
|
||||
// demux_timeline already does caching, doing it for the sub-demuxers
|
||||
// would be pointless and wasteful.
|
||||
if (part->source) {
|
||||
demuxer->is_network |= part->source->is_network;
|
||||
demuxer->is_streaming |= part->source->is_streaming;
|
||||
}
|
||||
if (part->source)
|
||||
demux_disable_cache(part->source);
|
||||
|
||||
struct segment *seg = talloc_ptrtype(src, seg);
|
||||
struct segment *seg = talloc_ptrtype(p, seg);
|
||||
*seg = (struct segment){
|
||||
.d = part->source,
|
||||
.url = part->source ? part->source->filename : part->url,
|
||||
.lazy = !part->source,
|
||||
.d_start = part->source_start,
|
||||
.start = part->start,
|
||||
.end = part->end,
|
||||
.end = next->start,
|
||||
};
|
||||
|
||||
associate_streams(demuxer, src, seg);
|
||||
associate_streams(demuxer, seg);
|
||||
|
||||
seg->index = n;
|
||||
MP_TARRAY_APPEND(src, src->segments, src->num_segments, seg);
|
||||
MP_TARRAY_APPEND(p, p->segments, p->num_segments, seg);
|
||||
}
|
||||
|
||||
demuxer->is_network |= tl->track_layout->is_network;
|
||||
demuxer->is_streaming |= tl->track_layout->is_streaming;
|
||||
return true;
|
||||
p->dash = p->tl->dash;
|
||||
|
||||
print_timeline(demuxer);
|
||||
|
||||
demuxer->seekable = true;
|
||||
demuxer->partially_seekable = false;
|
||||
|
||||
demuxer->filetype = meta->filetype ? meta->filetype : meta->desc->name;
|
||||
|
||||
demuxer->is_network = p->tl->demuxer->is_network;
|
||||
|
||||
reselect_streams(demuxer);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void d_close(struct demuxer *demuxer)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
|
||||
for (int x = 0; x < p->num_sources; x++) {
|
||||
struct virtual_source *src = p->sources[x];
|
||||
|
||||
src->current = NULL;
|
||||
TA_FREEP(&src->next);
|
||||
close_lazy_segments(demuxer, src);
|
||||
}
|
||||
|
||||
struct demuxer *master = p->tl->demuxer;
|
||||
p->current = NULL;
|
||||
close_lazy_segments(demuxer);
|
||||
timeline_destroy(p->tl);
|
||||
demux_free(master);
|
||||
free_demuxer(master);
|
||||
}
|
||||
|
||||
static void d_switched_tracks(struct demuxer *demuxer)
|
||||
static int d_control(struct demuxer *demuxer, int cmd, void *arg)
|
||||
{
|
||||
reselect_streams(demuxer);
|
||||
if (cmd == DEMUXER_CTRL_SWITCHED_TRACKS) {
|
||||
reselect_streams(demuxer);
|
||||
return CONTROL_OK;
|
||||
}
|
||||
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
|
||||
const demuxer_desc_t demuxer_desc_timeline = {
|
||||
.name = "timeline",
|
||||
.desc = "timeline segments",
|
||||
.read_packet = d_read_packet,
|
||||
.fill_buffer = d_fill_buffer,
|
||||
.open = d_open,
|
||||
.close = d_close,
|
||||
.seek = d_seek,
|
||||
.switched_tracks = d_switched_tracks,
|
||||
.control = d_control,
|
||||
};
|
||||
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
* Copyright (C) 2001 Alex Beregszaszi
|
||||
*
|
||||
* Feb 19, 2002: Significant rewrites by Charles R. Henrich (henrich@msu.edu)
|
||||
* to add support for audio, and bktr *BSD support.
|
||||
*
|
||||
* 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 <assert.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/msg.h"
|
||||
|
||||
#include "options/m_option.h"
|
||||
#include "options/m_config.h"
|
||||
#include "options/options.h"
|
||||
|
||||
#include "demux.h"
|
||||
#include "codec_tags.h"
|
||||
|
||||
#include "audio/format.h"
|
||||
#include "osdep/endian.h"
|
||||
|
||||
#include "stream/stream.h"
|
||||
#include "stream/tv.h"
|
||||
|
||||
static int demux_open_tv(demuxer_t *demuxer, enum demux_check check)
|
||||
{
|
||||
tvi_handle_t *tvh;
|
||||
const tvi_functions_t *funcs;
|
||||
|
||||
if (check > DEMUX_CHECK_REQUEST)
|
||||
return -1;
|
||||
|
||||
if (!demuxer->stream->info || strcmp(demuxer->stream->info->name, "tv") != 0)
|
||||
return -1;
|
||||
|
||||
tv_param_t *params = mp_get_config_group(demuxer, demuxer->global,
|
||||
&tv_params_conf);
|
||||
bstr urlparams = bstr0(demuxer->stream->path);
|
||||
bstr channel, input;
|
||||
bstr_split_tok(urlparams, "/", &channel, &input);
|
||||
if (channel.len) {
|
||||
talloc_free(params->channel);
|
||||
params->channel = bstrto0(NULL, channel);
|
||||
}
|
||||
if (input.len) {
|
||||
bstr r;
|
||||
params->input = bstrtoll(input, &r, 0);
|
||||
if (r.len) {
|
||||
MP_ERR(demuxer->stream, "invalid input: '%.*s'\n", BSTR_P(input));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
assert(demuxer->priv==NULL);
|
||||
if(!(tvh=tv_begin(params, demuxer->log))) return -1;
|
||||
if (!tvh->functions->init(tvh->priv)) return -1;
|
||||
|
||||
tvh->demuxer = demuxer;
|
||||
|
||||
if (!open_tv(tvh)){
|
||||
tv_uninit(tvh);
|
||||
return -1;
|
||||
}
|
||||
funcs = tvh->functions;
|
||||
demuxer->priv=tvh;
|
||||
|
||||
struct sh_stream *sh_v = demux_alloc_sh_stream(STREAM_VIDEO);
|
||||
|
||||
/* get IMAGE FORMAT */
|
||||
int fourcc;
|
||||
funcs->control(tvh->priv, TVI_CONTROL_VID_GET_FORMAT, &fourcc);
|
||||
if (fourcc == MP_FOURCC_MJPEG || fourcc == MP_FOURCC_JPEG) {
|
||||
sh_v->codec->codec = "mjpeg";
|
||||
} else {
|
||||
sh_v->codec->codec = "rawvideo";
|
||||
sh_v->codec->codec_tag = fourcc;
|
||||
}
|
||||
|
||||
/* set FPS and FRAMETIME */
|
||||
|
||||
if(!sh_v->codec->fps)
|
||||
{
|
||||
float tmp;
|
||||
if (funcs->control(tvh->priv, TVI_CONTROL_VID_GET_FPS, &tmp) != TVI_CONTROL_TRUE)
|
||||
sh_v->codec->fps = 25.0f; /* on PAL */
|
||||
else sh_v->codec->fps = tmp;
|
||||
}
|
||||
|
||||
if (tvh->tv_param->fps != -1.0f)
|
||||
sh_v->codec->fps = tvh->tv_param->fps;
|
||||
|
||||
/* If playback only mode, go to immediate mode, fail silently */
|
||||
if(tvh->tv_param->immediate == 1)
|
||||
{
|
||||
funcs->control(tvh->priv, TVI_CONTROL_IMMEDIATE, 0);
|
||||
tvh->tv_param->audio = 0;
|
||||
}
|
||||
|
||||
/* set width */
|
||||
funcs->control(tvh->priv, TVI_CONTROL_VID_GET_WIDTH, &sh_v->codec->disp_w);
|
||||
|
||||
/* set height */
|
||||
funcs->control(tvh->priv, TVI_CONTROL_VID_GET_HEIGHT, &sh_v->codec->disp_h);
|
||||
|
||||
demux_add_sh_stream(demuxer, sh_v);
|
||||
|
||||
demuxer->seekable = 0;
|
||||
|
||||
/* here comes audio init */
|
||||
if (tvh->tv_param->audio && funcs->control(tvh->priv, TVI_CONTROL_IS_AUDIO, 0) == TVI_CONTROL_TRUE)
|
||||
{
|
||||
int audio_format;
|
||||
|
||||
/* yeah, audio is present */
|
||||
|
||||
funcs->control(tvh->priv, TVI_CONTROL_AUD_SET_SAMPLERATE,
|
||||
&tvh->tv_param->audiorate);
|
||||
|
||||
if (funcs->control(tvh->priv, TVI_CONTROL_AUD_GET_FORMAT, &audio_format) != TVI_CONTROL_TRUE)
|
||||
goto no_audio;
|
||||
|
||||
switch(audio_format)
|
||||
{
|
||||
// This is the only format any of the current inputs generate.
|
||||
case AF_FORMAT_S16:
|
||||
break;
|
||||
default:
|
||||
MP_ERR(tvh, "Audio type '%s' unsupported!\n",
|
||||
af_fmt_to_str(audio_format));
|
||||
goto no_audio;
|
||||
}
|
||||
|
||||
struct sh_stream *sh_a = demux_alloc_sh_stream(STREAM_AUDIO);
|
||||
|
||||
funcs->control(tvh->priv, TVI_CONTROL_AUD_GET_SAMPLERATE,
|
||||
&sh_a->codec->samplerate);
|
||||
int nchannels = sh_a->codec->channels.num;
|
||||
funcs->control(tvh->priv, TVI_CONTROL_AUD_GET_CHANNELS,
|
||||
&nchannels);
|
||||
mp_chmap_from_channels(&sh_a->codec->channels, nchannels);
|
||||
|
||||
// s16ne
|
||||
mp_set_pcm_codec(sh_a->codec, true, false, 16, BYTE_ORDER == BIG_ENDIAN);
|
||||
|
||||
demux_add_sh_stream(demuxer, sh_a);
|
||||
|
||||
MP_VERBOSE(tvh, " TV audio: %d channels, %d bits, %d Hz\n",
|
||||
nchannels, 16, sh_a->codec->samplerate);
|
||||
}
|
||||
no_audio:
|
||||
|
||||
if(!(funcs->start(tvh->priv))){
|
||||
// start failed :(
|
||||
tv_uninit(tvh);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* set color eq */
|
||||
tv_set_color_options(tvh, TV_COLOR_BRIGHTNESS, tvh->tv_param->brightness);
|
||||
tv_set_color_options(tvh, TV_COLOR_HUE, tvh->tv_param->hue);
|
||||
tv_set_color_options(tvh, TV_COLOR_SATURATION, tvh->tv_param->saturation);
|
||||
tv_set_color_options(tvh, TV_COLOR_CONTRAST, tvh->tv_param->contrast);
|
||||
|
||||
if(tvh->tv_param->gain!=-1)
|
||||
if(funcs->control(tvh->priv,TVI_CONTROL_VID_SET_GAIN,&tvh->tv_param->gain)!=TVI_CONTROL_TRUE)
|
||||
MP_WARN(tvh, "Unable to set gain control!\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void demux_close_tv(demuxer_t *demuxer)
|
||||
{
|
||||
tvi_handle_t *tvh=(tvi_handle_t*)(demuxer->priv);
|
||||
if (!tvh) return;
|
||||
tv_uninit(tvh);
|
||||
free(tvh);
|
||||
demuxer->priv=NULL;
|
||||
}
|
||||
|
||||
static int demux_tv_fill_buffer(demuxer_t *demux)
|
||||
{
|
||||
tvi_handle_t *tvh=(tvi_handle_t*)(demux->priv);
|
||||
demux_packet_t* dp;
|
||||
unsigned int len=0;
|
||||
struct sh_stream *want_audio = NULL, *want_video = NULL;
|
||||
|
||||
int num_streams = demux_get_num_stream(demux);
|
||||
for (int n = 0; n < num_streams; n++) {
|
||||
struct sh_stream *sh = demux_get_stream(demux, n);
|
||||
if (!demux_has_packet(sh) && demux_stream_is_selected(sh)) {
|
||||
if (sh->type == STREAM_AUDIO)
|
||||
want_audio = sh;
|
||||
if (sh->type == STREAM_VIDEO)
|
||||
want_video = sh;
|
||||
}
|
||||
}
|
||||
|
||||
/* ================== ADD AUDIO PACKET =================== */
|
||||
|
||||
if (want_audio && tvh->tv_param->audio &&
|
||||
tvh->functions->control(tvh->priv,
|
||||
TVI_CONTROL_IS_AUDIO, 0) == TVI_CONTROL_TRUE)
|
||||
{
|
||||
len = tvh->functions->get_audio_framesize(tvh->priv);
|
||||
|
||||
dp=new_demux_packet(len);
|
||||
if (dp) {
|
||||
dp->keyframe = true;
|
||||
dp->pts=tvh->functions->grab_audio_frame(tvh->priv, dp->buffer,len);
|
||||
demux_add_packet(want_audio, dp);
|
||||
}
|
||||
}
|
||||
|
||||
/* ================== ADD VIDEO PACKET =================== */
|
||||
|
||||
if (want_video && tvh->functions->control(tvh->priv,
|
||||
TVI_CONTROL_IS_VIDEO, 0) == TVI_CONTROL_TRUE)
|
||||
{
|
||||
len = tvh->functions->get_video_framesize(tvh->priv);
|
||||
dp=new_demux_packet(len);
|
||||
if (dp) {
|
||||
dp->keyframe = true;
|
||||
dp->pts=tvh->functions->grab_video_frame(tvh->priv, dp->buffer, len);
|
||||
demux_add_packet(want_video, dp);
|
||||
}
|
||||
}
|
||||
|
||||
if (tvh->tv_param->scan) tv_scan(tvh);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int demux_tv_control(demuxer_t *demuxer, int cmd, void *arg)
|
||||
{
|
||||
tvi_handle_t *tvh=(tvi_handle_t*)(demuxer->priv);
|
||||
if (cmd != DEMUXER_CTRL_STREAM_CTRL)
|
||||
return CONTROL_UNKNOWN;
|
||||
struct demux_ctrl_stream_ctrl *ctrl = arg;
|
||||
ctrl->res = tv_stream_control(tvh, ctrl->ctrl, ctrl->arg);
|
||||
return CONTROL_OK;
|
||||
}
|
||||
|
||||
const demuxer_desc_t demuxer_desc_tv = {
|
||||
.name = "tv",
|
||||
.desc = "TV card demuxer",
|
||||
.fill_buffer = demux_tv_fill_buffer,
|
||||
.control = demux_tv_control,
|
||||
.open = demux_open_tv,
|
||||
.close = demux_close_tv,
|
||||
};
|
||||
+9
-30
@@ -31,26 +31,11 @@
|
||||
|
||||
#include "packet.h"
|
||||
|
||||
// Free any refcounted data dp holds (but don't free dp itself). This does not
|
||||
// care about pointers that are _not_ refcounted (like demux_packet.codec).
|
||||
// Normally, a user should use talloc_free(dp). This function is only for
|
||||
// annoyingly specific obscure use cases.
|
||||
void demux_packet_unref_contents(struct demux_packet *dp)
|
||||
{
|
||||
if (dp->avpacket) {
|
||||
assert(!dp->is_cached);
|
||||
av_packet_unref(dp->avpacket);
|
||||
talloc_free(dp->avpacket);
|
||||
dp->avpacket = NULL;
|
||||
dp->buffer = NULL;
|
||||
dp->len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void packet_destroy(void *ptr)
|
||||
{
|
||||
struct demux_packet *dp = ptr;
|
||||
demux_packet_unref_contents(dp);
|
||||
av_packet_unref(dp->avpacket);
|
||||
mp_packet_tags_unref(dp->metadata);
|
||||
}
|
||||
|
||||
// This actually preserves only data and side data, not PTS/DTS/pos/etc.
|
||||
@@ -71,6 +56,7 @@ struct demux_packet *new_demux_packet_from_avpacket(struct AVPacket *avpkt)
|
||||
.end = MP_NOPTS_VALUE,
|
||||
.stream = -1,
|
||||
.avpacket = talloc_zero(dp, AVPacket),
|
||||
.kf_seek_pts = MP_NOPTS_VALUE,
|
||||
};
|
||||
av_init_packet(dp->avpacket);
|
||||
int r = -1;
|
||||
@@ -124,10 +110,8 @@ struct demux_packet *new_demux_packet(size_t len)
|
||||
void demux_packet_shorten(struct demux_packet *dp, size_t len)
|
||||
{
|
||||
assert(len <= dp->len);
|
||||
if (dp->len) {
|
||||
dp->len = len;
|
||||
memset(dp->buffer + dp->len, 0, AV_INPUT_BUFFER_PADDING_SIZE);
|
||||
}
|
||||
av_shrink_packet(dp->avpacket, len);
|
||||
dp->len = dp->avpacket->size;
|
||||
}
|
||||
|
||||
void free_demux_packet(struct demux_packet *dp)
|
||||
@@ -145,10 +129,9 @@ void demux_packet_copy_attribs(struct demux_packet *dst, struct demux_packet *sr
|
||||
dst->start = src->start;
|
||||
dst->end = src->end;
|
||||
dst->codec = src->codec;
|
||||
dst->back_restart = src->back_restart;
|
||||
dst->back_preroll = src->back_preroll;
|
||||
dst->keyframe = src->keyframe;
|
||||
dst->stream = src->stream;
|
||||
mp_packet_tags_setref(&dst->metadata, src->metadata);
|
||||
}
|
||||
|
||||
struct demux_packet *demux_copy_packet(struct demux_packet *dp)
|
||||
@@ -166,7 +149,7 @@ struct demux_packet *demux_copy_packet(struct demux_packet *dp)
|
||||
return new;
|
||||
}
|
||||
|
||||
#define ROUND_ALLOC(s) MP_ALIGN_UP((s), 16)
|
||||
#define ROUND_ALLOC(s) MP_ALIGN_UP(s, 64)
|
||||
|
||||
// Attempt to estimate the total memory consumption of the given packet.
|
||||
// This is important if we store thousands of packets and not to exceed
|
||||
@@ -179,15 +162,11 @@ struct demux_packet *demux_copy_packet(struct demux_packet *dp)
|
||||
size_t demux_packet_estimate_total_size(struct demux_packet *dp)
|
||||
{
|
||||
size_t size = ROUND_ALLOC(sizeof(struct demux_packet));
|
||||
size += 8 * sizeof(void *); // ta overhead
|
||||
size += 10 * sizeof(void *); // additional estimate for ta_ext_header
|
||||
size += ROUND_ALLOC(dp->len);
|
||||
if (dp->avpacket) {
|
||||
assert(!dp->is_cached);
|
||||
size += ROUND_ALLOC(dp->len);
|
||||
size += ROUND_ALLOC(sizeof(AVPacket));
|
||||
size += 8 * sizeof(void *); // ta overhead
|
||||
size += ROUND_ALLOC(sizeof(AVBufferRef));
|
||||
size += ROUND_ALLOC(64); // upper bound estimate on sizeof(AVBuffer)
|
||||
size += 64; // upper bound estimate on sizeof(AVBuffer)
|
||||
size += ROUND_ALLOC(dp->avpacket->side_data_elems *
|
||||
sizeof(dp->avpacket->side_data[0]));
|
||||
for (int n = 0; n < dp->avpacket->side_data_elems; n++)
|
||||
|
||||
+7
-26
@@ -24,34 +24,16 @@
|
||||
|
||||
// Holds one packet/frame/whatever
|
||||
typedef struct demux_packet {
|
||||
int len;
|
||||
unsigned char *buffer;
|
||||
|
||||
double pts;
|
||||
double dts;
|
||||
double duration;
|
||||
int64_t pos; // position in source file byte stream
|
||||
|
||||
union {
|
||||
// Normally valid for packets.
|
||||
struct {
|
||||
unsigned char *buffer;
|
||||
size_t len;
|
||||
};
|
||||
|
||||
// Used if is_cached==true, special uses only.
|
||||
struct {
|
||||
uint64_t pos;
|
||||
} cached_data;
|
||||
};
|
||||
|
||||
int stream; // source stream index (typically sh_stream.index)
|
||||
|
||||
bool keyframe;
|
||||
|
||||
// backward playback
|
||||
bool back_restart : 1; // restart point (reverse and return previous frames)
|
||||
bool back_preroll : 1; // initial discarded frame for smooth decoder reinit
|
||||
|
||||
// If true, cached_data is valid, while buffer/len are not.
|
||||
bool is_cached : 1;
|
||||
int64_t pos; // position in source file byte stream
|
||||
int stream; // source stream index
|
||||
|
||||
// segmentation (ordered chapters, EDL)
|
||||
bool segmented;
|
||||
@@ -61,7 +43,8 @@ typedef struct demux_packet {
|
||||
// private
|
||||
struct demux_packet *next;
|
||||
struct AVPacket *avpacket; // keep the buffer allocation and sidedata
|
||||
uint64_t cum_pos; // demux.c internal: cumulative size until _start_ of pkt
|
||||
double kf_seek_pts; // demux.c internal: seek pts for keyframe range
|
||||
struct mp_packet_tags *metadata; // timed metadata (demux.c internal)
|
||||
} demux_packet_t;
|
||||
|
||||
struct AVBufferRef;
|
||||
@@ -81,6 +64,4 @@ int demux_packet_set_padding(struct demux_packet *dp, int start, int end);
|
||||
int demux_packet_add_blockadditional(struct demux_packet *dp, uint64_t id,
|
||||
void *data, size_t size);
|
||||
|
||||
void demux_packet_unref_contents(struct demux_packet *dp);
|
||||
|
||||
#endif /* MPLAYER_DEMUX_PACKET_H */
|
||||
Loaded 100 of 404 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user