Compare commits

..
42 Commits
Author SHA1 Message Date
Jan Ekström 723fd02919 Release 0.29.1 2018-10-02 22:03:41 +03:00
Akemi 7c2368ad7d cocoa-cb: fix side by side Split View
when entering a Split View a windowDidEnterFullScreen event happens
without a previous toggleFullScreen call. in that case it tries to stop
an animation that was never initiated by us and basically breaks the
system initiated fullscreen, or in this case the Split View. immediately
after entering the fullscreen it tries top stop the animation and
resizes the window, which causes the window to exit fullscreen. only
try to stop an animation that was initiated by us and is safe to stop.

(cherry picked from commit 2b0b9bb6a1)
2018-10-02 20:36:55 +03:00
Rodger Combs 008ddf42a1 {mac,cocoa}: trim trailing null out of macosx_icon when loading it
This prevents crashes when loading the application icon image.

Suggested-by: Akemi <der.richter@gmx.de>
(cherry picked from commit 1842a932d3)
2018-10-02 00:42:58 +03:00
Rodger Combs 5b9cf1d0a6 mac: fix crash if we can't get an event tap
without assistive-device permissions the event tap can't be create on
10.14 any more which lead to an assertion.

System Preferences > Security & Privacy > Privacy > Accessibility and
add mpv or your terminal App to the list.

(cherry picked from commit ada4f7c600)
2018-10-02 00:42:51 +03:00
Niklas Haas a985e8ed6b encode: fix AVPacket deinitialization logic
Since this function is called with packets on the stack, trying to free
them makes no sense. Instead, it should unref (which is what
`av_interleaved_write_frame` does anyway, rather than freeing).

Also, the calling code tried unreffing the packet a second time, even
after it was "freed" by the callee in the failure case - and after
ownership was taken over by `av_interleaved_write_frame` in the
successful case. Both of these cases were wrong.

(cherry picked from commit 6d61c5f68a)
2018-10-01 20:29:51 +03:00
wm4 36eeeecff6 demux_lavf: hack-fix EDL mp4 DASH hack
I encountered a stream that fails with "Could not demux init fragment.".
It turns out this is a regression from the recent change to that code.
The assumption was that demux_lavf.c would treat this as concatenated
stream - which it does, but not for probing.

Doing this transparently is hard without doing it properly. Doing it
properly would mean creating some sort of stream_concat (reminiscent of
that FFmpeg security bug). I probably don't want to go there, and I
think libavformat should just support this directly, so whatever.
Hack-fix this with the knowledge that the init segment will always
contain the headers.

(cherry picked from commit 315004a38a)
2018-10-01 18:55:06 +03:00
wm4 e74ffab36e stream_libarchive: fix hangs when demuxer does out of bound seeks
This happened with a .flac file inside an archive. It tried to seek
beyond the end of the archive entry in a format where seeking isn't
supported. stream_libarchive handles these situations by skipping data.
But when the end of the archive is reached, archive_read_data() returns
0. While libarchive didn't bother to fucking document this, they do say
it's supposed to work like read(), so I guess a return value of 0 really
means EOF. So change the "< 0" to "<= 0". Also add some error logging.

The same file actually worked without out of bounds reads when
extracted, so there still might be something very wrong.

(cherry picked from commit 7e85dc2167)
2018-10-01 18:54:54 +03:00
wm4 4f47596043 demux_lavf: v4l streams are not seekable
FFmpeg is retarded enough not to give us any indication whether it is
(unless we query fields not in the ABI/API). I bet FFmpeg developers
love it when library users have to litter their code with duplicated
information.

(cherry picked from commit 20d381d1e9)
2018-10-01 18:54:27 +03:00
Akemi 3e8fd22f07 cocoa-cb: add Apple Software Renderer support
by default the pixel format creation falls back to software renderer
when everything fails. this is mostly needed for VMs. additionally one
can directly request an sw renderer or exclude it entirely.

(cherry picked from commit 8d2d0f0640)
2018-09-30 23:38:11 +03:00
Akemi 53a1a955d7 cocoa-cb: move macOS option retrieval to the earliest point possible
moved the retrieval of the macOS specific options from the backend
initialisation to the initialisation of the CocoaCB class, the earliest
point possible. this way macOS specific options can be used for the
opengl context creation for example.

(cherry picked from commit 44e49aee3c)
2018-09-30 23:38:04 +03:00
Akemi 5a9add1fb3 add swift as main dependency so all dependencies can be disabled easily
(cherry picked from commit 5c2056ad0a)
2018-09-30 23:37:57 +03:00
Akemi 89e52a228c build: check for Swift version and disable dependencies if needed
(cherry picked from commit 50787acb27)
2018-09-30 23:37:50 +03:00
Anton Kindestam 46969b449a drm_atomic: Allow to create atomic context w/o drmprime video plane
This is to improve the experience when running with default settings
on a driver that doesn't have any overlay planes (or indeed only one
plane), but still supports DRM atomic. Since the drmprime video plane
is set to pick an overlay plane by default it would fail on these
drivers due to not being able to create any atomic context. Users with
such cards had to specify --drm-video-plane-id manually to some bogus
value (it's not used after all).

The "video" plane is only ever used by the drmprime-drm hwdec interop,
which is not used at all in the typical usecase where everything is
actually rendered on to the "OSD" plane using EGL, so having an atomic
context without the "video" plane should be fine most of the time.

(cherry picked from commit 810acf32d6)
2018-09-30 17:16:40 +03:00
Anton Kindestam ed9d644e06 manpage: minor fix to --drm-format
Looking at other examples, a bar should be used when listing OPT_CHOICE options.

(cherry picked from commit f277f9f6d2)
2018-09-30 17:16:33 +03:00
Tom Yan 5a7aca4be8 demux/packet: fix demux_packet_shorten
for the rawaudio demuxer to do the expected gapless playback

(cherry picked from commit 95636c65e7)
2018-09-30 12:35:13 +03:00
Jan Ekström af39f87258 travis: enable CI for release branches
(cherry picked from commit 085943df25)
2018-09-29 23:07:19 +03:00
Jan Ekström e0682490d7 ao_alsa: log the ALSA state if we get a non-XRUN error
The ALSA state generally can tell us more information in case we
get an unexpected error.

(cherry picked from commit cea4ff3e5f)
2018-09-29 23:07:19 +03:00
Jan Ekström 03e3abcb0c ao_alsa: handle XRUNs separately from other errors
According to ALSA doxy, EPIPE is a synonym to SND_PCM_STATE_XRUN,
and that is a state that we should attempt to automatically recover
from. In case recovery fails, log an error and return zero.

A warning message will still be output for each XRUN since those
are not something we should generally be receiving.

(cherry picked from commit fdc952486a)
2018-09-29 23:07:19 +03:00
Jan Ekström 1dd77e41c1 ao_alsa: early exit get_space if paused or ALSA is not ready
This has been way too long coming, and for me to notice that a
whole lot of ao_alsa functions do an early return if the AO is
paused.

For the STATE_SETUP case, I had this reproduced once, and never
since. Still, seems like we can start calling this function before
the ALSA device has been fully initialized so we might as well
early exit in that case.

(cherry picked from commit 3218a58082)
2018-09-29 23:07:19 +03:00
Ricardo Constantino 8e0b94e7eb man/options: emphasize ytdl_hook's script options
(cherry picked from commit 9c184078a6)
2018-09-29 23:07:15 +03:00
Ricardo Constantino 0f7b08b0fc ytdl_hook: fix audio not being picked up for some sites
(cherry picked from commit 9dbab9661c)
2018-09-29 23:06:49 +03:00
Niklas Haas a8a4c7def7 ao_jack: only auto-connect to audio ports
This prevents ao_jack from auto-connecting to MIDI ports (or other,
hypothetical future port types).

(cherry picked from commit fed0ea111b)
2018-09-29 23:06:49 +03:00
TSaaristo c994a81aa4 encode: fix expected streams when using --lavfi-complex
mpctx->current_track[0][STREAM_VIDEO] (and STREAM_AUDIO) are empty when
using --lavfi-complex. Moving the muxer stream hinting after audio/video chain
initialization and checking if the chains exist fixes encoding with --lavfi-complex.

Previously, the output audio/video streams did not get prepared and the encode
would fail due to unexpected stream addition.

(cherry picked from commit 3744d0bda9)
2018-09-29 23:06:49 +03:00
Niklas Haas 5ae13ad13b mp_image: strip all HDR peak information from SDR clips
By overriding it with 1.0 (aka SDR). This prevents blowing up on
mistagged clips.

Fixes #6111

(cherry picked from commit 48c38f730d)
2018-09-29 23:06:49 +03:00
Tom Yan 0a92548684 ao_pulse: fix tlength calculation
also remove the now unused non-sensical af_fmt_seconds_to_bytes.

(cherry picked from commit 9d6b15ab32)
2018-09-29 23:06:49 +03:00
Michael Hoang 56098c5636 Revert "ao_openal: enable building on OSX"
This reverts commit af6126adbe. Apple's
OpenAL support is ridiculously out of date, revert back to just using
OpenAL Soft on macOS (fixes #4645).

(cherry picked from commit 91786fa99c)
2018-09-29 23:06:49 +03:00
Niklas Haas 312b88d360 vo_gpu: avoid overwriting compute shader block sizes
When using multiple compute shaders as part of the same pass, there can
be a conflict in the block sizes. In the problematic case, the HDR
detection shader can collide with the polar sampling shader. In this
case, the solution is clear - the passes that can handle any size should
"give in" and not overwrite the block sizes.

Fixes #6083.

(cherry picked from commit 1890ca024e)
2018-09-29 23:06:49 +03:00
Hector Martin b97f172faa af_rubberband: reset delay to 0 on reset
This fixes A-V drift on seeking

(cherry picked from commit a10754f038)
2018-09-29 23:06:49 +03:00
Anton Kindestam f3a58e5f4a manpage: fix reference to --tone-mapping by old option name
(cherry picked from commit d2d7dba6ee)
2018-09-29 23:06:49 +03:00
Ricardo Constantino 49606f5a3b ytdl_hook: always load ytdl:// links with ytdl_hook first
Suggested in IRC by sfan5.

(cherry picked from commit d8131568c8)
2018-09-29 23:06:03 +03:00
Michael Hoang 7d5468ace3 osx: Fix initialization and access of service menu
Replace dot syntax with accessor syntax so that clang no longer errors
out due to not finding the property servicesMenu on NSApp.

(cherry picked from commit 4e9e46b9f8)
2018-09-29 22:22:20 +03:00
Akemi a4e7580c5a cocoa-cb: fix crash on macOS 10.10
the colorspace of the layer is only available on 10.11 and upwards.

Fixes #6041

(cherry picked from commit 049816c145)
2018-09-29 22:22:20 +03:00
Akemi 7b91bf7f20 cocoa-cb: fix crash when no screen is available
instead of force unwrapping and chaining the optional vars in our
containsMouseLocation function, safely unwrap and guard the resulting
var.

Fixes #6062

(cherry picked from commit 6bf0edc59c)
2018-09-29 22:22:20 +03:00
Tom Yan 4243d06761 audio/format: decouple af_fmt_is_planar from af_fmt_to_planar
so that af_fmt_to_planar (and hence af_fmt_from_planar) can just
return the input when it is not an interleaved (planar) format.

(cherry picked from commit f2311ff514)
2018-09-29 22:22:20 +03:00
jaseg 4d074a960d manpage: Correct show-text duration default value
duration is parsed as an integer, and the default value is used if ```-1``` is passed. Passing ```-``` as described here causes a parameter value error.

(cherry picked from commit cfecbac863)
2018-09-29 22:22:20 +03:00
pavelxdd 071556efee manpage: fix --vf exclamation mark description
An exclamation mark disables the filter by default instead of
enabling it.

(cherry picked from commit 759a6a259e)
2018-09-29 22:22:20 +03:00
Tom Yan 876341417a audio/format: minor fix for af_fmt_from_planar
See af_fmt_to_planar.

(cherry picked from commit 4e91cb72ef)
2018-09-29 22:22:20 +03:00
Martin Herkt 6a57e1a298 ci: explicitly call waf with python3
Python 2 may not be present in the CI images in the future,
but waf’s shebang line still uses its executable name.
Explicitly call the right major version of the interpreter.

(cherry picked from commit 6eb59fea2f)
2018-09-29 22:22:20 +03:00
Jan Ekström 0e316d7032 ci: do bootstrap outside the docker container
This way the docker container in itself does no networking.

It seems like travis disabled network access from the actual docker
containers.

(cherry picked from commit 2d785f3ea3)
2018-09-29 22:22:20 +03:00
Yclept Nemo 0914da082f stream_smb/stream_file: fix write_buffer
Functions `write` and `smbc_write` are given a diminishing buffer of
incorrect constant size. After partial writes, the code would do another
write of the full original length, failing to subtract the amount
already written.
2018-07-29 12:59:03 +03:00
Yclept Nemo 021bea1592 stream_smb: locking to bypass libsmbclient issues
Thread-unsafe libsmbclient is likely to crash when used simultaneously
across threads. For example, by:

    mpv "smb://...mkv" --sub-file "smb://...srt"

Work around this by locking all calls to this library under a single
global mutex. This is a temporary solution. When the libsmbclient bug
[1] is fixed, switch to the code from branch [2] which uses a new api to
create per-thread smb contexts. Fixes [3].

[1] https://bugzilla.samba.org/show_bug.cgi?id=11413
[2] https://github.com/orbisvicis/mpv/tree/smbclient_threaded_api
[3] https://github.com/mpv-player/mpv/issues/5936

(cherry picked from commit 112b3fa922)
2018-07-29 12:59:03 +03:00
Daniel M. Capella 0d134dc010 manpage: fixup mistaken show playlist/track-list shortcuts
This was mistaken in 496b13227b and
not noticed in review.

(cherry picked from commit 45beb7073a)
2018-07-23 01:33:53 +03:00
404 changed files with 24122 additions and 24027 deletions

No files matched your search

+6 -4
View File
@@ -1,5 +1,7 @@
Read this before you submit this pull request:
https://github.com/mpv-player/mpv/blob/master/DOCS/contribute.md
Ideally, you shouldn't need enter any text here, and your commit messages should
explain your changes sufficiently (especially why they are needed). Read
https://github.com/mpv-player/mpv/blob/master/DOCS/contribute.md for coding
style and development conventions. Remove this text block, but if you haven't
agreed to it before, leave the following sentence in place:
Reading this link and following the rules will get your pull request reviewed
and merged faster. Nobody wants lazy pull requests.
I agree that my changes can be relicensed to LGPL 2.1 or later.
+12 -42
View File
@@ -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
+13 -3
View File
@@ -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).
+1 -19
View File
@@ -16,7 +16,7 @@ The version number is the same as used for MPV_CLIENT_API_VERSION (see
``client.h`` how to convert between major/minor version numbers and the flat
32 bit integer).
Also, read the section ``Compatibility`` in ``client.h``, and compatibility.rst.
Also, read the section ``Compatibility`` in ``client.h``.
Options, commands, properties
=============================
@@ -32,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
-177
View File
@@ -1,177 +0,0 @@
CLI and API compatibility policy
================================
Human users and API users rely on the mpv/libmpv/scripting/IPC interface not
breaking their use case. On the other hand, active development occasionally
requires breaking things, such as removing old options or changing options in
a way that may break.
This document lists rules when, what, and how incompatible changes can be made.
It's interesting both for mpv developers, who want to change user-visible parts,
and mpv users, who want to know what is guaranteed to remain stable.
Any of the rules below may be overridden by statements in more specific
documentation (for example, if the manpage says that a particular option may be
removed any time, it means that, and the option probably won't even go through
deprecation).
Additions
---------
Additions are basically always allowed. API users etc. are supposed to deal with
the possibility that for example new API functions are added. Some parts of the
API may document how they are extended.
Options, commands, properties, events, hooks (command interface)
----------------------------------------------------------------
All of these are important for interfacing both with end users and API users
(which include Lua scripts, libmpv, and the JSON IPC). As such, they constitute
a large part of the user interface and APIs.
All incompatible changes to this must be declared in interface-changes.rst.
(This may also list compatible additions, but it's not a requirement.)
Degrees of importance and compatibility preservation
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Critical and central parts of the command interface have the strictest
requirements. It may not be reasonable to break them, and other means to achieve
some change have to be found. For example, the "seek" command is a bit of a
mess, but since changing it would likely affect almost every user, it may be
impossible to break at least the commonly used syntax. If changed anyway, there
should be a deprecation period of at least 1 year, during which the command
still works, and possibly a warning should remain even after this.
Important/often used parts must be deprecated for at least 2 releases before
they can be broken. There must be at least 1 release where the deprecated
functionality still works, and a replacement is available (if technically
reasonable). For example, a feature deprecated in mpv 0.30.0 may be removed in
mpv 0.32.0. Minor releases do not count towards this.
Less useful parts can be broken immediately, but must come with some sort of
removal warning-
Parts for debugging and testing may be removed any time, potentially even
without any sort of documentation.
Currently, the importance of a part is not documented and not even well-defined,
which is probably a mistake.
Renaming or removing options
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Typically, renaming an option can be done in a compatible way with OPT_REPLACED.
You may need to check whether the corresponding properties still work (including
messy details like observing properties for changes).
OPT_REMOVED can be used to inform the user of alternatives or reasons for the
removal, which is better than an option not found error. Likewise,
m_option.deprecation_message should be set to something helpful.
Both OPT_REPLACED and OPT_REMOVED can remain in the code for a long time, since
they're unintrusive and hopefully make incompatible changes less painful for
users.
Scripting APIs
--------------
This affects internal scripting APIs (currently Lua and JavaScript).
Vaguely the same rules as with the command interface apply. Since there is a
large number of scripts, an effort should be made to provide compatibility
for old scripts, but it does not need to be stronger than that of the command
interface.
Undocumented parts of the scripting APIs are _not_ guaranteed for compatibility.
This applies especially for internals. Languages like Lua do not have strict
access control (nor does the mpv code try to emulate any), so if a script
accesses private parts, and breaks on the next mpv release, it's not mpv's
problem.
JSON IPC
--------
The JSON IPC is a thin protocol wrapping the libmpv API and the command
interface. Compatibility-wise, it's about the same as the scripting APIs.
The JSON protocol commands should remain as compatible as possible, and it
should probably accept the current way commands are delimited (line breaks)
forever.
The protocol may accept non-standard JSON extensions, but only standard JSON
(possibly with restrictions) is guaranteed for compatibility. Clients which want
to remain compatible should not use any extensions.
CLI
---
Things such as default key bindings do not necessarily require compatibility.
However, the release notes should be extremely clear on changes to "important"
key bindings. Bindings which restore the old behavior should be added to
restore-old-bindings.conf.
Some option parsing is CLI-only and not available from libmpv or scripting. No
compatibility guarantees come with them. However, the rules which mpv uses to
distinguish between options and filenames must remain consistent (if the
non-deprecated options syntax is used).
Terminal and log output
-----------------------
There are no compatibility guarantees for the terminal output, or the text
logged via ``MPV_EVENT_LOG_MESSAGE`` and similar APIs. In particular, scripts
invoking mpv CLI are extremely discouraged from trying to parse text output,
and should use other mechanisms such as the JSON IPC.
Protocols, filters, demuxers, etc.
----------------------------------
Which of these are present is generally not guaranteed, and can even depend
on compile time settings.
The filter list and their sub-options are considered part of the
command-interface.
libmpv C API
------------
The libmpv client API (such as ``<libmpv/client.h>``) mostly gives access to
the command interface. The API itself (if looked at as a component separate
from the command interface) is intended to be extremely stable.
All API changes are documented in client-api-changes.rst.
API compatibility
^^^^^^^^^^^^^^^^^
The API is *always* compatible. Incompatible changes are only allowed on major
API version changes (see ``MPV_CLIENT_API_VERSION``). A major version change is
an extremely rare event, which means usually no API symbols are ever removed.
Essentially removing API functions by making them always return an error, or
making it do nothing is allowed in cases where it is unlikely to break most
clients, but requires a deprecation period of 2 releases. (This has happened to
``mpv_suspend()`` for example.)
API symbols can be deprecated. This should be clearly marked in the doxygen
with ``@deprecated``, and if possible, the affected API symbols should not be
visible if the API user defines ``MPV_ENABLE_DEPRECATED`` to 0.
ABI compatibility
^^^^^^^^^^^^^^^^^
The ABI must never be broken, except on major API version changes. For example,
constants don't change their values.
Structs are tricky. If a struct can be allocated by a user (such as ``mpv_node``),
no fields can be added. (Unless it's an union, and the addition does not change
the offset or alignment of any of the fields or the struct itself. This has
happened to ``mpv_node`` in the past.) If a struct is allocated by libmpv only,
new fields can be appended to the end (for example ``mpv_event``).
The ABI is only backward compatible. This means if a host application is linked
to an older libmpv, and libmpv is updated to a newer version, it will still
work (as in not causing any undefined behavior).
Forward compatibility (an application would work with an older libmpv than it
was linked to) is not required.
+1 -1
View File
@@ -136,7 +136,7 @@ pacman -S git python $MINGW_PACKAGE_PREFIX-{pkg-config,gcc}
# Install the most important MinGW-w64 dependencies. libass and lcms2 are also
# pulled in as dependencies of ffmpeg.
pacman -S $MINGW_PACKAGE_PREFIX-{ffmpeg,libjpeg-turbo,lua51}
pacman -S $MINGW_PACKAGE_PREFIX-{ffmpeg,libjpeg-turbo,lua51,angleproject-git}
```
Building mpv
+20 -69
View File
@@ -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
-102
View File
@@ -100,18 +100,6 @@ entries affect all other file entries in the EDL file. Their format is highly
implementation specific. They should generally follow the file header, and come
before any file entries.
Disabling chapter generation and copying
========================================
By default, chapters from the source ranges are copied to the virtual file's
chapters. Also, a chapter is inserted after each range. This can be disabled
with the ``no_chapters`` header.
Example::
!no_chapters
MP4 DASH
========
@@ -143,45 +131,6 @@ The current implementation will
- not add segment boundaries as chapter points
- require full compatibility between all segments (same codec etc.)
Another header part of this mechanism is ``no_clip``. This header is similar
to ``mp4_dash``, but does not include on-demand opening/closing of segments,
and does not support init segments. It also exists solely to support internal
ytdl requirements.
The ``mp4_dash`` and ``no_clip`` headers are not part of the core EDL format.
They may be changed or removed at any time, depending on mpv's internal
requirements.
Separate files for tracks
=========================
The special ``new_stream`` header lets you specify separate parts and time
offsets for separate tracks. This can for example be used to source audio and
video track from separate files.
Example::
# mpv EDL v0
video.mkv
!new_stream
audio.mkv
This adds all tracks from both files to the virtual track list. Upon playback,
the tracks will be played at the same time, instead of appending them. The files
can contain more than 1 stream; the apparent effect is the same as if the second
part after the ``!new_stream`` part were in a separate ``.edl`` file and added
with ``--external-file``.
Note that all metadata between the stream sets created by ``new_stream`` is
disjoint. Global metadata is taken from the first part only.
In context of mpv, this is redundant to the ``--audio-file`` and
``--external-file`` options, but (as of this writing) has the advantage that
this will use a unified cache for all streams.
The ``new_stream`` header is not part of the core EDL format. It may be changed
or removed at any time, depending on mpv's internal requirements.
Timestamp format
================
@@ -198,54 +147,6 @@ Example::
Plays chapter 3 and ends with the start of chapter 7 (4 chapters later).
Implicit chapters
=================
mpv will add one chapter per segment entry to the virtual timeline.
By default, the chapter's titles will match the entries' filenames.
You can override set the ``title`` option to override the chapter title for
that segment.
Example::
# mpv EDL v0
cap.ts,5,240
OP.mkv,0,90,title=Show Opening
The virtual timeline will have two chapters, one called "cap.ts" from 0-240s
and a second one called "Show Opening" from 240-330s.
Entry which defines the track layout
====================================
Normally, you're supposed to put only files with compatible layouts into an EDL
file. However, at least the mpv implementation accepts entries that use
different codecs, or even have a different number of audio/video/subtitle
tracks. In this case, it's not obvious, which virtual tracks the EDL show should
expose when being played.
Currently, mpv will apply an arbitrary heuristic which tracks the EDL file
should expose. (Before mpv 0.30.0, it always used the first source file in the
segment list.)
You can set the ``layout`` option to ``this`` to make a specific entry define
the track layout.
Example::
# mpv EDL v0
file_with_2_streams.ts,5,240
file_with_5_streams.mkv,0,90,layout=this
The way the different virtual EDL tracks are associated with the per-segment
ones is highly implementation-defined, and uses a heuristic. If a segment is
missing a track, there will be a "hole", and bad behavior may result. Improving
this is subject to further development (due to being fringe cases, they don't
have a high priority).
If future versions of mpv change this again, this option may be ignored.
Syntax of EDL URIs
==================
@@ -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.
+4 -89
View File
@@ -7,16 +7,11 @@ mpv provides access to its internals via the following means:
- commands
- properties
- events
- hooks
The sum of these mechanisms is sometimes called command interface.
All of these are important for interfacing both with end users and API users
(which include Lua scripts, libmpv, and the JSON IPC). As such, they constitute
a large part of the user interface and APIs.
Also see compatibility.rst.
This document lists changes to them. New changes are added to the top.
Interface changes
@@ -24,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
+38
View File
@@ -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
-4
View File
@@ -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
View File
File diff suppressed because it is too large. Load diff
+2 -40
View File
@@ -74,12 +74,6 @@ some wrapper like .NET's NamedPipeClientStream.)
Protocol
--------
The protocol uses UTF-8-only JSON as defined by RFC-8259. Unlike standard JSON,
"\u" escape sequences are not allowed to construct surrogate pairs. To avoid
getting conflicts, encode all text characters including and above codepoint
U+0020 as UTF-8. mpv might output broken UTF-8 in corner cases (see "UTF-8"
section below).
Clients can execute commands on the player by sending JSON messages of the
following form:
@@ -114,10 +108,7 @@ Because events can occur at any time, it may be difficult at times to determine
which response goes with which command. Commands may optionally include a
``request_id`` which, if provided in the command request, will be copied
verbatim into the response. mpv does not intrepret the ``request_id`` in any
way; it is solely for the use of the requester. The only requirement is that
the ``request_id`` field must be an integer (a number without fractional parts
in the range ``-2^63..2^63-1``). Using other types is deprecated and will
currently show a warning. In the future, this will raise an error.
way; it is solely for the use of the requester.
For example, this request:
@@ -131,11 +122,6 @@ Would generate this response:
{ "error": "success", "data": 1.468135, "request_id": 100 }
If you don't specify a ``request_id``, command replies will set it to 0.
Commands may run asynchronously in the future, instead of blocking the socket
until a reply is sent.
All commands, replies, and events are separated from each other with a line
break character (``\n``).
@@ -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.
+1 -6
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large. Load diff
+8 -50
View File
@@ -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
View File
@@ -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
View File
@@ -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.
+3 -4
View File
@@ -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.
+51 -8
View File
@@ -1,6 +1,8 @@
![mpv logo](https://raw.githubusercontent.com/mpv-player/mpv.io/master/source/images/mpv-logo-128.png)
![http://mpv.io/](https://raw.githubusercontent.com/mpv-player/mpv.io/master/source/images/mpv-logo-128.png)
# mpv
## mpv
--------------
* [External links](#external-links)
@@ -9,9 +11,12 @@
* [Downloads](#downloads)
* [Changelog](#changelog)
* [Compilation](#compilation)
* [FFmpeg vs. Libav](#ffmpeg-vs-libav)
* [FFmpeg ABI compatibility](#ffmpeg-abi-compatibility)
* [Release cycle](#release-cycle)
* [Bug reports](#bug-reports)
* [Contributing](#contributing)
* [Relation to MPlayer and mplayer2](#relation-to-mplayer-and-mplayer2)
* [License](#license)
* [Contact](#contact)
@@ -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
View File
@@ -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
+3 -1
View File
@@ -10,9 +10,11 @@ export PYTHON=/usr/bin/python3
"$PYTHON" bootstrap.py
"$PYTHON" waf configure \
--check-c-compiler=gcc \
--enable-spirv-cross \
--disable-egl-angle-lib \
--enable-crossc \
--enable-d3d-hwaccel \
--enable-d3d11 \
--enable-egl-angle \
--enable-jpeg \
--enable-lcms2 \
--enable-libarchive \
+9 -9
View File
@@ -1,8 +1,6 @@
#!/usr/bin/bash
set -e
export PYTHON=/usr/bin/python3
# Write an empty fonts.conf to speed up fc-cache
export FONTCONFIG_FILE=/dummy-fonts.conf
cat >"$FONTCONFIG_FILE" <<EOF
@@ -14,12 +12,12 @@ EOF
# Install build dependencies for mpv
pacman -S --noconfirm --needed \
$MINGW_PACKAGE_PREFIX-toolchain \
$MINGW_PACKAGE_PREFIX-angleproject-git \
$MINGW_PACKAGE_PREFIX-cmake \
$MINGW_PACKAGE_PREFIX-lcms2 \
$MINGW_PACKAGE_PREFIX-libarchive \
$MINGW_PACKAGE_PREFIX-libass \
$MINGW_PACKAGE_PREFIX-libjpeg-turbo \
$MINGW_PACKAGE_PREFIX-libplacebo \
$MINGW_PACKAGE_PREFIX-lua51 \
$MINGW_PACKAGE_PREFIX-ninja \
$MINGW_PACKAGE_PREFIX-rubberband \
@@ -54,19 +52,21 @@ pacman -Sc --noconfirm
# Compile shaderc
(
git clone --depth=1 https://github.com/google/shaderc && cd shaderc
"$PYTHON" utils/git-sync-deps
git clone --depth=1 https://github.com/google/glslang.git third_party/glslang
git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Tools.git third_party/spirv-tools
git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Headers.git third_party/spirv-headers
mkdir build && cd build
cmake -GNinja -DCMAKE_BUILD_TYPE=Release -DSHADERC_SKIP_TESTS=ON \
-DCMAKE_INSTALL_PREFIX=$MINGW_PREFIX ..
ninja install
cp -f libshaderc/libshaderc_shared.dll $MINGW_PREFIX/bin/
)
# Compile SPIRV-Cross
# Compile crossc
(
git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Cross && cd SPIRV-Cross
git clone --depth=1 https://github.com/rossy/crossc && cd crossc
git submodule update --init
mkdir build && cd build
cmake -GNinja -DSPIRV_CROSS_SHARED=ON -DCMAKE_INSTALL_PREFIX=$MINGW_PREFIX ..
ninja install
make -j4 install prefix=$MINGW_PREFIX
)
+2 -50
View File
@@ -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
View File
@@ -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)
-105
View File
@@ -1,105 +0,0 @@
-- Test script for some command API details.
local utils = require("mp.utils")
function join(sep, arr, count)
local r = ""
if count == nil then
count = #arr
end
for i = 1, count do
if i > 1 then
r = r .. sep
end
r = r .. utils.to_string(arr[i])
end
return r
end
mp.observe_property("vo-configured", "bool", function(_, v)
if v ~= true then
return
end
print("async expand-text")
mp.command_native_async({"expand-text", "hello ${path}!"},
function(res, val, err)
print("done async expand-text: " .. join(" ", {res, val, err}))
end)
-- make screenshot writing very slow
mp.set_property("screenshot-format", "png")
mp.set_property("screenshot-png-compression", "9")
timer = mp.add_periodic_timer(0.1, function() print("I'm alive") end)
timer:resume()
print("Slow screenshot command...")
res, err = mp.command_native({"screenshot"})
print("done, res: " .. utils.to_string(res))
print("Slow screenshot async command...")
res, err = mp.command_native_async({"screenshot"}, function(res)
print("done (async), res: " .. utils.to_string(res))
timer:kill()
end)
print("done (sending), res: " .. utils.to_string(res))
print("Broken screenshot async command...")
mp.command_native_async({"screenshot-to-file", "/nonexistent/bogus.png"},
function(res, val, err)
print("done err scr.: " .. join(" ", {res, val, err}))
end)
mp.command_native_async({name = "subprocess", args = {"sh", "-c", "echo hi && sleep 10s"}, capture_stdout = true},
function(res, val, err)
print("done subprocess: " .. join(" ", {res, val, err}))
end)
local x = mp.command_native_async({name = "subprocess", args = {"sleep", "inf"}},
function(res, val, err)
print("done sleep inf subprocess: " .. join(" ", {res, val, err}))
end)
mp.add_timeout(15, function()
print("aborting sleep inf subprocess after timeout")
mp.abort_async_command(x)
end)
-- (assuming this "freezes")
local y = mp.command_native_async({name = "sub-add", url = "-"},
function(res, val, err)
print("done sub-add stdin: " .. join(" ", {res, val, err}))
end)
mp.add_timeout(20, function()
print("aborting sub-add stdin after timeout")
mp.abort_async_command(y)
end)
-- This should get killed on script exit.
mp.command_native_async({name = "subprocess", playback_only = false,
args = {"sleep", "inf"}}, function()end)
-- Runs detached; should be killed on player exit (forces timeout)
mp.command_native({_flags={"async"}, name = "subprocess",
playback_only = false, args = {"sleep", "inf"}})
end)
function freeze_test(playback_only)
-- This "freezes" the script, should be killed via timeout.
counter = counter and counter + 1 or 0
print("freeze! " .. counter)
local x = mp.command_native({name = "subprocess",
playback_only = playback_only,
args = {"sleep", "inf"}})
print("done, killed=" .. utils.to_string(x.killed_by_us))
end
mp.register_event("shutdown", function()
freeze_test(false)
end)
mp.register_event("idle", function()
freeze_test(true)
end)
-37
View File
@@ -1,37 +0,0 @@
-- Test a float property which internally uses NaN.
-- Run with --no-config (or just scale-param1 not set).
local utils = require 'mp.utils'
prop_name = "scale-param1"
-- internal NaN, return string "default" instead of NaN
v = mp.get_property_native(prop_name, "fail")
print("Exp:", "string", "\"default\"")
print("Got:", type(v), utils.to_string(v))
v = mp.get_property(prop_name)
print("Exp:", "default")
print("Got:", v)
-- not representable -> return provided fallback value
v = mp.get_property_number(prop_name, -100)
print("Exp:", -100)
print("Got:", v)
mp.set_property_native(prop_name, 123)
v = mp.get_property_number(prop_name, -100)
print("Exp:", "number", 123)
print("Got:", type(v), utils.to_string(v))
-- try to set an actual NaN
st, msg = mp.set_property_number(prop_name, 0.0/0)
print("Exp:", nil, "<message>")
print("Got:", st, msg)
-- set default
mp.set_property(prop_name, "default")
v = mp.get_property(prop_name)
print("Exp:", "default")
print("Got:", v)
-266
View File
@@ -1,266 +0,0 @@
--[[
Automatically skip in files if video frames with pre-supplied fingerprints are
detected. This will skip ahead by a pre-configured amount of time if a matching
video frame is detected.
This requires the vf_fingerprint video filter to be compiled in. Read the
documentation of this filter for caveats (which will automatically apply to
this script as well), such as no support for zero-copy hardware decoding.
You need to manually gather and provide fingerprints for video frames and add
them to a configuration file in script-opts/skip-logo.conf (the "script-opts"
directory must be in the mpv configuration directory, typically ~/.config/mpv/).
Example script-opts/skip-logo.conf:
cases = {
{
-- Skip ahead 10 seconds if a black frame was detected
-- Note: this is dangerous non-sense. It's just for demonstration.
name = "black frame", -- print if matched
skip = 10, -- number of seconds to skip forward
score = 0.3, -- required score
fingerprint = "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
},
{
-- Skip ahead 20 seconds if a white frame was detected
-- Note: this is dangerous non-sense. It's just for demonstration.
name = "fun2",
skip = 20,
fingerprint = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
},
}
This is actually a lua file. Lua was chosen because it seemed less of a pain to
parse. Future versions of this script may change the format.
The fingerprint is a video frame, converted to "gray" (8 bit per pixels), full
range, each pixel concatenated into an array, converted to a hex string. You
can produce these fingerprints by running this manually:
mpv --vf=fingerprint:print yourfile.mkv
This will log the fingerprint of each video frame to the console, along with its
timestamp. You find the fingerprint of a unique-enough looking frame, and add
it as entry to skip-logo.conf.
You can provide a score for "fuzziness". If no score is provided, a default
value of 0.3 is used. The score is inverse: 0 means exactly the same, while a
higher score means a higher difference. Currently, the score is computed as
euclidean distance between the video frame and the pre-provided fingerprint,
thus the highest score is 16. You probably want a score lower than 1 at least.
(This algorithm is very primitive, but also simple and fast to compute.)
There's always the danger of false positives, which might be quite annoying.
It's up to you what you hate more, the logo, or random skips if false positives
are detected. Also, it's always active, and might eat too much CPU with files
that have a high resolution or framerate. To temporarily disable the script,
having a keybind like this in your input.conf will be helpful:
ctrl+k vf toggle @skip-logo
This will disable/enable the fingerprint filter, which the script automatically
adds at start.
Another important caveat is that the script currently disables matching during
seeking or playback initialization, which means it cannot match the first few
frames of a video. This could be fixed, but the author was too lazy to do so.
--]]
local utils = require "mp.utils"
local msg = require "mp.msg"
local label = "skip-logo"
local meta_property = string.format("vf-metadata/%s", label)
local config = {}
local cases = {}
local cur_bmp
local seeking = false
local playback_start_pts = nil
-- Convert a hex string to an array. Convert each byte to a [0,1] float by
-- interpreting it as normalized uint8_t.
-- The data parameter, if not nil, may be used as storage (avoiding garbage).
local function hex_to_norm8(hex, data)
local size = math.floor(#hex / 2)
if #hex ~= size * 2 then
return nil
end
local res
if (data ~= nil) and (#data == size) then
res = data
else
res = {}
end
for i = 1, size do
local num = tonumber(hex:sub(i * 2, i * 2 + 1), 16)
if num == nil then
return nil
end
res[i] = num / 255.0
end
return res
end
local function compare_bmp(a, b)
if #a ~= #b then
return nil -- can't compare
end
local sum = 0
for i = 1, #a do
local diff = a[i] - b[i]
sum = sum + diff * diff
end
return math.sqrt(sum)
end
local function load_config()
local conf_file = mp.find_config_file("script-opts/skip-logo.conf")
local conf_fn
local err = nil
if conf_file then
if setfenv then
conf_fn, err = loadfile(conf_file)
if conf_fn then
setfenv(conf_fn, config)
end
else
msg.warn("Lua 5.2 was not tested, this might go wrong.")
conf_fn, err = loadfile(conf_file, "t", config)
end
else
err = "config file not found"
end
if conf_fn and (not err) then
local ok, err2 = pcall(conf_fn)
err = err2
end
if err then
msg.error("Failed to load config file:", err)
end
if config.cases then
for n, case in ipairs(config.cases) do
local err = nil
case.bitmap = hex_to_norm8(case.fingerprint)
if case.bitmap == nil then
err = "invalid or missing fingerprint field"
end
if case.score == nil then
case.score = 0.3
end
if type(case.score) ~= "number" then
err = "score field is not a number"
end
if type(case.skip) ~= "number" then
err = "skip field is not a number or missing"
end
if case.name == nil then
case.name = ("Entry %d"):format(n)
end
if err == nil then
cases[#cases + 1] = case
else
msg.error(("Entry %s: %s, ignoring."):format(case.name, err))
end
end
end
end
load_config()
-- Returns true on match and if something was done.
local function check_fingerprint(hex, pts)
local bmp = hex_to_norm8(hex, cur_bmp)
cur_bmp = bmp
-- If parsing the filter's result failed (well, it shouldn't).
assert(bmp ~= nil, "filter returned nonsense")
for _, case in ipairs(cases) do
local score = compare_bmp(case.bitmap, bmp)
if (score ~= nil) and (score <= case.score) then
msg.warn(("Matching %s: score=%f (required: %f) at %s, skipping %f seconds"):
format(case.name, score, case.score, mp.format_time(pts), case.skip))
mp.commandv("seek", pts + case.skip, "absolute+exact")
return true
end
end
return false
end
local function read_frames()
local result = mp.get_property_native(meta_property)
if result == nil then
return
end
-- Try to get all entries. Out of laziness, assume that there are at most
-- 100 entries. (In fact, vf_fingerprint limits it to 10.)
for i = 0, 99 do
local prefix = string.format("fp%d.", i)
local hex = result[prefix .. "hex"]
local pts = tonumber(result[prefix .. "pts"])
if (hex == nil) or (pts == nil) then
break
end
local skip = false -- blame Lua for not having "continue" or "goto", not me
-- If seeking just stopped, there will be frames before the seek target,
-- ignore them by checking the timestamps.
if playback_start_pts ~= nil then
if pts >= playback_start_pts then
playback_start_pts = nil -- just for robustness
else
skip = true
end
end
if not skip then
if check_fingerprint(hex, pts) then
break
end
end
end
end
mp.observe_property(meta_property, "none", function()
-- Ignore frames that are decoded/filtered during seeking.
if seeking then
return
end
read_frames()
end)
mp.observe_property("seeking", "bool", function(name, val)
seeking = val
if seeking == false then
playback_start_pts = mp.get_property_number("playback-time")
read_frames()
end
end)
local filters = mp.get_property_native("option-info/vf/choices", {})
local found = false
for _, f in ipairs(filters) do
if f == "fingerprint" then
found = true
break
end
end
if found then
mp.command(("no-osd vf add @%s:fingerprint"):format(label, filter))
else
msg.warn("vf_fingerprint not found")
end
+2 -2
View File
@@ -7,7 +7,7 @@ import fileinput
from optparse import OptionParser
def sh(command):
return os.popen(command).read().strip()
return os.popen(command).read()
def bundle_path(binary_name):
return "%s.app" % binary_name
@@ -80,7 +80,7 @@ def main():
if options.deps:
print("> bundling dependencies")
print(sh(" ".join(["TOOLS/dylib-unhell.py", target_binary(binary_name)])))
sh(" ".join(["TOOLS/dylib-unhell.py", target_binary(binary_name)]))
print("done.")
+1 -1
View File
@@ -1,6 +1,6 @@
#!/bin/sh
if [ "x$TARGET" != "xx86_64-w64-mingw32" ]; then
if [ "x$LIBAV" != "xffmpeg-git" ] || [ "x$TRAVIS_OS_NAME" != "xlinux" ]; then
# trigger build only on one of the matrix nodes
exit;
fi
Executable
+283
View File
@@ -0,0 +1,283 @@
#!/usr/bin/perl
# Generate ZSH completion
#
# This file is part of mpv.
#
# mpv is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# mpv is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with mpv. If not, see <http://www.gnu.org/licenses/>.
#
use strict;
use warnings;
use warnings FATAL => 'uninitialized';
my $mpv = $ARGV[0] || 'mpv';
my @opts = parse_main_opts('--list-options', '^ (\-\-[^\s\*]*)\*?\s*(.*)');
die "Couldn't find any options" unless (@opts);
my @ao = parse_opts('--ao=help', '^ ([^\s\:]*)\s*(.*)');
my @vo = parse_opts('--vo=help', '^ ([^\s\:]*)\s*(.*)');
my @af = parse_opts('--af=help', '^ ([^\s\:]*)\s*(.*)');
my @vf = parse_opts('--vf=help', '^ ([^\s\:]*)\s*(.*)');
my @protos = parse_opts('--list-protocols', '^ ([^\s]*)');
my ($opts_str, $ao_str, $vo_str, $af_str, $vf_str, $protos_str);
$opts_str .= qq{ '$_' \\\n} foreach (@opts);
chomp $opts_str;
$ao_str .= qq{ '$_' \\\n} foreach (@ao);
chomp $ao_str;
$vo_str .= qq{ '$_' \\\n} foreach (@vo);
chomp $vo_str;
$af_str .= qq{ '$_' \\\n} foreach (@af);
chomp $af_str;
$vf_str .= qq{ '$_' \\\n} foreach (@vf);
chomp $vf_str;
$protos_str = join(' ', @protos);
my $runtime_completions = <<'EOS';
profile|show-profile)
local -a profiles
local current
for current in "${(@f)$($words[1] --profile=help)}"; do
current=${current//\*/\\\*}
current=${current//\:/\\\:}
current=${current//\[/\\\[}
current=${current//\]/\\\]}
if [[ $current =~ $'\t'([^$'\t']*)$'\t'(.*) ]]; then
if [[ -n $match[2] ]]; then
current="$match[1][$match[2]]"
else
current="$match[1]"
fi
profiles=($profiles $current)
fi
done
if [[ $state == show-profile ]]; then
# For --show-profile, only one allowed
if (( ${#profiles} > 0 )); then
_values 'profile' $profiles && rc=0
fi
else
# For --profile, multiple allowed
profiles=($profiles 'help[list profiles]')
_values -s , 'profile(s)' $profiles && rc=0
fi
;;
audio-device)
local -a audio_devices
local current
for current in "${(@f)$($words[1] --audio-device=help)}"; do
current=${current//\*/\\\*}
current=${current//\:/\\\:}
current=${current//\[/\\\[}
current=${current//\]/\\\]}
if [[ $current =~ ' '\'([^\']*)\'' \('(.*)'\)' ]]; then
audio_devices=($audio_devices "$match[1][$match[2]]")
fi
done
audio_devices=($audio_devices 'help[list audio devices]')
_values 'audio device' $audio_devices && rc=0
;;
EOS
chomp $runtime_completions;
my $tmpl = <<"EOS";
#compdef mpv
# For customization, see:
# https://github.com/mpv-player/mpv/wiki/Zsh-completion-customization
local curcontext="\$curcontext" state state_descr line
typeset -A opt_args
local -a match mbegin mend
local MATCH MBEGIN MEND
# By default, don't complete URLs unless no files match
local -a tag_order
zstyle -a ":completion:*:*:\$service:*" tag-order tag_order || \
zstyle ":completion:*:*:\$service:*" tag-order '!urls'
local rc=1
_arguments -C -S \\
$opts_str
'*:files:->mfiles' && rc=0
case \$state in
ao)
_values -s , 'audio outputs' \\
$ao_str
&& rc=0
;;
vo)
_values -s , 'video outputs' \\
$vo_str
&& rc=0
;;
af)
_values -s , 'audio filters' \\
$af_str
&& rc=0
;;
vf)
_values -s , 'video filters' \\
$vf_str
&& rc=0
;;
$runtime_completions
files)
compset -P '*,'
compset -S ',*'
_files -r ',/ \\t\\n\\-' && rc=0
;;
mfiles)
local expl
_tags files urls
while _tags; do
_requested files expl 'media file' _files && rc=0
if _requested urls; then
while _next_label urls expl URL; do
_urls "\$expl[@]" && rc=0
compadd -S '' "\$expl[@]" $protos_str && rc=0
done
fi
(( rc )) || return 0
done
;;
esac
return rc
EOS
print $tmpl;
sub parse_main_opts {
my ($cmd, $regex) = @_;
my @list;
my @lines = call_mpv($cmd);
foreach my $line (@lines) {
my ($name, $desc) = ($line =~ /^$regex/) or next;
next if ($desc eq 'removed' || $desc eq 'alias');
if ($desc =~ /^Flag/) {
push @list, $name;
$name =~ /^--(.*)/;
if ($1 !~ /^(\{|\}|v|list-options|really-quiet|no-.*)$/) {
push @list, "--no-$1";
}
} elsif ($desc =~ /^Print/) {
push @list, $name;
} else {
# Option takes argument
my $entry = $name;
$desc =~ s/\:/\\:/g;
$entry .= "=-:$desc:";
if ($desc =~ /^Choices\\: ([^(]*)/) {
my $choices = $1;
$choices =~ s/ +$//; # strip trailing space
$entry .= "($choices)";
# If "no" is one of the choices, it can also be
# negated like a flag (--no-whatever).
if ($choices =~ /\bno\b/) {
$name =~ s/^--/--no-/;
push @list, $name;
}
} elsif ($line =~ /\[file\]/) {
$entry .= '->files';
} elsif ($name =~ /^--(ao|vo|af|vf|profile|show-profile|audio-device)$/) {
$entry .= "->$1";
}
push @list, $entry;
}
}
# Sort longest first, because zsh won't complete an option listed
# after one that's a prefix of it.
@list = sort {
$a =~ /([^=]*)/; my $ma = $1;
$b =~ /([^=]*)/; my $mb = $1;
length($mb) <=> length($ma)
} @list;
return @list;
}
sub parse_opts {
my ($cmd, $regex) = @_;
my @list;
my @lines = call_mpv($cmd);
foreach my $line (@lines) {
if ($line !~ /^$regex/) {
next;
}
my $entry = $1;
if (defined $2) {
my $desc = $2;
$desc =~ s/\:/\\:/g;
$entry .= "[$desc]";
}
push @list, $entry
}
return @list;
}
sub call_mpv {
my ($cmd) = @_;
my $output = `"$mpv" --no-config $cmd`;
if ($? == -1) {
die "Could not run mpv: $!";
} elsif ((my $exit_code = $? >> 8) != 0) {
die "mpv returned $exit_code with output:\n$output";
}
return split /\n/, $output;
}
+1 -1
View File
@@ -1 +1 @@
0.30.0
0.29.1
-6
View File
@@ -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
-50
View File
@@ -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;
-4
View File
@@ -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))
-3
View File
@@ -87,9 +87,6 @@ static void copy_planes(struct mp_audio_buffer *ab,
uint8_t **dst, int dst_offset,
uint8_t **src, int src_offset, int length)
{
if (!length)
return;
for (int n = 0; n < ab->num_planes; n++) {
memmove((char *)dst[n] + dst_offset * ab->sstride,
(char *)src[n] + src_offset * ab->sstride,
+4 -12
View File
@@ -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
View File
@@ -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;
+112
View File
@@ -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,
};
+47
View File
@@ -0,0 +1,47 @@
/*
* Copyright (C) 2002 Anders Johansson ajh@atri.curtin.edu.au
*
* This file is part of mpv.
*
* mpv is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* mpv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MPLAYER_EQUALIZER_H
#define MPLAYER_EQUALIZER_H
/* Equalizer plugin header file defines struct used for setting or
getting the gain of a specific channel and frequency */
typedef struct equalizer_s
{
float gain; // Gain in dB -15 - 15
int channel; // Channel number 0 - 5
int band; // Frequency band 0 - 9
}equalizer_t;
/* The different frequency bands are:
nr. center frequency
0 31.25 Hz
1 62.50 Hz
2 125.0 Hz
3 250.0 Hz
4 500.0 Hz
5 1.000 kHz
6 2.000 kHz
7 4.000 kHz
8 8.000 kHz
9 16.00 kHz
*/
#endif /* MPLAYER_EQUALIZER_H */
-2
View File
@@ -27,14 +27,12 @@ static const struct {
{AV_SAMPLE_FMT_U8, AF_FORMAT_U8},
{AV_SAMPLE_FMT_S16, AF_FORMAT_S16},
{AV_SAMPLE_FMT_S32, AF_FORMAT_S32},
{AV_SAMPLE_FMT_S64, AF_FORMAT_S64},
{AV_SAMPLE_FMT_FLT, AF_FORMAT_FLOAT},
{AV_SAMPLE_FMT_DBL, AF_FORMAT_DOUBLE},
{AV_SAMPLE_FMT_U8P, AF_FORMAT_U8P},
{AV_SAMPLE_FMT_S16P, AF_FORMAT_S16P},
{AV_SAMPLE_FMT_S32P, AF_FORMAT_S32P},
{AV_SAMPLE_FMT_S64P, AF_FORMAT_S64P},
{AV_SAMPLE_FMT_FLTP, AF_FORMAT_FLOATP},
{AV_SAMPLE_FMT_DBLP, AF_FORMAT_DOUBLEP},
-4
View File
@@ -29,7 +29,6 @@ int af_fmt_to_bytes(int format)
case AF_FORMAT_U8: return 1;
case AF_FORMAT_S16: return 2;
case AF_FORMAT_S32: return 4;
case AF_FORMAT_S64: return 8;
case AF_FORMAT_FLOAT: return 4;
case AF_FORMAT_DOUBLE: return 8;
}
@@ -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";
-2
View File
@@ -27,7 +27,6 @@ enum af_format {
AF_FORMAT_U8,
AF_FORMAT_S16,
AF_FORMAT_S32,
AF_FORMAT_S64,
AF_FORMAT_FLOAT,
AF_FORMAT_DOUBLE,
@@ -35,7 +34,6 @@ enum af_format {
AF_FORMAT_U8P,
AF_FORMAT_S16P,
AF_FORMAT_S32P,
AF_FORMAT_S64P,
AF_FORMAT_FLOATP,
AF_FORMAT_DOUBLEP,
+4 -37
View File
@@ -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
View File
@@ -35,6 +35,10 @@ enum aocontrol {
AOCONTROL_SET_MUTE,
// Has char* as argument, which contains the desired stream title.
AOCONTROL_UPDATE_STREAM_TITLE,
// the AO does the equivalent of af_volume (return CONTROL_TRUE if yes)
AOCONTROL_HAS_SOFT_VOLUME,
// like above, but volume persists (per app), mpv won't restore volume
AOCONTROL_HAS_PER_APP_VOLUME,
};
// If set, then the queued audio data is the last. Note that after a while, new
@@ -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
View File
@@ -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,
};
+3 -3
View File
@@ -49,8 +49,8 @@ static OSStatus render_cb_lpcm(void *ctx, AudioUnitRenderActionFlags *aflags,
planes[n] = buffer_list->mBuffers[n].mData;
int64_t end = mp_time_us();
end += ca_frames_to_us(ao, frames);
end += p->device_latency * 1e6;
end += ca_get_latency(ts) + ca_frames_to_us(ao, frames);
ao_read_data(ao, planes, frames, end);
return noErr;
}
@@ -155,7 +155,7 @@ static void start(struct ao *ao)
struct priv *p = ao->priv;
AVAudioSession *instance = AVAudioSession.sharedInstance;
p->device_latency = [instance outputLatency] + [instance IOBufferDuration];
p->device_latency = [instance outputLatency];
OSStatus err = AudioOutputUnitStart(p->audio_unit);
CHECK_CA_WARN("can't start audio unit");
@@ -192,7 +192,7 @@ const struct ao_driver audio_out_audiounit = {
.name = "audiounit",
.uninit = uninit,
.init = init,
.reset = stop,
.pause = stop,
.resume = start,
.priv_size = sizeof(struct priv),
};
+2
View File
@@ -117,6 +117,8 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
return get_volume(ao, arg);
case AOCONTROL_SET_VOLUME:
return set_volume(ao, arg);
case AOCONTROL_HAS_SOFT_VOLUME:
return CONTROL_TRUE;
}
return CONTROL_UNKNOWN;
}
+1 -17
View File
@@ -31,8 +31,6 @@
#if HAVE_COREAUDIO
#include "audio/out/ao_coreaudio_properties.h"
#include <CoreAudio/HostTime.h>
#else
#include <mach/mach_time.h>
#endif
CFStringRef cfstr_from_cstr(char *str)
@@ -312,9 +310,9 @@ int64_t ca_frames_to_us(struct ao *ao, uint32_t frames)
return frames / (float) ao->samplerate * 1e6;
}
#if HAVE_COREAUDIO
int64_t ca_get_latency(const AudioTimeStamp *ts)
{
#if HAVE_COREAUDIO
uint64_t out = AudioConvertHostTimeToNanos(ts->mHostTime);
uint64_t now = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());
@@ -322,22 +320,8 @@ int64_t ca_get_latency(const AudioTimeStamp *ts)
return 0;
return (out - now) * 1e-3;
#else
static mach_timebase_info_data_t timebase;
if (timebase.denom == 0)
mach_timebase_info(&timebase);
uint64_t out = ts->mHostTime;
uint64_t now = mach_absolute_time();
if (now > out)
return 0;
return (out - now) * timebase.numer / timebase.denom / 1e3;
#endif
}
#if HAVE_COREAUDIO
bool ca_stream_supports_compressed(struct ao *ao, AudioStreamID stream)
{
AudioStreamRangedDescription *formats = NULL;
+2
View File
@@ -65,7 +65,9 @@ bool ca_asbd_is_better(AudioStreamBasicDescription *req,
AudioStreamBasicDescription *new);
int64_t ca_frames_to_us(struct ao *ao, uint32_t frames);
#if HAVE_COREAUDIO
int64_t ca_get_latency(const AudioTimeStamp *ts);
#endif
#if HAVE_COREAUDIO
bool ca_stream_supports_compressed(struct ao *ao, AudioStreamID stream);
+2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -61,7 +61,6 @@ struct priv {
char *cfg_host;
int cfg_buffer;
int cfg_latency_hacks;
int cfg_allow_suspended;
};
#define GENERIC_ERR_MSG(str) \
@@ -202,18 +201,13 @@ static const struct format_map {
static pa_encoding_t map_digital_format(int format)
{
switch (format) {
case AF_FORMAT_S_AC3: return PA_ENCODING_AC3_IEC61937;
case AF_FORMAT_S_EAC3: return PA_ENCODING_EAC3_IEC61937;
case AF_FORMAT_S_MP3: return PA_ENCODING_MPEG_IEC61937;
case AF_FORMAT_S_DTS: return PA_ENCODING_DTS_IEC61937;
#ifdef PA_ENCODING_DTSHD_IEC61937
case AF_FORMAT_S_DTSHD: return PA_ENCODING_DTSHD_IEC61937;
#endif
case AF_FORMAT_S_AC3: return PA_ENCODING_AC3_IEC61937;
case AF_FORMAT_S_EAC3: return PA_ENCODING_EAC3_IEC61937;
case AF_FORMAT_S_MP3: return PA_ENCODING_MPEG_IEC61937;
case AF_FORMAT_S_DTS:
case AF_FORMAT_S_DTSHD: return PA_ENCODING_DTS_IEC61937;
#ifdef PA_ENCODING_MPEG2_AAC_IEC61937
case AF_FORMAT_S_AAC: return PA_ENCODING_MPEG2_AAC_IEC61937;
#endif
#ifdef PA_ENCODING_TRUEHD_IEC61937
case AF_FORMAT_S_TRUEHD: return PA_ENCODING_TRUEHD_IEC61937;
case AF_FORMAT_S_AAC: return PA_ENCODING_MPEG2_AAC_IEC61937;
#endif
default:
if (af_fmt_is_spdif(format))
@@ -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",
-1
View File
@@ -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
+2
View File
@@ -61,6 +61,8 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
return CONTROL_FALSE;
sio_setvol(p->hdl, vol->left * SIO_MAXVOL / 100);
break;
case AOCONTROL_HAS_SOFT_VOLUME:
return CONTROL_TRUE;
default:
return CONTROL_UNKNOWN;
}
+4
View File
@@ -340,6 +340,8 @@ static int thread_control_exclusive(struct ao *ao, enum aocontrol cmd, void *arg
if (!(state->vol_hw_support & ENDPOINT_HARDWARE_SUPPORT_MUTE))
return CONTROL_FALSE;
break;
case AOCONTROL_HAS_PER_APP_VOLUME:
return CONTROL_FALSE;
}
float volume;
@@ -398,6 +400,8 @@ static int thread_control_shared(struct ao *ao, enum aocontrol cmd, void *arg)
mute = *(bool *)arg;
ISimpleAudioVolume_SetMute(state->pAudioVolume, mute, NULL);
return CONTROL_OK;
case AOCONTROL_HAS_PER_APP_VOLUME:
return CONTROL_TRUE;
}
return CONTROL_UNKNOWN;
}
+1 -7
View File
@@ -68,7 +68,7 @@ struct ao {
char *redirect;
// Internal events (use ao_request_reload(), ao_hotplug_event())
atomic_uint events_;
atomic_int events_;
// Float gain multiplicator
mp_atomic_float gain;
@@ -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
View File
@@ -153,13 +153,8 @@ int ao_read_data(struct ao *ao, void **data, int samples, int64_t out_time_us)
int buffered_bytes = mp_ring_buffered(p->buffers[0]);
bytes = MPMIN(buffered_bytes, full_bytes);
if (full_bytes > bytes && !atomic_load(&p->draining)) {
if (ao->driver->reports_underruns) {
ao_underrun_event(ao);
} else {
atomic_fetch_add(&p->underflow, (full_bytes - bytes) / ao->sstride);
}
}
if (buffered_bytes < bytes && !atomic_load(&p->draining))
atomic_fetch_add(&p->underflow, (bytes - buffered_bytes) / ao->sstride);
if (bytes > 0)
atomic_store(&p->end_time_us, out_time_us);
-4
View File
@@ -16,10 +16,6 @@ if os.path.exists("waf"):
print("Found 'waf', skipping download.")
sys.exit(0)
if "--no-download" in sys.argv[1:]:
print("Did not find {} and no download was requested.".format(WAFRELEASE))
sys.exit(1)
try:
from urllib.request import urlopen, URLError
except:
-26
View File
@@ -1,26 +0,0 @@
#!/usr/bin/env bash
set -e
FFMPEG_SYSROOT="${HOME}/deps/sysroot"
MPV_INSTALL_PREFIX="${HOME}/out/mpv"
MPV_VARIANT="${TRAVIS_OS_NAME}"
if [[ -d "./build/${MPV_VARIANT}" ]] ; then
rm -rf "./build/${MPV_VARIANT}"
fi
if [[ ! -e "./waf" ]] ; then
python3 ./bootstrap.py
fi
PKG_CONFIG_PATH="${FFMPEG_SYSROOT}/lib/pkgconfig/" CC="${CC}" CXX="${CXX}" python3 \
./waf configure \
--variant="${MPV_VARIANT}" \
--prefix="${MPV_INSTALL_PREFIX}" \
--enable-{gl,iconv,lcms2,libass,libass-osd,libmpv-shared,lua,jpeg,plain-gl,zlib} \
--enable-{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}"
+1
View File
@@ -24,6 +24,7 @@ python3 ./waf configure \
--enable-libarchive \
--enable-libass \
--enable-libbluray \
--enable-dvdread \
--enable-dvdnav \
--enable-uchardet \
--enable-vulkan \
+3 -1
View File
@@ -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
-38
View File
@@ -1,38 +0,0 @@
#!/usr/bin/env bash
set -e
FFMPEG_SRC_DIR="${HOME}/deps/src/ffmpeg"
FFMPEG_BUILD_DIR="${FFMPEG_SRC_DIR}/${TRAVIS_OS_NAME}"
FFMPEG_SYSROOT="${HOME}/deps/sysroot"
FFMPEG_HASH="18928e2bb4568cbe5e9061c3e6b63559392af3d2"
# Get the sauce if not around
if [[ ! -d "${FFMPEG_SRC_DIR}" ]] ; then
git clone "https://git.videolan.org/git/ffmpeg.git" "${FFMPEG_SRC_DIR}"
fi
# pop into FFmpeg's source dir and clean up & check out our wanted revision
pushd "${FFMPEG_SRC_DIR}"
git reset --hard HEAD && git clean -dfx
git checkout "${FFMPEG_HASH}"
popd
# If a build dir of the same type is around, clean it up
if [[ -d "${FFMPEG_BUILD_DIR}" ]] ; then
rm -rf "${FFMPEG_BUILD_DIR}"
fi
# Create and move into the build dir, configure and build!
mkdir -p "${FFMPEG_BUILD_DIR}" && pushd "${FFMPEG_BUILD_DIR}"
PKG_CONFIG_PATH="${FFMPEG_SYSROOT}/lib/pkgconfig/" ../configure \
--disable-{autodetect,stripping} \
--cc="${CC}" \
--cxx="${CXX}" \
--prefix="${FFMPEG_SYSROOT}" \
--enable-{zlib,securetransport,videotoolbox}
make -j4 && make install && popd
exit 0
-7
View File
@@ -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)
-2
View File
@@ -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
View File
@@ -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");
}
-10
View File
@@ -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
+2 -5
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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__)
+1 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}
-16
View File
@@ -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
View File
@@ -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
View File
@@ -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(&param, true);
int type = read_int_2(&param);
double time = read_time(&param);
if (cur_track) {
if (type == 1) {
+976 -2247
View File
File diff suppressed because it is too large. Load diff
+48 -44
View File
@@ -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
View File
@@ -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
View File
@@ -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("-", &params, demuxer->cancel, demuxer->global);
p->slave = demux_open(demuxer->stream, &params, 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
View File
@@ -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, &params, root->cancel,
root->global);
struct demuxer *d = demux_open_url(filename, &params, 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://", &params, 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
View File
@@ -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,
};
+2 -5
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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, &params, 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);
}
+3 -7
View File
@@ -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;
}
+65
View File
@@ -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
View File
@@ -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
View File
@@ -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, &params,
demuxer->cancel, demuxer->global);
if (!src->current->d && !demux_cancel_test(demuxer))
p->current->d = demux_open_url(p->current->url, &params,
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,
};
+265
View File
@@ -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
View File
@@ -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
View File
@@ -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