Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f1afc6ba2 | ||
|
|
35aa705c3e | ||
|
|
3739d1318f | ||
|
|
8432514c5f | ||
|
|
8bc335e3db | ||
|
|
6ac3d77e90 | ||
|
|
d0f1d382e6 | ||
|
|
1672204dc5 | ||
|
|
f89ff14195 | ||
|
|
cc3eb531eb | ||
|
|
f4d75376fe | ||
|
|
5085cf295f | ||
|
|
e285e22143 | ||
|
|
c94ede9e62 | ||
|
|
6d236bd96e | ||
|
|
96a45a16af | ||
|
|
061b752217 | ||
|
|
e0250b9604 | ||
|
|
5fbad204a6 | ||
|
|
46a556bd4b | ||
|
|
c7b72a6afe | ||
|
|
19551200d5 | ||
|
|
92f9747c2b | ||
|
|
aaad2d847e | ||
|
|
8362577f8c | ||
|
|
58c9f1a822 | ||
|
|
7e6b37be07 | ||
|
|
61202bb364 | ||
|
|
97680bf604 | ||
|
|
a46fc5e5eb | ||
|
|
50991fac81 | ||
|
|
1ec6cd89b9 | ||
|
|
95d4c2d7f6 | ||
|
|
fb9a32977d | ||
|
|
e13a62fc34 | ||
|
|
d11c03faee | ||
|
|
5f10a415d7 | ||
|
|
6da224b1dd | ||
|
|
f39a1cb1b0 | ||
|
|
8bbdecea83 | ||
|
|
ce23dfa2fa | ||
|
|
644e23a0f5 | ||
|
|
738de7d3fe | ||
|
|
f1c9032ddf | ||
|
|
bbf01346ff | ||
|
|
2493d91122 | ||
|
|
9831d3ada6 | ||
|
|
ee000f2133 | ||
|
|
ccf2b302cf | ||
|
|
9c12d54afa | ||
|
|
55d6408526 | ||
|
|
c178920505 | ||
|
|
07494aab16 | ||
|
|
9a464f53ef | ||
|
|
e017c24174 | ||
|
|
6dafc85187 | ||
|
|
dccb752b5f | ||
|
|
954625c165 | ||
|
|
bed94df521 | ||
|
|
9692814502 | ||
|
|
a6d29494ca | ||
|
|
2a7d2d1bc9 | ||
|
|
0aac7533dc | ||
|
|
443d3a91d3 | ||
|
|
d9ef1333c6 | ||
|
|
d41f25009d | ||
|
|
ea40fa36ee | ||
|
|
9c90c902c1 | ||
|
|
cfda696580 | ||
|
|
a9e0f4b279 | ||
|
|
c3205d294e | ||
|
|
70411f2709 | ||
|
|
4e53f9e5b2 | ||
|
|
abc6d130ac | ||
|
|
60452f640f | ||
|
|
753dbea83f | ||
|
|
9980b11058 | ||
|
|
bdbb3142ec | ||
|
|
d303ebd9b6 | ||
|
|
39adaf3dcc | ||
|
|
04376fa024 | ||
|
|
801fa486b0 | ||
|
|
ce803da90d | ||
|
|
194b26536b | ||
|
|
3b0e886193 | ||
|
|
63b34143a5 | ||
|
|
7be495b369 | ||
|
|
f762fc4230 | ||
|
|
d16ba1f7f6 | ||
|
|
c06fd19d8f | ||
|
|
b14fac9afa | ||
|
|
6be58df8d1 | ||
|
|
00eadcec8d | ||
|
|
3e89e061c2 | ||
|
|
5c942128d8 | ||
|
|
06c55ac6c3 | ||
|
|
310671e91a | ||
|
|
73858bb0cc | ||
|
|
085dfdea32 | ||
|
|
fd203ff16a | ||
|
|
d890e0731c | ||
|
|
a35a5bb5f3 | ||
|
|
4adfde5dd1 | ||
|
|
13add62cf0 | ||
|
|
717ee3ddba | ||
|
|
9490b628c5 | ||
|
|
fe02e5023c | ||
|
|
202f9b218a | ||
|
|
4fe45fb3a1 | ||
|
|
8cbb2b5e9a | ||
|
|
880bf54d7e | ||
|
|
06c8ec27f6 | ||
|
|
e277fadd60 | ||
|
|
c54c3b6991 | ||
|
|
04858c0b83 | ||
|
|
e7d902bdc0 | ||
|
|
c522d0dfbd | ||
|
|
da8011c5df | ||
|
|
398e2d5d42 | ||
|
|
a38283d5d9 | ||
|
|
9d68d8fb0f | ||
|
|
e94890a5d6 | ||
|
|
4b3b3a045a | ||
|
|
9d69eae162 | ||
|
|
ff9f2c4b6e | ||
|
|
cda31b71de | ||
|
|
a4cdd8bb82 | ||
|
|
9850932722 | ||
|
|
64041f963e | ||
|
|
fb6481ecb5 | ||
|
|
083e470091 | ||
|
|
1b1771f2a7 | ||
|
|
91fb7078e2 | ||
|
|
8a23892d04 | ||
|
|
3a85976f8e | ||
|
|
6b00663755 | ||
|
|
bbdecb792a | ||
|
|
348c610b68 | ||
|
|
397705b12c | ||
|
|
e91331e683 | ||
|
|
191cdbd31e | ||
|
|
ddaab3349b | ||
|
|
e48f1f31cb | ||
|
|
6c28824a92 | ||
|
|
812128bab7 | ||
|
|
d9376fc86f | ||
|
|
b26ab4d08c | ||
|
|
88dfb9a5e7 | ||
|
|
6de00d10c8 | ||
|
|
14010e7cf6 | ||
|
|
e4ae62da52 | ||
|
|
44e06b70d5 | ||
|
|
ac98ff71dd | ||
|
|
e552f5342e | ||
|
|
aecedd9250 | ||
|
|
5174add43a | ||
|
|
3d7b3996d2 | ||
|
|
e618b1b352 | ||
|
|
fb4ae3c06c | ||
|
|
06b30cc81f | ||
|
|
162c2e2d00 | ||
|
|
26d25d567f | ||
|
|
a51e991b56 | ||
|
|
fcb970492c | ||
|
|
32186de367 | ||
|
|
a41bedfce5 | ||
|
|
2afef344fb | ||
|
|
f4263922c7 | ||
|
|
9fd986b53a | ||
|
|
7dc74ee037 | ||
|
|
5f449fc11f | ||
|
|
fee5ceccad | ||
|
|
449eb208f4 | ||
|
|
46b74a38f1 | ||
|
|
a05c5b4ec6 | ||
|
|
cbd8abcbff | ||
|
|
f8baae5854 | ||
|
|
77320ad45e | ||
|
|
902424d065 | ||
|
|
c2c065913b | ||
|
|
ee66efeb99 | ||
|
|
c000b37e9a | ||
|
|
ed937b6eca | ||
|
|
4e25feda0d | ||
|
|
334cad57c6 | ||
|
|
0e8b9ed228 | ||
|
|
9c5cbbf5d7 | ||
|
|
854651f4f5 | ||
|
|
5ab11016c8 | ||
|
|
0ae753bc3d | ||
|
|
42a60143c2 | ||
|
|
5d7f881bdc | ||
|
|
afcf3e154a | ||
|
|
c4ad2732f9 | ||
|
|
e0f25010c7 | ||
|
|
c2fdedc2ec | ||
|
|
2ce5523893 | ||
|
|
d230194f62 | ||
|
|
f2a24ccfa5 | ||
|
|
68d737bd92 | ||
|
|
c17c26f404 | ||
|
|
d3eafc976d | ||
|
|
7aca328eb1 | ||
|
|
0b9f1e0323 | ||
|
|
737e3b1758 | ||
|
|
0e7dd6d4ff | ||
|
|
2ff0745ddb | ||
|
|
f34faca910 | ||
|
|
43386a7c92 | ||
|
|
8e41f314f1 | ||
|
|
0067d1dbef | ||
|
|
5ed4119057 | ||
|
|
b14fc38590 | ||
|
|
58a0c43cf4 | ||
|
|
7033a4334b | ||
|
|
d4e7b981bf | ||
|
|
b9cebf180b | ||
|
|
35716b53db | ||
|
|
22a22322cb | ||
|
|
a283b35556 | ||
|
|
321dd794c8 |
No files matched your search
+4
-6
@@ -101,18 +101,16 @@ mpv uses C99 with K&R formatting, with some exceptions.
|
||||
do_something();
|
||||
}
|
||||
```
|
||||
- If the body of an if statement uses braces, the else branch should also
|
||||
use braces (and reverse).
|
||||
- If the if has an else branch, both branches should use braces, even if they're
|
||||
technically redundant.
|
||||
|
||||
Example:
|
||||
|
||||
```C
|
||||
if (a) {
|
||||
// do something
|
||||
something();
|
||||
something_else();
|
||||
} else {
|
||||
one_line();
|
||||
} else {
|
||||
one_other_line();
|
||||
}
|
||||
```
|
||||
- If an if condition spans multiple physical lines, then put the opening brace
|
||||
|
||||
+40
-3
@@ -51,7 +51,8 @@ The rest of the lines belong to one of these classes:
|
||||
1) An empty or commented line. A comment starts with ``#``, which must be the
|
||||
first character in the line. The rest of the line (up until the next line
|
||||
break) is ignored. An empty line has 0 bytes between two line feed bytes.
|
||||
2) A segment entry in all other cases.
|
||||
2) A header entry if the line starts with ``!``.
|
||||
3) A segment entry in all other cases.
|
||||
|
||||
Each segment entry consists of a list of named or unnamed parameters.
|
||||
Parameters are separated with ``,``. Named parameters consist of a name,
|
||||
@@ -63,8 +64,8 @@ Syntax::
|
||||
segment_entry ::= <param> ( <param> ',' )*
|
||||
param ::= [ <name> '=' ] ( <value> | '%' <number> '%' <valuebytes> )
|
||||
|
||||
The ``name`` string can consist of any characters, except ``=%,;\n``. The
|
||||
``value`` string can consist of any characters except of ``,;\n``.
|
||||
The ``name`` string can consist of any characters, except ``=%,;\n!``. The
|
||||
``value`` string can consist of any characters except of ``,;\n!``.
|
||||
|
||||
The construct starting with ``%`` allows defining any value with arbitrary
|
||||
contents inline, where ``number`` is an integer giving the number of bytes in
|
||||
@@ -94,6 +95,42 @@ to ``20``, ``param3`` to ``value,escaped``, ``param4`` to ``value2``.
|
||||
Instead of line breaks, the character ``;`` can be used. Line feed bytes and
|
||||
``;`` are treated equally.
|
||||
|
||||
Header entries start with ``!`` as first character after a line break. Header
|
||||
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.
|
||||
|
||||
MP4 DASH
|
||||
========
|
||||
|
||||
This is a header that helps implementing DASH, although it only provides a low
|
||||
level mechanism.
|
||||
|
||||
If this header is set, the given url designates an mp4 init fragment. It's
|
||||
downloaded, and every URL in the EDL is prefixed with the init fragment on the
|
||||
byte stream level. This is mostly for use by mpv's internal ytdl support. The
|
||||
ytdl script will call youtube-dl, which in turn actually processes DASH
|
||||
manifests. It may work only for this very specific purpose and fail to be
|
||||
useful in other scenarios. It can be removed or changed in incompatible ways
|
||||
at any times.
|
||||
|
||||
Example::
|
||||
|
||||
!mp4_dash,init=url
|
||||
|
||||
The ``url`` is encoded as parameter value as defined in the general EDL syntax.
|
||||
It's expected to point to an "initialization fragment", which will be prefixed
|
||||
to every entry in the EDL on the byte stream level.
|
||||
|
||||
The current implementation will
|
||||
|
||||
- ignore stream start times
|
||||
- use durations as hint for seeking only
|
||||
- not adjust source timestamps
|
||||
- open and close segments (i.e. fragments) as needed
|
||||
- not add segment boundaries as chapter points
|
||||
- require full compatibility between all segments (same codec etc.)
|
||||
|
||||
Timestamp format
|
||||
================
|
||||
|
||||
|
||||
@@ -19,6 +19,24 @@ Interface changes
|
||||
|
||||
::
|
||||
|
||||
--- mpv 0.24.0 ---
|
||||
- deprecate --hwdec-api and replace it with --opengl-hwdec-interop.
|
||||
The new option accepts both --hwdec values, as well as named backends.
|
||||
A minor difference is that --hwdec-api=no (which used to be the default)
|
||||
now actually does not preload any interop layer, while the new default
|
||||
("") uses the value of --hwdec.
|
||||
- drop deprecated --ad/--vd features
|
||||
- drop deprecated --sub-codepage syntax
|
||||
- rename properties:
|
||||
- "drop-frame-count" to "decoder-frame-drop-count"
|
||||
- "vo-drop-frame-count" to "frame-drop-count"
|
||||
The old names still work, but are deprecated.
|
||||
- remove the --stream-capture option and property. No replacement.
|
||||
(--record-file might serve as alternative)
|
||||
- add --sub-justify
|
||||
- add --sub-ass-justify
|
||||
- internally there's a different way to enable the demuxer cache now
|
||||
it can be auto-enabled even if the stream cache remains disabled
|
||||
--- mpv 0.23.0 ---
|
||||
- remove deprecated vf_vdpaurb (use "--hwdec=vdpau-copy" instead)
|
||||
- the following properties now have new semantics:
|
||||
|
||||
+11
-8
@@ -940,15 +940,19 @@ Property list
|
||||
Total A-V sync correction done. Unavailable if audio or video is
|
||||
disabled.
|
||||
|
||||
``drop-frame-count``
|
||||
``decoder-frame-drop-count``
|
||||
Video frames dropped by decoder, because video is too far behind audio (when
|
||||
using ``--framedrop=decoder``). Sometimes, this may be incremented in other
|
||||
situations, e.g. when video packets are damaged, or the decoder doesn't
|
||||
follow the usual rules. Unavailable if video is disabled.
|
||||
|
||||
``vo-drop-frame-count``
|
||||
``drop-frame-count`` is a deprecated alias.
|
||||
|
||||
``frame-drop-count``
|
||||
Frames dropped by VO (when using ``--framedrop=vo``).
|
||||
|
||||
``vo-drop-frame-count`` is a deprecated alias.
|
||||
|
||||
``mistimed-frame-count``
|
||||
Number of video frames that were not timed correctly in display-sync mode
|
||||
for the sake of keeping A/V sync. This does not include external
|
||||
@@ -1331,8 +1335,8 @@ Property list
|
||||
This is known only once the VO has opened (and possibly later). With some
|
||||
VOs (like ``opengl``), this might be never known in advance, but only when
|
||||
the decoder attempted to create the hw decoder successfully. (Using
|
||||
``--hwdec-preload`` can load it eagerly.) If there are multiple drivers
|
||||
loaded, they will be separated by ``,``.
|
||||
``--opengl-hwdec-interop`` can load it eagerly.) If there are multiple
|
||||
drivers loaded, they will be separated by ``,``.
|
||||
|
||||
If no VO is active or no interop driver is known, this property is
|
||||
unavailable.
|
||||
@@ -1341,6 +1345,9 @@ Property list
|
||||
multiple interop drivers for the same hardware decoder, depending on
|
||||
platform and VO.
|
||||
|
||||
This is somewhat similar to the ``--opengl-hwdec-interop`` option, but
|
||||
it returns the actually loaded backend, not the value of this option.
|
||||
|
||||
``video-format``
|
||||
Video format as string.
|
||||
|
||||
@@ -1545,10 +1552,6 @@ Property list
|
||||
|
||||
This property is experimental and might be removed in the future.
|
||||
|
||||
``stream-capture`` (RW)
|
||||
A filename, see ``--stream-capture``. Setting this will start capture using
|
||||
the given filename. Setting it to an empty string will stop it.
|
||||
|
||||
``tv-brightness``, ``tv-contrast``, ``tv-saturation``, ``tv-hue`` (RW)
|
||||
TV stuff.
|
||||
|
||||
|
||||
@@ -14,3 +14,62 @@ mpv, further documentation is spread over a few places:
|
||||
- http://mpv.io/manual/master/#list-of-input-commands
|
||||
- http://mpv.io/manual/master/#properties
|
||||
- https://github.com/mpv-player/mpv-examples/tree/master/libmpv
|
||||
|
||||
C PLUGINS
|
||||
=========
|
||||
|
||||
You can write C plugins for mpv. These use the libmpv API, although they do not
|
||||
use the libmpv library itself.
|
||||
|
||||
Currently, they must be explicitly enabled at build time with
|
||||
``--enable-cplugins``. They are available on Linux/BSD platforms only.
|
||||
|
||||
C plugins location
|
||||
------------------
|
||||
|
||||
C plugins are put into the mpv scripts directory in its config directory
|
||||
(see the `FILES`_ section for details). They must have a ``.so`` file extension.
|
||||
They can also be explicitly loaded with the ``--script`` option.
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
A C plugin must export the following function::
|
||||
|
||||
int mpv_open_cplugin(mpv_handle *handle)
|
||||
|
||||
The plugin function will be called on loading time. This function does not
|
||||
return as long as your plugin is loaded (it runs in its own thread). The
|
||||
``handle`` will be deallocated as soon as the plugin function returns.
|
||||
|
||||
The return value is interpreted as error status. A value of ``0`` is
|
||||
interpreted as success, while ``-1`` signals an error. In the latter case,
|
||||
the player prints an uninformative error message that loading failed.
|
||||
|
||||
Return values other than ``0`` and ``-1`` are reserved, and trigger undefined
|
||||
behavior.
|
||||
|
||||
Within the plugin function, you can call libmpv API functions. The ``handle``
|
||||
is created by ``mpv_create_client()`` (or actually an internal equivalent),
|
||||
and belongs to you. You can call ``mpv_wait_event()`` to wait for things
|
||||
happening, and so on.
|
||||
|
||||
Note that the player might block until your plugin calls ``mpv_wait_event()``
|
||||
for the first time. This gives you a chance to install initial hooks etc.
|
||||
before playback begins.
|
||||
|
||||
The details are quite similar to Lua scripts.
|
||||
|
||||
Linkage to libmpv
|
||||
-----------------
|
||||
|
||||
The current implementation requires that your plugins are **not** linked against
|
||||
libmpv. What your plugins uses are not symbols from a libmpv binary, but
|
||||
symbols from the mpv host binary.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
See:
|
||||
|
||||
- https://github.com/mpv-player/mpv-examples/tree/master/cplugins
|
||||
@@ -451,6 +451,11 @@ are useful only in special situations.
|
||||
multiple properties at once, you might not want to act on each property
|
||||
change, but only when all change notifications have been received.
|
||||
|
||||
``mp.unregister_idle(fn)``
|
||||
Undo ``mp.register_idle(fn)``. This removes all idle handlers that
|
||||
are equal to the ``fn`` parameter. This uses normal Lua ``==`` comparison,
|
||||
so be careful when dealing with closures.
|
||||
|
||||
``mp.enable_messages(level)``
|
||||
Set the minimum log level of which mpv message output to receive. These
|
||||
messages are normally printed to the terminal. By calling this function,
|
||||
|
||||
+5
-1
@@ -140,7 +140,7 @@ L
|
||||
Toggle infinite looping.
|
||||
|
||||
Ctrl + and Ctrl -
|
||||
Adjust audio delay by +/- 0.1 seconds.
|
||||
Adjust audio delay (A/V sync) by +/- 0.1 seconds.
|
||||
|
||||
u
|
||||
Switch between applying no style overrides to SSA/ASS subtitles, and
|
||||
@@ -673,6 +673,10 @@ PROTOCOLS
|
||||
either aliases to documented protocols, or are just redirections to
|
||||
protocols implemented and documented in FFmpeg.
|
||||
|
||||
``data:`` is supported in FFmpeg (not in Libav), but needs to be in the
|
||||
format ``data://``. This is done to avoid ambiguity with filenames. You
|
||||
can also prefix it with ``lavf://`` or ``ffmpeg://``.
|
||||
|
||||
``ytdl://...``
|
||||
By default, the youtube-dl hook script (enabled by default for mpv CLI)
|
||||
only looks at http URLs. Prefixing an URL with ``ytdl://`` forces it to
|
||||
|
||||
+166
-53
@@ -640,8 +640,8 @@ Video
|
||||
``--opengl-backend=dxinterop`` (Windows only)
|
||||
:dxva2-copy: copies video back to system RAM (Windows only)
|
||||
:d3d11va: requires ``--vo=opengl`` with ``--opengl-backend=angle``
|
||||
(Windows only)
|
||||
:d3d11va-copy: copies video back to system RAM (Windows only)
|
||||
(Windows 8+ only)
|
||||
:d3d11va-copy: copies video back to system RAM (Windows 8+ only)
|
||||
:mediacodec: copies video back to system RAM (Android only)
|
||||
:rpi: requires ``--vo=opengl`` (Raspberry Pi only - default if available)
|
||||
:rpi-copy: copies video back to system RAM (Raspberry Pi only)
|
||||
@@ -669,6 +669,14 @@ Video
|
||||
forcing it with ``--opengl-backend=x11``, but the vaapi/GLX interop is
|
||||
said to be slower than ``vaapi-copy``.
|
||||
|
||||
The ``cuda`` and ``cuda-copy`` modes provides deinterlacing in the decoder
|
||||
which is useful as there is no other deinterlacing mechanism in the opengl
|
||||
output path. To use this deinterlacing you must pass the option:
|
||||
``vd-lavc-o=deint=[weave|bob|adaptive]``.
|
||||
Pass ``weave`` (or leave the option unset) to not attempt any
|
||||
deinterlacing. ``cuda`` should always be preferred unless the ``opengl``
|
||||
vo is not being used or filters are required.
|
||||
|
||||
Most video filters will not work with hardware decoding as they are
|
||||
primarily implemented on the CPU. Some exceptions are ``vdpaupp``,
|
||||
``vdpaurb`` and ``vavpp``. See `VIDEO FILTERS`_ for more details.
|
||||
@@ -719,18 +727,6 @@ Video
|
||||
affect this additionally. This can give incorrect results even with
|
||||
completely ordinary video sources.
|
||||
|
||||
``cuda`` is usually safe. Interlaced content can be deinterlaced by
|
||||
the decoder, which is useful as there is no other deinterlacing
|
||||
mechanism in the opengl output path. To use this deinterlacing you
|
||||
must pass the option: ``vd-lavc-o=deint=[weave|bob|adaptive]``. Pass
|
||||
``weave`` to not attempt any deinterlacing.
|
||||
10 and 12bit HEVC is available if the hardware supports it and a
|
||||
sufficiently new driver (> 375.xx) is used.
|
||||
|
||||
``cuda-copy`` has the same behaviour as ``cuda`` - including the ability
|
||||
to deinterlace inside the decoder. However, traditional deinterlacing
|
||||
filters can be used in this case.
|
||||
|
||||
``rpi`` always uses the hardware overlay renderer, even with
|
||||
``--vo=opengl``.
|
||||
|
||||
@@ -746,12 +742,12 @@ Video
|
||||
In particular, ``auto-copy`` will only select safe modes
|
||||
(although potentially slower than other methods).
|
||||
|
||||
``--hwdec-preload=<api>``
|
||||
``--opengl-hwdec-interop=<name>``
|
||||
This is useful for the ``opengl`` and ``opengl-cb`` VOs for creating the
|
||||
hardware decoding OpenGL interop context, but without actually enabling
|
||||
hardware decoding itself (like ``--hwdec`` does).
|
||||
|
||||
If set to ``no`` (default), the ``--hwdec`` option is used.
|
||||
If set to an empty string (default), the ``--hwdec`` option is used.
|
||||
|
||||
For ``opengl``, if set, do not create the interop context on demand, but
|
||||
when the VO is created.
|
||||
@@ -762,6 +758,19 @@ Video
|
||||
to temporarily set the ``hwdec`` option just during OpenGL context
|
||||
initialization with ``mpv_opengl_cb_init_gl()``.
|
||||
|
||||
See ``--opengl-hwdec-interop=help`` for accepted values. This lists the
|
||||
interop backend, with the ``--hwdec`` alias after it in ``[...]``. Consider
|
||||
all values except the proper interop backend name, ``auto``, and ``no`` as
|
||||
silently deprecated and subject to change. Also, if you use this in
|
||||
application code (e.g. via libmpv), any value other than ``auto`` and ``no``
|
||||
should be avoided, as backends can change.
|
||||
|
||||
Currently the option sets a single value. It is possible that the option
|
||||
type changes to a list in the future.
|
||||
|
||||
The old alias ``--hwdec-preload`` has different behavior if the option value
|
||||
is ``no``.
|
||||
|
||||
``--videotoolbox-format=<name>``
|
||||
Set the internal pixel format used by ``--hwdec=videotoolbox`` on OSX. The
|
||||
choice of the format can influence performance considerably. On the other
|
||||
@@ -1132,41 +1141,31 @@ Audio
|
||||
multichannel PCM, and mpv supports lossless DTS-HD decoding via
|
||||
FFmpeg's new DCA decoder (based on libdcadec).
|
||||
|
||||
``--ad=<[+|-]family1:(*|decoder1),[+|-]family2:(*|decoder2),...[-]>``
|
||||
``--ad=<decoder1,decoder2,...[-]>``
|
||||
Specify a priority list of audio decoders to be used, according to their
|
||||
decoder name. When determining which decoder to use, the first decoder that
|
||||
matches the audio format is selected. If that is unavailable, the next
|
||||
decoder is used. Finally, it tries all other decoders that are not
|
||||
explicitly selected or rejected by the option.
|
||||
|
||||
Specifying family names is deprecated. Entries like ``family:*`` prioritize
|
||||
all decoders of the given family.
|
||||
|
||||
``-`` at the end of the list suppresses fallback on other available
|
||||
decoders not on the ``--ad`` list. ``+`` in front of an entry forces the
|
||||
decoder. Both of these should not normally be used, because they break
|
||||
normal decoder auto-selection! Both of these methods are deprecated.
|
||||
|
||||
``-`` in front of an entry disables selection of the decoder. This is
|
||||
deprecated.
|
||||
|
||||
.. admonition:: Examples
|
||||
|
||||
``--ad=mp3float``
|
||||
Prefer the FFmpeg/Libav ``mp3float`` decoder over all other MP3
|
||||
decoders.
|
||||
|
||||
``--ad=lavc:mp3float``
|
||||
Prefer the FFmpeg/Libav ``mp3float`` decoder over all other MP3
|
||||
decoders. (Using deprecated family syntax.)
|
||||
|
||||
``--ad=help``
|
||||
List all available decoders.
|
||||
|
||||
.. admonition:: Warning
|
||||
|
||||
Enabling compressed audio passthrough (AC3 and DTS via SPDIF/HDMI) with
|
||||
this option is deprecated. Use ``--audio-spdif`` instead.
|
||||
this option is not possible. Use ``--audio-spdif`` instead.
|
||||
|
||||
``--volume=<value>``
|
||||
Set the startup volume. 0 means silence, 100 means no volume reduction or
|
||||
@@ -1727,6 +1726,13 @@ Subtitles
|
||||
|
||||
Disabled by default.
|
||||
|
||||
``--image-subs-video-resolution=<yes|no>``
|
||||
Override the image subtitle resolution with the video resolution
|
||||
(default: no). Normally, the subtitle canvas is fit into the video canvas
|
||||
(e.g. letterboxed). Setting this option uses the video size as subtitle
|
||||
canvas size. Can be useful to test broken subtitles, which often happen
|
||||
when the video was trancoded, while attempting to keep the old subtitles.
|
||||
|
||||
``--sub-ass``, ``--no-sub-ass``
|
||||
Render ASS subtitles natively (enabled by default).
|
||||
|
||||
@@ -1781,8 +1787,9 @@ Subtitles
|
||||
subtitles are interpreted as UTF-8 with "Latin 1" as fallback for bytes
|
||||
which are not valid UTF-8 sequences. iconv is never involved in this mode.
|
||||
|
||||
This option changed in mpv 0.23.0. The old syntax is still emulated to some
|
||||
degree.
|
||||
This option changed in mpv 0.23.0. Support for the old syntax was fully
|
||||
removed in mpv 0.24.0.
|
||||
|
||||
|
||||
``--sub-fix-timing``, ``--no-sub-fix-timing``
|
||||
By default, subtitle timing is adjusted to remove minor gaps or overlaps
|
||||
@@ -1965,6 +1972,18 @@ Subtitles
|
||||
Vertical position (default: ``bottom``).
|
||||
Details see ``--sub-align-x``.
|
||||
|
||||
``--sub-justify=<auto|left|center|right>``
|
||||
Control how multi line subs are justified irrespective of where they
|
||||
are aligned (default: ``auto`` which justifies as defined by
|
||||
``--sub-align-y``).
|
||||
Left justification is recommended to make the subs easier to read
|
||||
as it is easier for the eyes.
|
||||
|
||||
``--sub-ass-justify=<yes|no>``
|
||||
Applies justification as defined by ``--sub-justify`` on ASS subtitles
|
||||
if ``--sub-ass-style-override`` is not set to ``no``.
|
||||
Default: ``no``.
|
||||
|
||||
``--sub-shadow-color=<color>``
|
||||
See ``--sub-color``. Color used for sub text shadow.
|
||||
|
||||
@@ -2101,6 +2120,9 @@ Window
|
||||
|
||||
Enabled by default.
|
||||
|
||||
``--snap-window``
|
||||
(Windows only) Snap the player window to screen edges.
|
||||
|
||||
``--ontop``
|
||||
Makes the player window stay on top of other windows.
|
||||
|
||||
@@ -2339,7 +2361,7 @@ Window
|
||||
- ``--monitoraspect=16:9`` or ``--monitoraspect=1.7777``
|
||||
|
||||
``--hidpi-window-scale``, ``--no-hidpi-window-scale``
|
||||
(OS X only)
|
||||
(OS X and X11 only)
|
||||
Scale the window size according to the backing scale factor (default: yes).
|
||||
On regular HiDPI resolutions the window opens with double the size but appears
|
||||
as having the same size as on none-HiDPI resolutions. This is the default OS X
|
||||
@@ -2724,6 +2746,27 @@ Demuxer
|
||||
(This value tends to be fuzzy, because many file formats don't store linear
|
||||
timestamps.)
|
||||
|
||||
``--prefetch-playlist=<yes|no>``
|
||||
Prefetch next playlist entry while playback of the current entry is ending
|
||||
(default: no). This merely opens the URL of the next playlist entry as soon
|
||||
as the current URL is fully read.
|
||||
|
||||
This does **not** work with URLs resolved by the ``youtube-dl`` wrapper,
|
||||
and it won't.
|
||||
|
||||
This does not affect HLS (``.m3u8`` URLs) - HLS prefetching depends on the
|
||||
demuxer cache settings and is on by default.
|
||||
|
||||
This can give subtly wrong results if per-file options are used, or if
|
||||
options are changed in the time window between prefetching start and next
|
||||
file played.
|
||||
|
||||
This can occasionally make wrong prefetching decisions. For example, it
|
||||
can't predict whether you go backwards in the playlist, and assumes you
|
||||
won't edit the playlist.
|
||||
|
||||
Highly experimental.
|
||||
|
||||
``--force-seekable=<yes|no>``
|
||||
If the player thinks that the media is not seekable (e.g. playing from a
|
||||
pipe, or it's an http stream with a server that doesn't support range
|
||||
@@ -4288,16 +4331,70 @@ The following video options are currently all specific to ``--vo=opengl`` and
|
||||
|
||||
Windows only.
|
||||
|
||||
``--opengl-dcomposition=<yes|no>``
|
||||
Allows DirectComposition when using the ANGLE backend (default: yes).
|
||||
DirectComposition implies flip-model presentation, which can improve
|
||||
rendering efficiency on Windows 8+ by avoiding a copy of the video frame.
|
||||
mpv uses it by default where possible, but it can cause poor behaviour with
|
||||
some drivers, such as a black screen or graphical corruption when leaving
|
||||
full-screen mode. Use "no" to disable it.
|
||||
``--angle-d3d11-feature-level=<11_0|10_1|10_0|9_3>``
|
||||
Selects a specific feature level when using the ANGLE backend with D3D11.
|
||||
By default, the highest available feature level is used. This option can be
|
||||
used to select a lower feature level, which is mainly useful for debugging.
|
||||
Note that OpenGL ES 3.0 is only supported at feature level 10_1 or higher.
|
||||
Most extended OpenGL features will not work at lower feature levels
|
||||
(similar to ``--opengl-dumb-mode``).
|
||||
|
||||
Windows with ANGLE only.
|
||||
|
||||
``--angle-d3d11-warp=<yes|no|auto>``
|
||||
Use WARP (Windows Advanced Rasterization Platform) when using the ANGLE
|
||||
backend with D3D11 (default: auto). This is a high performance software
|
||||
renderer. By default, it is used when the Direct3D hardware does not
|
||||
support Direct3D 11 feature level 9_3. While the extended OpenGL features
|
||||
will work with WARP, they can be very slow.
|
||||
|
||||
Windows with ANGLE only.
|
||||
|
||||
``--angle-egl-windowing=<yes|no|auto>``
|
||||
Use ANGLE's built in EGL windowing functions to create a swap chain
|
||||
(default: auto). If this is set to ``no`` and the D3D11 renderer is in use,
|
||||
ANGLE's built in swap chain will not be used and a custom swap chain that
|
||||
is optimized for video rendering will be created instead. If set to
|
||||
``auto``, a custom swap chain will be used for D3D11 and the built in swap
|
||||
chain will be used for D3D9. This option is mainly for debugging purposes,
|
||||
in case the custom swap chain has poor performance or does not work.
|
||||
|
||||
If set to ``yes``, the ``--angle-max-frame-latency`` and
|
||||
``--angle-swapchain-length`` options will have no effect.
|
||||
|
||||
Windows with ANGLE only.
|
||||
|
||||
``--angle-max-frame-latency=<1-16>``
|
||||
Sets the maximum number of frames that the system is allowed to queue for
|
||||
rendering with the ANGLE backend (default: 3). Lower values should make
|
||||
VSync timing more accurate, but a value of ``1`` requires powerful
|
||||
hardware, since the CPU will not be able to "render ahead" of the GPU.
|
||||
|
||||
Windows with ANGLE only.
|
||||
|
||||
``--angle-renderer=<d3d9|d3d11|auto>``
|
||||
Forces a specific renderer when using the ANGLE backend (default: auto). In
|
||||
auto mode this will pick D3D11 for systems that support Direct3D 11 feature
|
||||
level 9_3 or higher, and D3D9 otherwise. This option is mainly for
|
||||
debugging purposes. Normally there is no reason to force a specific
|
||||
renderer, though ``--angle-renderer=d3d9`` may give slightly better
|
||||
performance on old hardware. Note that the D3D9 renderer only supports
|
||||
OpenGL ES 2.0, so most extended OpenGL features will not work if this
|
||||
renderer is selected (similar to ``--opengl-dumb-mode``).
|
||||
|
||||
Windows with ANGLE only.
|
||||
|
||||
``--angle-swapchain-length=<2-16>``
|
||||
Sets the number of buffers in the D3D11 presentation queue when using the
|
||||
ANGLE backend (default: 6). At least 2 are required, since one is the back
|
||||
buffer that mpv renders to and the other is the front buffer that is
|
||||
presented by the DWM. Additional buffers can improve performance, because
|
||||
for example, mpv will not have to wait on the DWM to release the front
|
||||
buffer before rendering a new frame to it. For this reason, Microsoft
|
||||
recommends at least 4.
|
||||
|
||||
Windows 8+ with ANGLE only.
|
||||
|
||||
``--opengl-sw``
|
||||
Continue even if a software renderer is detected.
|
||||
|
||||
@@ -4322,6 +4419,9 @@ The following video options are currently all specific to ``--vo=opengl`` and
|
||||
work.
|
||||
x11
|
||||
X11/GLX
|
||||
x11probe
|
||||
For internal autoprobing, equivalent to ``x11`` otherwise. Don't use
|
||||
directly, it could be removed without warning as autoprobing is changed.
|
||||
wayland
|
||||
Wayland/EGL
|
||||
drm
|
||||
@@ -4561,6 +4661,7 @@ The following video options are currently all specific to ``--vo=opengl`` and
|
||||
unavailable, it silently falls back on a normal framebuffer. Note that
|
||||
if you set the ``--opengl-fbo-format`` option to a non-default value, a
|
||||
format with alpha must be specified, or this won't work.
|
||||
This does not work on X11 with EGL and Mesa (freedesktop bug 67676).
|
||||
no
|
||||
Ignore alpha component.
|
||||
|
||||
@@ -4716,21 +4817,10 @@ Miscellaneous
|
||||
Input file type for ``mf://`` (available: jpeg, png, tga, sgi). By default,
|
||||
this is guessed from the file extension.
|
||||
|
||||
``--stream-capture=<filename>``
|
||||
Allows capturing the primary stream (not additional audio tracks or other
|
||||
kind of streams) into the given file. Capturing can also be started and
|
||||
stopped by changing the filename with the ``stream-capture`` property.
|
||||
Generally this will not produce usable results for anything else than MPEG
|
||||
or raw streams, unless capturing includes the file headers and is not
|
||||
interrupted. Note that, due to cache latencies, captured data may begin and
|
||||
end somewhat delayed compared to what you see displayed.
|
||||
|
||||
The destination file is always appended. (Before mpv 0.8.0, the file was
|
||||
overwritten.)
|
||||
|
||||
``--stream-dump=<filename>``
|
||||
Same as ``--stream-capture``, but do not start playback. Instead, the entire
|
||||
file is dumped.
|
||||
``--stream-dump=<destination-filename>``
|
||||
Instead of playing a file, read its byte stream and write it to the given
|
||||
destination file. The destination is overwritten. Can be useful to test
|
||||
network-related behavior.
|
||||
|
||||
``--stream-lavf-o=opt1=value1,opt2=value2,...``
|
||||
Set AVOptions on streams opened with libavformat. Unknown or misspelled
|
||||
@@ -4773,6 +4863,29 @@ Miscellaneous
|
||||
This does not affect playlist expansion, redirection, or other loading of
|
||||
referenced files like with ordered chapters.
|
||||
|
||||
``--record-file=<file>``
|
||||
Record the current stream to the given target file. The target file will
|
||||
always be overwritten without asking.
|
||||
|
||||
This remuxes the source stream without reencoding, which makes this a
|
||||
highly fragile and experimental feature. It's entirely possible that this
|
||||
writes files which are broken, not standards compliant, not playable with
|
||||
all players (including mpv), or incomplete.
|
||||
|
||||
The target file format is determined by the file extension of the target
|
||||
filename. It is recommended to use the same target container as the source
|
||||
container if possible, and preferring Matroska as fallback.
|
||||
|
||||
Seeking during stream recording, or enabling/disabling stream recording
|
||||
during playback, can cut off data, or produce "holes" in the output file.
|
||||
These are technical restrictions. In particular, video data or subtitles
|
||||
which were read ahead can produce such holes, which might cause playback
|
||||
problems with various players (including mpv).
|
||||
|
||||
The behavior of this option might changed in the future, such as changing
|
||||
it to a template (similar to ``--screenshot-template``), being renamed,
|
||||
removed, or anything else, until it is declared semi-stable.
|
||||
|
||||
``--lavfi-complex=<string>``
|
||||
Set a "complex" libavfilter filter, which means a single filter graph can
|
||||
take input from multiple source audio and video tracks. The graph can result
|
||||
|
||||
@@ -8,7 +8,10 @@ behaves. Although there are still many similarities to its ancestors, **mpv**
|
||||
should generally be treated as a completely different program.
|
||||
|
||||
.. admonition:: Warning
|
||||
This document is not updated anymore, and is incomplete and outdated.
|
||||
|
||||
This document is **not updated** anymore, and is **incomplete** and
|
||||
**outdated**. If you look for old option replacements, always check with
|
||||
the current mpv manpage, as the options could have changed meanwhile.
|
||||
|
||||
General Changes from MPlayer to mpv
|
||||
-----------------------------------
|
||||
|
||||
+121
@@ -1,3 +1,124 @@
|
||||
Release 0.24.0
|
||||
==============
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- Windows: allow snapping to screen edges (#2248)
|
||||
- macOS: add border cycling (#2430)
|
||||
- player: add experimental C plugin interface
|
||||
- player: add experimental stream recording feature (--record-file)
|
||||
- player: add prefetching of the next playlist entry (--prefetch-playlist; does NOT work with URLs resolved by youtube-dl)
|
||||
- stream_lavf: add support for data URIs (#4058)
|
||||
- vaapi: add support for 10-bit video formats (requires patched Mesa and capable hardware)
|
||||
- x11: pseudo HiDPI scaling
|
||||
|
||||
|
||||
Options and Commands
|
||||
--------------------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- sub: add --sub-justify and --sub-ass-justify options
|
||||
- sub: add option to force using video resolution for image subtitles (--image-subs-video-resolution)
|
||||
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- command: rename framedrop properties
|
||||
- drop-frame-count → decoder-frame-drop-count
|
||||
- vo-drop-frame-count → frame-drop-count
|
||||
- input.conf: change "L" to change loop-file by default
|
||||
|
||||
|
||||
Deprecated
|
||||
~~~~~~~~~~
|
||||
|
||||
- --hwdec-preload (replaced with --opengl-hwdec-interop)
|
||||
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- options: drop deprecated --sub-codepage syntax
|
||||
- options: drop deprecated --vd/--ad codecs selection features
|
||||
- player: remove --stream-capture option/property
|
||||
|
||||
|
||||
Fixes and Minor Enhancements
|
||||
----------------------------
|
||||
|
||||
- Windows: update the fullscreen state on restoring
|
||||
- ad_spdif: fix obscure cases of AC3 passthrough (e.g. 44100 Hz AC3)
|
||||
- ao_oss: use --audio-device if --oss-device isn't set (#4122)
|
||||
- build: fix --disable-gl if cuda is enabled
|
||||
- build: rpi: rely on pkgconfig for compiler flags to ease cross compilation
|
||||
- charset_conv: fallback to interpreting subs as latin1 if iconv fails
|
||||
- charset_conv: fix "auto" fallback with uchardet not compiled (#3954)
|
||||
- client API: fix freeze when destroying mpv_handle before mpv_initialize
|
||||
- client API: fix mpv_set_property() return value before init (#3988)
|
||||
- command: fix potential crash for script-binding with multi-commands
|
||||
- command: nicer OSD formatting for loop-file
|
||||
- command: shorten long playlists on OSD
|
||||
- config: do not resolve default profile during "include" processing (#4024)
|
||||
- cuda: fix 10 bit decoding
|
||||
- cue: accept lower-case cue commands (#4057)
|
||||
- demux_mkv: any reference makes a frame not a keyframe (fixes seeking in WebM files with alpha channel)
|
||||
- lavfi: slightly better disconnected output handling (#4118)
|
||||
- lua: close directory after reading its entries (#4045)
|
||||
- macOS: consistent normalization when searching for external files (#4016)
|
||||
- macOS: don't change Space on quit in fullscreen (#3957)
|
||||
- macOS: don't constrain window frame for fullscreen (#4044)
|
||||
- macOS: don't init displaylink on reconfig (#4031)
|
||||
- macOS: fix build on OS X 10.9 (#3946)
|
||||
- macOS: fix color profile retrieval
|
||||
- macOS: fix displaylink refresh rate retrieval
|
||||
- macOS: fix dropping of files and URLs (#4036)
|
||||
- macOS: fix handling of geometry option (#3867)
|
||||
- macOS: fix unwanted behavior with window level other than the default (#1757, #1884)
|
||||
- macOS: fix window size in certain circumstances
|
||||
- macOS: properly recover from toggleFullscreen fail (#4035)
|
||||
- osc: allow playlist buttons when looping (#4092)
|
||||
- osc: bottom/topbar: clip title instead of stretching
|
||||
- osc: bottombar/topbar: make chapter markers slightly bigger
|
||||
- osc: box: clip with ellipsis after too much stretching
|
||||
- osc: fix crashes related to field eventresponder being nil (#3210)
|
||||
- player: add .scc subtitle extension
|
||||
- stream_bluray: use proper 0-based index
|
||||
- sub: remove .txt as text subtitle extension
|
||||
- tv: fix segfaults on TV input (#4096)
|
||||
- vaapi: set libva > 0.39.4 message callbacks to prevent it from printing messages to the terminal
|
||||
- vd_lavc: improved fallback behavior for --hwdec=cuda (#3914)
|
||||
- vdpau: reject decoding of non-4:2:0 (would decode to garbage)
|
||||
- vf_lavfi: don't crash with VOs without hardware decoding support (#4064)
|
||||
- vo_opengl, vo_opengl_cb: better hwdec interop backend selection
|
||||
- vo_opengl: angle: rewrite with custom swap chain (fixes problems with e.g. 24 fps video on 24 Hz monitors, avoids problems with DirectComposition, enables "screenshot window" command on Windows 8 and newer)
|
||||
- vo_opengl: partially fix rotation for 4:2:2 content
|
||||
- vo_opengl: x11egl: fix alpha mode
|
||||
- wayland: fix high CPU usage with mpv paused and visible
|
||||
- ytdl_hook, edl: implement pseudo-DASH support
|
||||
- ytdl_hook: add non-dash fallbacks to default formats
|
||||
- ytdl_hook: fix opening hitbox.tv rtmp stream
|
||||
- ytdl_hook: respect --no-audio, don't force-select track
|
||||
- ytdl_hook: support livestream segmented DASH VODs
|
||||
- ytdl_hook: support segmented dash
|
||||
|
||||
|
||||
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.23.0..v0.24.0`
|
||||
in the git repository or by visiting either
|
||||
https://github.com/mpv-player/mpv/compare/v0.23.0...v0.24.0 or
|
||||
https://git.srsfckn.biz/mpv/log/?qt=range&q=v0.23.0..v0.24.0
|
||||
|
||||
|
||||
Release 0.23.0
|
||||
==============
|
||||
|
||||
|
||||
Whitespace-only changes.
@@ -5,23 +5,27 @@
|
||||
# of every string, so code using the string may need to remove that to get
|
||||
# the exact contents of the original file.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import sys
|
||||
|
||||
# Indexing a byte string yields int on Python 3.x, and a str on Python 2.x
|
||||
def pord(c):
|
||||
return ord(c) if type(c) == str else c
|
||||
|
||||
def main(infile):
|
||||
def file2string(infilename, infile, outfile):
|
||||
outfile.write("// Generated from %s\n\n" % infilename)
|
||||
|
||||
conv = ['\\' + ("%03o" % c) for c in range(256)]
|
||||
safe_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" \
|
||||
"0123456789!#%&'()*+,-./:;<=>?[]^_{|}~ "
|
||||
|
||||
for c in safe_chars:
|
||||
conv[ord(c)] = c
|
||||
for c, esc in ("\nn", "\tt", r"\\", '""'):
|
||||
conv[ord(c)] = '\\' + esc
|
||||
for line in infile:
|
||||
sys.stdout.write('"' + ''.join(conv[pord(c)] for c in line) + '"\n')
|
||||
outfile.write('"' + ''.join(conv[pord(c)] for c in line) + '"\n')
|
||||
|
||||
with open(sys.argv[1], 'rb') as infile:
|
||||
sys.stdout.write("// Generated from %s\n\n" % sys.argv[1])
|
||||
main(infile)
|
||||
if __name__ == "__main__":
|
||||
with open(sys.argv[1], 'rb') as infile:
|
||||
file2string(sys.argv[1], infile, sys.stdout)
|
||||
+13
-8
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generate C definitions for parsing Matroska files.
|
||||
Can also be used to directly parse Matroska files and display their contents.
|
||||
@@ -69,6 +69,12 @@ elements_matroska = (
|
||||
'BlockDuration, 9b, uint',
|
||||
'ReferenceBlock*, fb, sint',
|
||||
'DiscardPadding, 75A2, sint',
|
||||
'BlockAdditions, 75A1, sub', (
|
||||
'BlockMore*, A6, sub', (
|
||||
'BlockAddID, EE, uint',
|
||||
'BlockAdditional, A5, binary',
|
||||
),
|
||||
),
|
||||
),
|
||||
'SimpleBlock*, a3, binary',
|
||||
),
|
||||
@@ -271,6 +277,7 @@ def parse_elems(l, namespace):
|
||||
for el in l:
|
||||
if isinstance(el, str):
|
||||
name, hexid, eltype = [x.strip() for x in el.split(',')]
|
||||
hexid = hexid.lower()
|
||||
multiple = name.endswith('*')
|
||||
name = name.strip('*')
|
||||
new = MatroskaElement(name, hexid, eltype, namespace)
|
||||
@@ -285,8 +292,8 @@ parse_elems(elements_ebml, 'EBML')
|
||||
parse_elems(elements_matroska, 'MATROSKA')
|
||||
|
||||
def printf(out, *args):
|
||||
out.write(' '.join([str(x) for x in args]))
|
||||
out.write('\n')
|
||||
out.write(u' '.join([str(x) for x in args]))
|
||||
out.write(u'\n')
|
||||
|
||||
def generate_C_header(out):
|
||||
printf(out, '// Generated by TOOLS/matroska.py, do not edit manually')
|
||||
@@ -400,10 +407,6 @@ def read_float(s, length):
|
||||
def parse_one(s, depth, parent, maxlen):
|
||||
elid = hexlify(read_id(s)).decode('ascii')
|
||||
elem = elementd.get(elid)
|
||||
if parent is not None and elid not in parent.subids and elid not in ('ec', 'bf'):
|
||||
print('Unexpected:', elid)
|
||||
if 1:
|
||||
raise NotImplementedError
|
||||
size, length = read_vint(s)
|
||||
this_length = len(elid) / 2 + size + length
|
||||
if elem is not None:
|
||||
@@ -442,7 +445,7 @@ def parse_one(s, depth, parent, maxlen):
|
||||
else:
|
||||
raise NotImplementedError
|
||||
else:
|
||||
print(depth, 'Unknown element:', elid, 'size:', length)
|
||||
print(" " * depth, '[' + elid + '] Unknown element! size:', length)
|
||||
read(s, length)
|
||||
return this_length
|
||||
|
||||
@@ -455,6 +458,8 @@ if __name__ == "__main__":
|
||||
elif sys.argv[1] == '--generate-definitions':
|
||||
generate_C_definitions(sys.stdout)
|
||||
else:
|
||||
if sys.version_info.major < 3:
|
||||
raise Exception("Dumping requires Python 3.")
|
||||
s = open(sys.argv[1], "rb")
|
||||
while 1:
|
||||
start = s.tell()
|
||||
|
||||
+5
-2
@@ -35,8 +35,11 @@ struct ad_functions {
|
||||
int (*init)(struct dec_audio *da, const char *decoder);
|
||||
void (*uninit)(struct dec_audio *da);
|
||||
int (*control)(struct dec_audio *da, int cmd, void *arg);
|
||||
int (*decode_packet)(struct dec_audio *da, struct demux_packet *pkt,
|
||||
struct mp_audio **out);
|
||||
// Return whether or not the packet has been consumed.
|
||||
bool (*send_packet)(struct dec_audio *da, struct demux_packet *pkt);
|
||||
// Return whether decoding is still going on (false if EOF was reached).
|
||||
// Never returns false & *out set, but can return true with !*out.
|
||||
bool (*receive_frame)(struct dec_audio *da, struct mp_audio **out);
|
||||
};
|
||||
|
||||
enum ad_ctrl {
|
||||
|
||||
+42
-45
@@ -45,7 +45,6 @@ struct priv {
|
||||
uint32_t skip_samples, trim_samples;
|
||||
bool preroll_done;
|
||||
double next_pts;
|
||||
bool needs_reset;
|
||||
AVRational codec_timebase;
|
||||
};
|
||||
|
||||
@@ -116,26 +115,18 @@ static int init(struct dec_audio *da, const char *decoder)
|
||||
av_opt_set_double(lavc_context, "drc_scale", opts->ac3drc,
|
||||
AV_OPT_SEARCH_CHILDREN);
|
||||
|
||||
#if HAVE_AVFRAME_SKIP_SAMPLES
|
||||
#if LIBAVCODEC_VERSION_MICRO >= 100
|
||||
// Let decoder add AV_FRAME_DATA_SKIP_SAMPLES.
|
||||
av_opt_set(lavc_context, "flags2", "+skip_manual", AV_OPT_SEARCH_CHILDREN);
|
||||
#endif
|
||||
|
||||
mp_set_avopts(da->log, lavc_context, opts->avopts);
|
||||
|
||||
lavc_context->codec_tag = c->codec_tag;
|
||||
lavc_context->sample_rate = c->samplerate;
|
||||
lavc_context->bit_rate = c->bitrate;
|
||||
lavc_context->block_align = c->block_align;
|
||||
lavc_context->bits_per_coded_sample = c->bits_per_coded_sample;
|
||||
lavc_context->channels = c->channels.num;
|
||||
if (!mp_chmap_is_unknown(&c->channels))
|
||||
lavc_context->channel_layout = mp_chmap_to_lavc(&c->channels);
|
||||
|
||||
// demux_mkv
|
||||
mp_lavc_set_extradata(lavc_context, c->extradata, c->extradata_size);
|
||||
|
||||
mp_set_lav_codec_headers(lavc_context, c);
|
||||
if (mp_set_avctx_codec_headers(lavc_context, c) < 0) {
|
||||
MP_ERR(da, "Could not set decoder parameters.\n");
|
||||
uninit(da);
|
||||
return 0;
|
||||
}
|
||||
|
||||
mp_set_avcodec_threads(da->log, lavc_context, opts->threads);
|
||||
|
||||
@@ -177,14 +168,12 @@ static int control(struct dec_audio *da, int cmd, void *arg)
|
||||
ctx->trim_samples = 0;
|
||||
ctx->preroll_done = false;
|
||||
ctx->next_pts = MP_NOPTS_VALUE;
|
||||
ctx->needs_reset = false;
|
||||
return CONTROL_TRUE;
|
||||
}
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
|
||||
static int decode_packet(struct dec_audio *da, struct demux_packet *mpkt,
|
||||
struct mp_audio **out)
|
||||
static bool send_packet(struct dec_audio *da, struct demux_packet *mpkt)
|
||||
{
|
||||
struct priv *priv = da->priv;
|
||||
AVCodecContext *avctx = priv->avctx;
|
||||
@@ -195,41 +184,48 @@ static int decode_packet(struct dec_audio *da, struct demux_packet *mpkt,
|
||||
if (mpkt && priv->next_pts == MP_NOPTS_VALUE)
|
||||
priv->next_pts = mpkt->pts;
|
||||
|
||||
int in_len = mpkt ? mpkt->len : 0;
|
||||
|
||||
AVPacket pkt;
|
||||
mp_set_av_packet(&pkt, mpkt, &priv->codec_timebase);
|
||||
|
||||
int got_frame = 0;
|
||||
av_frame_unref(priv->avframe);
|
||||
int ret = avcodec_send_packet(avctx, mpkt ? &pkt : NULL);
|
||||
|
||||
if (priv->needs_reset)
|
||||
control(da, ADCTRL_RESET, NULL);
|
||||
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
|
||||
return false;
|
||||
|
||||
int ret = avcodec_send_packet(avctx, &pkt);
|
||||
if (ret >= 0 || ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
|
||||
if (ret >= 0 && mpkt)
|
||||
mpkt->len = 0;
|
||||
ret = avcodec_receive_frame(avctx, priv->avframe);
|
||||
if (ret >= 0)
|
||||
got_frame = 1;
|
||||
if (ret == AVERROR_EOF)
|
||||
priv->needs_reset = true;
|
||||
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
|
||||
ret = 0;
|
||||
}
|
||||
if (ret < 0) {
|
||||
if (ret < 0)
|
||||
MP_ERR(da, "Error decoding audio.\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool receive_frame(struct dec_audio *da, struct mp_audio **out)
|
||||
{
|
||||
struct priv *priv = da->priv;
|
||||
AVCodecContext *avctx = priv->avctx;
|
||||
|
||||
int ret = avcodec_receive_frame(avctx, priv->avframe);
|
||||
|
||||
if (ret == AVERROR_EOF) {
|
||||
// If flushing was initialized earlier and has ended now, make it start
|
||||
// over in case we get new packets at some point in the future.
|
||||
control(da, ADCTRL_RESET, NULL);
|
||||
return false;
|
||||
} else if (ret < 0 && ret != AVERROR(EAGAIN)) {
|
||||
MP_ERR(da, "Error decoding audio.\n");
|
||||
return -1;
|
||||
}
|
||||
if (!got_frame)
|
||||
return 0;
|
||||
|
||||
#if LIBAVCODEC_VERSION_MICRO >= 100
|
||||
if (priv->avframe->flags & AV_FRAME_FLAG_DISCARD)
|
||||
av_frame_unref(priv->avframe);
|
||||
#endif
|
||||
|
||||
if (!priv->avframe->buf[0])
|
||||
return true;
|
||||
|
||||
double out_pts = mp_pts_from_av(priv->avframe->pts, &priv->codec_timebase);
|
||||
|
||||
struct mp_audio *mpframe = mp_audio_from_avframe(priv->avframe);
|
||||
if (!mpframe)
|
||||
return -1;
|
||||
return true;
|
||||
|
||||
struct mp_chmap lavc_chmap = mpframe->channels;
|
||||
if (lavc_chmap.num != avctx->channels)
|
||||
@@ -247,7 +243,7 @@ static int decode_packet(struct dec_audio *da, struct demux_packet *mpkt,
|
||||
if (mpframe->pts != MP_NOPTS_VALUE)
|
||||
priv->next_pts = mpframe->pts + mpframe->samples / (double)mpframe->rate;
|
||||
|
||||
#if HAVE_AVFRAME_SKIP_SAMPLES
|
||||
#if LIBAVCODEC_VERSION_MICRO >= 100
|
||||
AVFrameSideData *sd =
|
||||
av_frame_get_side_data(priv->avframe, AV_FRAME_DATA_SKIP_SAMPLES);
|
||||
if (sd && sd->size >= 10) {
|
||||
@@ -279,8 +275,8 @@ static int decode_packet(struct dec_audio *da, struct demux_packet *mpkt,
|
||||
|
||||
av_frame_unref(priv->avframe);
|
||||
|
||||
MP_DBG(da, "Decoded %d -> %d samples\n", in_len, mpframe->samples);
|
||||
return 0;
|
||||
MP_DBG(da, "Decoded %d samples\n", mpframe->samples);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void add_decoders(struct mp_decoder_list *list)
|
||||
@@ -294,5 +290,6 @@ const struct ad_functions ad_lavc = {
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.control = control,
|
||||
.decode_packet = decode_packet,
|
||||
.send_packet = send_packet,
|
||||
.receive_frame = receive_frame,
|
||||
};
|
||||
+74
-21
@@ -42,6 +42,8 @@ struct spdifContext {
|
||||
bool use_dts_hd;
|
||||
struct mp_audio fmt;
|
||||
struct mp_audio_pool *pool;
|
||||
bool got_eof;
|
||||
struct demux_packet *queued_packet;
|
||||
};
|
||||
|
||||
static int write_packet(void *p, uint8_t *buf, int buf_size)
|
||||
@@ -71,6 +73,7 @@ static void uninit(struct dec_audio *da)
|
||||
av_freep(&lavf_ctx->pb->buffer);
|
||||
av_freep(&lavf_ctx->pb);
|
||||
avformat_free_context(lavf_ctx);
|
||||
talloc_free(spdif_ctx->queued_packet);
|
||||
spdif_ctx->lavf_ctx = NULL;
|
||||
}
|
||||
}
|
||||
@@ -90,13 +93,34 @@ static int init(struct dec_audio *da, const char *decoder)
|
||||
return spdif_ctx->codec_id != AV_CODEC_ID_NONE;
|
||||
}
|
||||
|
||||
static int determine_codec_profile(struct dec_audio *da, AVPacket *pkt)
|
||||
static void determine_codec_params(struct dec_audio *da, AVPacket *pkt,
|
||||
int *out_profile, int *out_rate)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
int profile = FF_PROFILE_UNKNOWN;
|
||||
AVCodecContext *ctx = NULL;
|
||||
AVFrame *frame = NULL;
|
||||
|
||||
AVCodecParserContext *parser = av_parser_init(spdif_ctx->codec_id);
|
||||
if (parser) {
|
||||
// Don't make it wait for the next frame.
|
||||
parser->flags |= PARSER_FLAG_COMPLETE_FRAMES;
|
||||
|
||||
ctx = avcodec_alloc_context3(NULL);
|
||||
|
||||
uint8_t *d = NULL;
|
||||
int s = 0;
|
||||
av_parser_parse2(parser, ctx, &d, &s, pkt->data, pkt->size, 0, 0, 0);
|
||||
*out_profile = profile = ctx->profile;
|
||||
*out_rate = ctx->sample_rate;
|
||||
|
||||
av_free(ctx);
|
||||
av_parser_close(parser);
|
||||
}
|
||||
|
||||
if (profile != FF_PROFILE_UNKNOWN || spdif_ctx->codec_id != AV_CODEC_ID_DTS)
|
||||
return;
|
||||
|
||||
AVCodec *codec = avcodec_find_decoder(spdif_ctx->codec_id);
|
||||
if (!codec)
|
||||
goto done;
|
||||
@@ -120,7 +144,8 @@ static int determine_codec_profile(struct dec_audio *da, AVPacket *pkt)
|
||||
if (avcodec_receive_frame(ctx, frame) < 0)
|
||||
goto done;
|
||||
|
||||
profile = ctx->profile;
|
||||
*out_profile = profile = ctx->profile;
|
||||
*out_rate = ctx->sample_rate;
|
||||
|
||||
done:
|
||||
av_frame_free(&frame);
|
||||
@@ -130,8 +155,6 @@ done:
|
||||
|
||||
if (profile == FF_PROFILE_UNKNOWN)
|
||||
MP_WARN(da, "Failed to parse codec profile.\n");
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
static int init_filter(struct dec_audio *da, AVPacket *pkt)
|
||||
@@ -139,8 +162,9 @@ static int init_filter(struct dec_audio *da, AVPacket *pkt)
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
|
||||
int profile = FF_PROFILE_UNKNOWN;
|
||||
if (spdif_ctx->codec_id == AV_CODEC_ID_DTS)
|
||||
profile = determine_codec_profile(da, pkt);
|
||||
int c_rate = 0;
|
||||
determine_codec_params(da, pkt, &profile, &c_rate);
|
||||
MP_VERBOSE(da, "In: profile=%d samplerate=%d\n", profile, c_rate);
|
||||
|
||||
AVFormatContext *lavf_ctx = avformat_alloc_context();
|
||||
if (!lavf_ctx)
|
||||
@@ -186,7 +210,7 @@ static int init_filter(struct dec_audio *da, AVPacket *pkt)
|
||||
break;
|
||||
case AV_CODEC_ID_AC3:
|
||||
sample_format = AF_FORMAT_S_AC3;
|
||||
samplerate = 48000;
|
||||
samplerate = c_rate > 0 ? c_rate : 48000;
|
||||
num_channels = 2;
|
||||
break;
|
||||
case AV_CODEC_ID_DTS: {
|
||||
@@ -243,44 +267,72 @@ fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int decode_packet(struct dec_audio *da, struct demux_packet *mpkt,
|
||||
struct mp_audio **out)
|
||||
|
||||
static bool send_packet(struct dec_audio *da, struct demux_packet *mpkt)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
|
||||
spdif_ctx->out_buffer_len = 0;
|
||||
if (spdif_ctx->queued_packet || spdif_ctx->got_eof)
|
||||
return false;
|
||||
|
||||
if (!mpkt)
|
||||
return 0;
|
||||
spdif_ctx->queued_packet = mpkt ? demux_copy_packet(mpkt) : NULL;
|
||||
spdif_ctx->got_eof = !mpkt;
|
||||
return true;
|
||||
}
|
||||
|
||||
double pts = mpkt->pts;
|
||||
static bool receive_frame(struct dec_audio *da, struct mp_audio **out)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
|
||||
if (spdif_ctx->got_eof) {
|
||||
spdif_ctx->got_eof = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!spdif_ctx->queued_packet)
|
||||
return true;
|
||||
|
||||
double pts = spdif_ctx->queued_packet->pts;
|
||||
|
||||
AVPacket pkt;
|
||||
mp_set_av_packet(&pkt, mpkt, NULL);
|
||||
mpkt->len = 0; // will be fully consumed
|
||||
mp_set_av_packet(&pkt, spdif_ctx->queued_packet, NULL);
|
||||
pkt.pts = pkt.dts = 0;
|
||||
if (!spdif_ctx->lavf_ctx) {
|
||||
if (init_filter(da, &pkt) < 0)
|
||||
return -1;
|
||||
goto done;
|
||||
}
|
||||
spdif_ctx->out_buffer_len = 0;
|
||||
int ret = av_write_frame(spdif_ctx->lavf_ctx, &pkt);
|
||||
avio_flush(spdif_ctx->lavf_ctx->pb);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
if (ret < 0) {
|
||||
MP_ERR(da, "spdif mux error: '%s'\n", mp_strerror(AVUNERROR(ret)));
|
||||
goto done;
|
||||
}
|
||||
|
||||
int samples = spdif_ctx->out_buffer_len / spdif_ctx->fmt.sstride;
|
||||
*out = mp_audio_pool_get(spdif_ctx->pool, &spdif_ctx->fmt, samples);
|
||||
if (!*out)
|
||||
return -1;
|
||||
goto done;
|
||||
|
||||
memcpy((*out)->planes[0], spdif_ctx->out_buffer, spdif_ctx->out_buffer_len);
|
||||
(*out)->pts = pts;
|
||||
|
||||
return 0;
|
||||
done:
|
||||
talloc_free(spdif_ctx->queued_packet);
|
||||
spdif_ctx->queued_packet = NULL;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int control(struct dec_audio *da, int cmd, void *arg)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
switch (cmd) {
|
||||
case ADCTRL_RESET:
|
||||
talloc_free(spdif_ctx->queued_packet);
|
||||
spdif_ctx->queued_packet = NULL;
|
||||
spdif_ctx->got_eof = false;
|
||||
return CONTROL_TRUE;
|
||||
}
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
|
||||
@@ -344,5 +396,6 @@ const struct ad_functions ad_spdif = {
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.control = control,
|
||||
.decode_packet = decode_packet,
|
||||
.send_packet = send_packet,
|
||||
.receive_frame = receive_frame,
|
||||
};
|
||||
+10
-11
@@ -27,6 +27,7 @@
|
||||
|
||||
#include "common/codecs.h"
|
||||
#include "common/msg.h"
|
||||
#include "common/recorder.h"
|
||||
#include "misc/bstr.h"
|
||||
|
||||
#include "stream/stream.h"
|
||||
@@ -201,7 +202,7 @@ static void fix_audio_pts(struct dec_audio *da)
|
||||
|
||||
void audio_work(struct dec_audio *da)
|
||||
{
|
||||
if (da->current_frame)
|
||||
if (da->current_frame || !da->ad_driver)
|
||||
return;
|
||||
|
||||
if (!da->packet && !da->new_segment &&
|
||||
@@ -217,30 +218,28 @@ void audio_work(struct dec_audio *da)
|
||||
da->packet = NULL;
|
||||
}
|
||||
|
||||
bool had_input_packet = !!da->packet;
|
||||
bool had_packet = da->packet || da->new_segment;
|
||||
if (da->ad_driver->send_packet(da, da->packet)) {
|
||||
if (da->recorder_sink)
|
||||
mp_recorder_feed_packet(da->recorder_sink, da->packet);
|
||||
|
||||
int ret = da->ad_driver->decode_packet(da, da->packet, &da->current_frame);
|
||||
if (ret < 0 || (da->packet && da->packet->len == 0)) {
|
||||
talloc_free(da->packet);
|
||||
da->packet = NULL;
|
||||
}
|
||||
|
||||
bool progress = da->ad_driver->receive_frame(da, &da->current_frame);
|
||||
|
||||
if (da->current_frame && !mp_audio_config_valid(da->current_frame)) {
|
||||
talloc_free(da->current_frame);
|
||||
da->current_frame = NULL;
|
||||
}
|
||||
|
||||
da->current_state = DATA_OK;
|
||||
if (!da->current_frame) {
|
||||
da->current_state = da->current_frame ? DATA_OK : DATA_AGAIN;
|
||||
if (!progress)
|
||||
da->current_state = DATA_EOF;
|
||||
if (had_packet)
|
||||
da->current_state = DATA_AGAIN;
|
||||
}
|
||||
|
||||
fix_audio_pts(da);
|
||||
|
||||
bool segment_end = !da->current_frame && !had_input_packet;
|
||||
bool segment_end = da->current_state == DATA_EOF;
|
||||
|
||||
if (da->current_frame) {
|
||||
mp_audio_clip_timestamps(da->current_frame, da->start, da->end);
|
||||
|
||||
@@ -37,6 +37,8 @@ struct dec_audio {
|
||||
|
||||
bool try_spdif;
|
||||
|
||||
struct mp_recorder_sink *recorder_sink;
|
||||
|
||||
// For free use by the ad_driver
|
||||
void *priv;
|
||||
|
||||
|
||||
@@ -246,7 +246,7 @@ static int control(struct af_instance *af, int cmd, void *arg)
|
||||
|
||||
static void get_metadata_from_av_frame(struct af_instance *af, AVFrame *frame)
|
||||
{
|
||||
#if HAVE_AVFRAME_METADATA
|
||||
#if LIBAVUTIL_VERSION_MICRO >= 100
|
||||
struct priv *p = af->priv;
|
||||
if (!p->metadata)
|
||||
p->metadata = talloc_zero(p, struct mp_tags);
|
||||
@@ -266,6 +266,12 @@ static int filter_frame(struct af_instance *af, struct mp_audio *data)
|
||||
if (!p->graph)
|
||||
goto error;
|
||||
|
||||
if (!data) {
|
||||
if (p->eof)
|
||||
return 0;
|
||||
p->eof = true;
|
||||
}
|
||||
|
||||
if (data) {
|
||||
frame = mp_audio_to_avframe_and_unref(data);
|
||||
data = NULL;
|
||||
|
||||
@@ -36,6 +36,9 @@
|
||||
#include "common/common.h"
|
||||
#include "config.h"
|
||||
|
||||
#define HAVE_LIBSWRESAMPLE HAVE_IS_FFMPEG
|
||||
#define HAVE_LIBAVRESAMPLE HAVE_IS_LIBAV
|
||||
|
||||
#if HAVE_LIBAVRESAMPLE
|
||||
#include <libavresample/avresample.h>
|
||||
#elif HAVE_LIBSWRESAMPLE
|
||||
|
||||
+11
-7
@@ -279,14 +279,21 @@ static int reopen_device(struct ao *ao, bool allow_format_changes)
|
||||
int format = ao->format;
|
||||
struct mp_chmap channels = ao->channels;
|
||||
|
||||
const char *device = PATH_DEV_DSP;
|
||||
if (ao->device)
|
||||
device = ao->device;
|
||||
if (p->dsp && p->dsp[0])
|
||||
device = p->dsp;
|
||||
|
||||
MP_VERBOSE(ao, "using '%s' dsp device\n", device);
|
||||
#ifdef __linux__
|
||||
p->audio_fd = open(p->dsp, O_WRONLY | O_NONBLOCK);
|
||||
p->audio_fd = open(device, O_WRONLY | O_NONBLOCK);
|
||||
#else
|
||||
p->audio_fd = open(p->dsp, O_WRONLY);
|
||||
p->audio_fd = open(device, O_WRONLY);
|
||||
#endif
|
||||
if (p->audio_fd < 0) {
|
||||
MP_ERR(ao, "Can't open audio device %s: %s\n",
|
||||
p->dsp, mp_strerror(errno));
|
||||
device, mp_strerror(errno));
|
||||
goto fail;
|
||||
}
|
||||
|
||||
@@ -443,9 +450,8 @@ static int init(struct ao *ao)
|
||||
p->oss_mixer_channel = SOUND_MIXER_PCM;
|
||||
}
|
||||
|
||||
MP_VERBOSE(ao, "using '%s' dsp device\n", p->dsp);
|
||||
MP_VERBOSE(ao, "using '%s' mixer device\n", p->oss_mixer_device);
|
||||
MP_VERBOSE(ao, "using '%s' mixer device\n", mixer_channels[p->oss_mixer_channel]);
|
||||
MP_VERBOSE(ao, "using '%s' mixer channel\n", mixer_channels[p->oss_mixer_channel]);
|
||||
|
||||
ao->format = af_fmt_from_planar(ao->format);
|
||||
|
||||
@@ -643,8 +649,6 @@ const struct ao_driver audio_out_oss = {
|
||||
.buffersize = -1,
|
||||
.outburst = 512,
|
||||
.oss_mixer_channel = SOUND_MIXER_PCM,
|
||||
|
||||
.dsp = PATH_DEV_DSP,
|
||||
.oss_mixer_device = PATH_DEV_MIXER,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
|
||||
@@ -255,9 +255,9 @@ static void uninit(struct ao *ao)
|
||||
"while waiting for audio thread to terminate\n");
|
||||
}
|
||||
|
||||
SAFE_RELEASE(state->hInitDone, CloseHandle(state->hInitDone));
|
||||
SAFE_RELEASE(state->hWake, CloseHandle(state->hWake));
|
||||
SAFE_RELEASE(state->hAudioThread,CloseHandle(state->hAudioThread));
|
||||
SAFE_DESTROY(state->hInitDone, CloseHandle(state->hInitDone));
|
||||
SAFE_DESTROY(state->hWake, CloseHandle(state->hWake));
|
||||
SAFE_DESTROY(state->hAudioThread,CloseHandle(state->hAudioThread));
|
||||
|
||||
wasapi_change_uninit(ao);
|
||||
|
||||
@@ -305,7 +305,7 @@ static int init(struct ao *ao)
|
||||
}
|
||||
|
||||
WaitForSingleObject(state->hInitDone, INFINITE); // wait on init complete
|
||||
SAFE_RELEASE(state->hInitDone,CloseHandle(state->hInitDone));
|
||||
SAFE_DESTROY(state->hInitDone,CloseHandle(state->hInitDone));
|
||||
if (FAILED(state->init_ret)) {
|
||||
if (!ao->probing)
|
||||
MP_FATAL(ao, "Received failure from audio thread\n");
|
||||
@@ -418,10 +418,10 @@ static int thread_control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
do {
|
||||
IAudioSessionControl_SetDisplayName(state->pSessionControl, title, NULL);
|
||||
|
||||
SAFE_RELEASE(tmp, CoTaskMemFree(tmp));
|
||||
SAFE_DESTROY(tmp, CoTaskMemFree(tmp));
|
||||
IAudioSessionControl_GetDisplayName(state->pSessionControl, &tmp);
|
||||
} while (lstrcmpW(title, tmp));
|
||||
SAFE_RELEASE(tmp, CoTaskMemFree(tmp));
|
||||
SAFE_DESTROY(tmp, CoTaskMemFree(tmp));
|
||||
talloc_free(title);
|
||||
return CONTROL_OK;
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ void wasapi_change_uninit(struct ao* ao);
|
||||
|
||||
#define EXIT_ON_ERROR(hres) \
|
||||
do { if (FAILED(hres)) { goto exit_label; } } while(0)
|
||||
#define SAFE_RELEASE(unk, release) \
|
||||
#define SAFE_DESTROY(unk, release) \
|
||||
do { if ((unk) != NULL) { release; (unk) = NULL; } } while(0)
|
||||
|
||||
#define mp_format_res_str(hres) \
|
||||
|
||||
@@ -242,5 +242,5 @@ void wasapi_change_uninit(struct ao *ao)
|
||||
change->pEnumerator, (IMMNotificationClient *)change);
|
||||
}
|
||||
|
||||
SAFE_RELEASE(change->pEnumerator, IMMDeviceEnumerator_Release(change->pEnumerator));
|
||||
SAFE_RELEASE(change->pEnumerator);
|
||||
}
|
||||
+19
-22
@@ -539,8 +539,7 @@ static void init_session_display(struct wasapi_state *state) {
|
||||
return;
|
||||
exit_label:
|
||||
// if we got here then the session control is useless - release it
|
||||
SAFE_RELEASE(state->pSessionControl,
|
||||
IAudioSessionControl_Release(state->pSessionControl));
|
||||
SAFE_RELEASE(state->pSessionControl);
|
||||
MP_WARN(state, "Error setting audio session display name: %s\n",
|
||||
mp_HRESULT_to_str(hr));
|
||||
return;
|
||||
@@ -571,10 +570,8 @@ static void init_volume_control(struct wasapi_state *state)
|
||||
return;
|
||||
exit_label:
|
||||
state->vol_hw_support = 0;
|
||||
SAFE_RELEASE(state->pEndpointVolume,
|
||||
IAudioEndpointVolume_Release(state->pEndpointVolume));
|
||||
SAFE_RELEASE(state->pAudioVolume,
|
||||
ISimpleAudioVolume_Release(state->pAudioVolume));
|
||||
SAFE_RELEASE(state->pEndpointVolume);
|
||||
SAFE_RELEASE(state->pAudioVolume);
|
||||
MP_WARN(state, "Error setting up volume control: %s\n",
|
||||
mp_HRESULT_to_str(hr));
|
||||
}
|
||||
@@ -707,7 +704,7 @@ exit_label:
|
||||
if (FAILED(hr))
|
||||
mp_warn(l, "Failed getting device name: %s\n", mp_HRESULT_to_str(hr));
|
||||
PropVariantClear(&devname);
|
||||
SAFE_RELEASE(pProps, IPropertyStore_Release(pProps));
|
||||
SAFE_RELEASE(pProps);
|
||||
return namestr ? namestr : talloc_strdup(talloc_ctx, "");
|
||||
}
|
||||
|
||||
@@ -722,7 +719,7 @@ static struct device_desc *get_device_desc(struct mp_log *l, IMMDevice *pDevice)
|
||||
struct device_desc *d = talloc_zero(NULL, struct device_desc);
|
||||
d->deviceID = talloc_memdup(d, deviceID,
|
||||
(wcslen(deviceID) + 1) * sizeof(wchar_t));
|
||||
SAFE_RELEASE(deviceID, CoTaskMemFree(deviceID));
|
||||
SAFE_DESTROY(deviceID, CoTaskMemFree(deviceID));
|
||||
|
||||
char *full_id = mp_to_utf8(NULL, d->deviceID);
|
||||
bstr id = bstr0(full_id);
|
||||
@@ -745,8 +742,8 @@ static void destroy_enumerator(struct enumerator *e)
|
||||
{
|
||||
if (!e)
|
||||
return;
|
||||
SAFE_RELEASE(e->pDevices, IMMDeviceCollection_Release(e->pDevices));
|
||||
SAFE_RELEASE(e->pEnumerator, IMMDeviceEnumerator_Release(e->pEnumerator));
|
||||
SAFE_RELEASE(e->pDevices);
|
||||
SAFE_RELEASE(e->pEnumerator);
|
||||
talloc_free(e);
|
||||
}
|
||||
|
||||
@@ -782,7 +779,7 @@ static struct device_desc *device_desc_for_num(struct enumerator *e, UINT i)
|
||||
return NULL;
|
||||
}
|
||||
struct device_desc *d = get_device_desc(e->log, pDevice);
|
||||
SAFE_RELEASE(pDevice, IMMDevice_Release(pDevice));
|
||||
SAFE_RELEASE(pDevice);
|
||||
return d;
|
||||
}
|
||||
|
||||
@@ -797,7 +794,7 @@ static struct device_desc *default_device_desc(struct enumerator *e)
|
||||
return NULL;
|
||||
}
|
||||
struct device_desc *d = get_device_desc(e->log, pDevice);
|
||||
SAFE_RELEASE(pDevice, IMMDevice_Release(pDevice));
|
||||
SAFE_RELEASE(pDevice);
|
||||
return d;
|
||||
}
|
||||
|
||||
@@ -834,7 +831,7 @@ static HRESULT load_device(struct mp_log *l,
|
||||
exit_label:
|
||||
if (FAILED(hr))
|
||||
mp_err(l, "Error loading selected device: %s\n", mp_HRESULT_to_str(hr));
|
||||
SAFE_RELEASE(pEnumerator, IMMDeviceEnumerator_Release(pEnumerator));
|
||||
SAFE_RELEASE(pEnumerator);
|
||||
return hr;
|
||||
}
|
||||
|
||||
@@ -908,7 +905,7 @@ LPWSTR wasapi_find_deviceID(struct ao *ao)
|
||||
BSTR_P(device), d->id, d->name);
|
||||
}
|
||||
}
|
||||
SAFE_RELEASE(d, talloc_free(d));
|
||||
SAFE_DESTROY(d, talloc_free(d));
|
||||
}
|
||||
|
||||
if (!deviceID)
|
||||
@@ -969,13 +966,13 @@ void wasapi_thread_uninit(struct ao *ao)
|
||||
if (state->pAudioClient)
|
||||
IAudioClient_Stop(state->pAudioClient);
|
||||
|
||||
SAFE_RELEASE(state->pRenderClient, IAudioRenderClient_Release(state->pRenderClient));
|
||||
SAFE_RELEASE(state->pAudioClock, IAudioClock_Release(state->pAudioClock));
|
||||
SAFE_RELEASE(state->pAudioVolume, ISimpleAudioVolume_Release(state->pAudioVolume));
|
||||
SAFE_RELEASE(state->pEndpointVolume, IAudioEndpointVolume_Release(state->pEndpointVolume));
|
||||
SAFE_RELEASE(state->pSessionControl, IAudioSessionControl_Release(state->pSessionControl));
|
||||
SAFE_RELEASE(state->pAudioClient, IAudioClient_Release(state->pAudioClient));
|
||||
SAFE_RELEASE(state->pDevice, IMMDevice_Release(state->pDevice));
|
||||
SAFE_RELEASE(state->hTask, AvRevertMmThreadCharacteristics(state->hTask));
|
||||
SAFE_RELEASE(state->pRenderClient);
|
||||
SAFE_RELEASE(state->pAudioClock);
|
||||
SAFE_RELEASE(state->pAudioVolume);
|
||||
SAFE_RELEASE(state->pEndpointVolume);
|
||||
SAFE_RELEASE(state->pSessionControl);
|
||||
SAFE_RELEASE(state->pAudioClient);
|
||||
SAFE_RELEASE(state->pDevice);
|
||||
SAFE_DESTROY(state->hTask, AvRevertMmThreadCharacteristics(state->hTask));
|
||||
MP_DBG(ao, "Thread uninit done\n");
|
||||
}
|
||||
+3
-4
@@ -251,12 +251,11 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
if (got_data) {
|
||||
p->still_playing = true;
|
||||
p->expected_end_time = 0;
|
||||
}
|
||||
|
||||
// If we don't have new data, the decoder thread basically promises it
|
||||
// will send new data as soon as it's available.
|
||||
if (got_data)
|
||||
// If we don't have new data, the decoder thread basically promises it
|
||||
// will send new data as soon as it's available.
|
||||
wakeup_playthread(ao);
|
||||
}
|
||||
pthread_mutex_unlock(&p->lock);
|
||||
return write_samples;
|
||||
}
|
||||
|
||||
+81
-47
@@ -33,6 +33,7 @@
|
||||
#include "common/msg.h"
|
||||
#include "demux/packet.h"
|
||||
#include "demux/stheader.h"
|
||||
#include "video/fmt-conversion.h"
|
||||
#include "av_common.h"
|
||||
#include "codecs.h"
|
||||
|
||||
@@ -41,7 +42,7 @@ int mp_lavc_set_extradata(AVCodecContext *avctx, void *ptr, int size)
|
||||
if (size) {
|
||||
av_free(avctx->extradata);
|
||||
avctx->extradata_size = 0;
|
||||
avctx->extradata = av_mallocz(size + FF_INPUT_BUFFER_PADDING_SIZE);
|
||||
avctx->extradata = av_mallocz(size + AV_INPUT_BUFFER_PADDING_SIZE);
|
||||
if (!avctx->extradata)
|
||||
return -1;
|
||||
avctx->extradata_size = size;
|
||||
@@ -50,35 +51,81 @@ int mp_lavc_set_extradata(AVCodecContext *avctx, void *ptr, int size)
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Copy the codec-related fields from st into avctx. This does not set the
|
||||
// codec itself, only codec related header data provided by libavformat.
|
||||
// The goal is to initialize a new decoder with the header data provided by
|
||||
// libavformat, and unlike avcodec_copy_context(), allow the user to create
|
||||
// a clean AVCodecContext for a manually selected AVCodec.
|
||||
// This is strictly for decoding only.
|
||||
void mp_copy_lav_codec_headers(AVCodecContext *avctx, AVCodecContext *st)
|
||||
enum AVMediaType mp_to_av_stream_type(int type)
|
||||
{
|
||||
mp_lavc_set_extradata(avctx, st->extradata, st->extradata_size);
|
||||
avctx->codec_tag = st->codec_tag;
|
||||
avctx->bit_rate = st->bit_rate;
|
||||
avctx->width = st->width;
|
||||
avctx->height = st->height;
|
||||
avctx->pix_fmt = st->pix_fmt;
|
||||
avctx->chroma_sample_location = st->chroma_sample_location;
|
||||
avctx->sample_rate = st->sample_rate;
|
||||
avctx->channels = st->channels;
|
||||
avctx->block_align = st->block_align;
|
||||
avctx->channel_layout = st->channel_layout;
|
||||
avctx->bits_per_coded_sample = st->bits_per_coded_sample;
|
||||
avctx->has_b_frames = st->has_b_frames;
|
||||
switch (type) {
|
||||
case STREAM_VIDEO: return AVMEDIA_TYPE_VIDEO;
|
||||
case STREAM_AUDIO: return AVMEDIA_TYPE_AUDIO;
|
||||
case STREAM_SUB: return AVMEDIA_TYPE_SUBTITLE;
|
||||
default: return AVMEDIA_TYPE_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
// This only copies ffmpeg-native codec parameters. Parameters produced by
|
||||
// other demuxers must be handled manually.
|
||||
void mp_set_lav_codec_headers(AVCodecContext *avctx, struct mp_codec_params *c)
|
||||
AVCodecParameters *mp_codec_params_to_av(struct mp_codec_params *c)
|
||||
{
|
||||
if (c->lav_codecpar)
|
||||
avcodec_parameters_to_context(avctx, c->lav_codecpar);
|
||||
AVCodecParameters *avp = avcodec_parameters_alloc();
|
||||
if (!avp)
|
||||
return NULL;
|
||||
|
||||
// If we have lavf demuxer params, they overwrite by definition any others.
|
||||
if (c->lav_codecpar) {
|
||||
if (avcodec_parameters_copy(avp, c->lav_codecpar) < 0)
|
||||
goto error;
|
||||
return avp;
|
||||
}
|
||||
|
||||
avp->codec_type = mp_to_av_stream_type(c->type);
|
||||
avp->codec_id = mp_codec_to_av_codec_id(c->codec);
|
||||
avp->codec_tag = c->codec_tag;
|
||||
if (c->extradata_size) {
|
||||
avp->extradata =
|
||||
av_mallocz(c->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);
|
||||
if (!avp->extradata)
|
||||
goto error;
|
||||
avp->extradata_size = c->extradata_size;
|
||||
memcpy(avp->extradata, c->extradata, avp->extradata_size);
|
||||
}
|
||||
avp->bits_per_coded_sample = c->bits_per_coded_sample;
|
||||
|
||||
// Video only
|
||||
avp->width = c->disp_w;
|
||||
avp->height = c->disp_h;
|
||||
if (c->codec && strcmp(c->codec, "mp-rawvideo") == 0) {
|
||||
avp->format = imgfmt2pixfmt(c->codec_tag);
|
||||
avp->codec_tag = 0;
|
||||
}
|
||||
|
||||
// Audio only
|
||||
avp->sample_rate = c->samplerate;
|
||||
avp->bit_rate = c->bitrate;
|
||||
avp->block_align = c->block_align;
|
||||
avp->channels = c->channels.num;
|
||||
if (!mp_chmap_is_unknown(&c->channels))
|
||||
avp->channel_layout = mp_chmap_to_lavc(&c->channels);
|
||||
|
||||
return avp;
|
||||
error:
|
||||
avcodec_parameters_free(&avp);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Set avctx codec headers for decoding. Returns <0 on failure.
|
||||
int mp_set_avctx_codec_headers(AVCodecContext *avctx, struct mp_codec_params *c)
|
||||
{
|
||||
enum AVMediaType codec_type = avctx->codec_type;
|
||||
enum AVCodecID codec_id = avctx->codec_id;
|
||||
AVCodecParameters *avp = mp_codec_params_to_av(c);
|
||||
if (!avp)
|
||||
return -1;
|
||||
|
||||
int r = avcodec_parameters_to_context(avctx, avp) < 0 ? -1 : 0;
|
||||
avcodec_parameters_free(&avp);
|
||||
|
||||
if (avctx->codec_type != AVMEDIA_TYPE_UNKNOWN)
|
||||
avctx->codec_type = codec_type;
|
||||
if (avctx->codec_id != AV_CODEC_ID_NONE)
|
||||
avctx->codec_id = codec_id;
|
||||
return r;
|
||||
}
|
||||
|
||||
// Pick a "good" timebase, which will be used to convert double timestamps
|
||||
@@ -108,37 +155,24 @@ AVRational mp_get_codec_timebase(struct mp_codec_params *c)
|
||||
return tb;
|
||||
}
|
||||
|
||||
// We merely pass-through our PTS/DTS as an int64_t; libavcodec won't use it.
|
||||
union pts { int64_t i; double d; };
|
||||
static AVRational get_def_tb(AVRational *tb)
|
||||
{
|
||||
return tb && tb->num > 0 && tb->den > 0 ? *tb : AV_TIME_BASE_Q;
|
||||
}
|
||||
|
||||
// Convert the mpv style timestamp (seconds as double) to a libavcodec style
|
||||
// timestamp (integer units in a given timebase).
|
||||
//
|
||||
// If the given timebase is NULL or invalid, pass through the mpv timestamp by
|
||||
// reinterpret casting them to int64_t. In this case, the timestamps will be
|
||||
// non-sense for libavcodec, but we expect that it doesn't interpret them,
|
||||
// and treats them as opaque.
|
||||
int64_t mp_pts_to_av(double mp_pts, AVRational *tb)
|
||||
{
|
||||
assert(sizeof(int64_t) >= sizeof(double));
|
||||
if (tb && tb->num > 0 && tb->den > 0) {
|
||||
return mp_pts == MP_NOPTS_VALUE ?
|
||||
AV_NOPTS_VALUE : llrint(mp_pts / av_q2d(*tb));
|
||||
}
|
||||
// The + 0.0 is to squash possible negative zero mp_pts, which would
|
||||
// happen to end up as AV_NOPTS_VALUE.
|
||||
return (union pts){.d = mp_pts + 0.0}.i;
|
||||
AVRational b = get_def_tb(tb);
|
||||
return mp_pts == MP_NOPTS_VALUE ? AV_NOPTS_VALUE : llrint(mp_pts / av_q2d(b));
|
||||
}
|
||||
|
||||
// Inverse of mp_pts_to_av(). (The timebases must be exactly the same.)
|
||||
double mp_pts_from_av(int64_t av_pts, AVRational *tb)
|
||||
{
|
||||
assert(sizeof(int64_t) >= sizeof(double));
|
||||
if (tb && tb->num > 0 && tb->den > 0)
|
||||
return av_pts == AV_NOPTS_VALUE ? MP_NOPTS_VALUE : av_pts * av_q2d(*tb);
|
||||
// Should libavcodec set the PTS to AV_NOPTS_VALUE, it would end up as
|
||||
// non-sense (usually negative zero) when unwrapped to double.
|
||||
return av_pts == AV_NOPTS_VALUE ? MP_NOPTS_VALUE : (union pts){.i = av_pts}.d;
|
||||
AVRational b = get_def_tb(tb);
|
||||
return av_pts == AV_NOPTS_VALUE ? MP_NOPTS_VALUE : av_pts * av_q2d(b);
|
||||
}
|
||||
|
||||
// Set dst from mpkt. Note that dst is not refcountable.
|
||||
|
||||
+3
-2
@@ -31,8 +31,9 @@ struct AVDictionary;
|
||||
struct mp_log;
|
||||
|
||||
int mp_lavc_set_extradata(AVCodecContext *avctx, void *ptr, int size);
|
||||
void mp_copy_lav_codec_headers(AVCodecContext *avctx, AVCodecContext *st);
|
||||
void mp_set_lav_codec_headers(AVCodecContext *avctx, struct mp_codec_params *c);
|
||||
enum AVMediaType mp_to_av_stream_type(int type);
|
||||
AVCodecParameters *mp_codec_params_to_av(struct mp_codec_params *c);
|
||||
int mp_set_avctx_codec_headers(AVCodecContext *avctx, struct mp_codec_params *c);
|
||||
AVRational mp_get_codec_timebase(struct mp_codec_params *c);
|
||||
void mp_set_av_packet(AVPacket *dst, struct demux_packet *mpkt, AVRational *tb);
|
||||
int64_t mp_pts_to_av(double mp_pts, AVRational *tb);
|
||||
|
||||
+4
-4
@@ -42,10 +42,10 @@
|
||||
#include <libavdevice/avdevice.h>
|
||||
#endif
|
||||
|
||||
#if HAVE_LIBAVRESAMPLE
|
||||
#if HAVE_IS_LIBAV
|
||||
#include <libavresample/avresample.h>
|
||||
#endif
|
||||
#if HAVE_LIBSWRESAMPLE
|
||||
#if HAVE_IS_FFMPEG
|
||||
#include <libswresample/swresample.h>
|
||||
#endif
|
||||
|
||||
@@ -197,10 +197,10 @@ bool print_libav_versions(struct mp_log *log, int v)
|
||||
{"libavformat", LIBAVFORMAT_VERSION_INT, avformat_version()},
|
||||
{"libswscale", LIBSWSCALE_VERSION_INT, swscale_version()},
|
||||
{"libavfilter", LIBAVFILTER_VERSION_INT, avfilter_version()},
|
||||
#if HAVE_LIBAVRESAMPLE
|
||||
#if HAVE_IS_LIBAV
|
||||
{"libavresample", LIBAVRESAMPLE_VERSION_INT, avresample_version()},
|
||||
#endif
|
||||
#if HAVE_LIBSWRESAMPLE
|
||||
#if HAVE_IS_FFMPEG
|
||||
{"libswresample", LIBSWRESAMPLE_VERSION_INT, swresample_version()},
|
||||
#endif
|
||||
};
|
||||
|
||||
+11
-58
@@ -33,26 +33,6 @@ void mp_add_decoder(struct mp_decoder_list *list, const char *family,
|
||||
MP_TARRAY_APPEND(list, list->entries, list->num_entries, entry);
|
||||
}
|
||||
|
||||
static void mp_add_decoder_entry(struct mp_decoder_list *list,
|
||||
struct mp_decoder_entry *entry)
|
||||
{
|
||||
mp_add_decoder(list, entry->family, entry->codec, entry->decoder,
|
||||
entry->desc);
|
||||
}
|
||||
|
||||
static struct mp_decoder_entry *find_decoder(struct mp_decoder_list *list,
|
||||
bstr family, bstr decoder)
|
||||
{
|
||||
for (int n = 0; n < list->num_entries; n++) {
|
||||
struct mp_decoder_entry *cur = &list->entries[n];
|
||||
if (bstr_equals0(decoder, cur->decoder)) {
|
||||
if (bstr_equals0(family, "*") || bstr_equals0(family, cur->family))
|
||||
return cur;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Add entry, but only if it's not yet on the list, and if the codec matches.
|
||||
// If codec == NULL, don't compare codecs.
|
||||
static void add_new(struct mp_decoder_list *to, struct mp_decoder_entry *entry,
|
||||
@@ -60,8 +40,7 @@ static void add_new(struct mp_decoder_list *to, struct mp_decoder_entry *entry,
|
||||
{
|
||||
if (!entry || (codec && strcmp(entry->codec, codec) != 0))
|
||||
return;
|
||||
if (!find_decoder(to, bstr0(entry->family), bstr0(entry->decoder)))
|
||||
mp_add_decoder_entry(to, entry);
|
||||
mp_add_decoder(to, entry->family, entry->codec, entry->decoder, entry->desc);
|
||||
}
|
||||
|
||||
// Select a decoder from the given list for the given codec. The selection
|
||||
@@ -71,10 +50,8 @@ static void add_new(struct mp_decoder_list *to, struct mp_decoder_entry *entry,
|
||||
// The selection string corresponds to --vd/--ad directly, and has the
|
||||
// following syntax:
|
||||
// selection = [<entry> ("," <entry>)*]
|
||||
// entry = [<family> ":"] <decoder> // prefer decoder
|
||||
// entry = <family> ":*" // prefer all decoders
|
||||
// entry = "+" [<family> ":"] <decoder> // force a decoder
|
||||
// entry = "-" [<family> ":"] <decoder> // exclude a decoder
|
||||
// entry = <decoder> // prefer decoder
|
||||
// entry = "-" <decoder> // exclude a decoder
|
||||
// entry = "-" // don't add fallback decoders
|
||||
// Forcing a decoder means it's added even if the codec mismatches.
|
||||
struct mp_decoder_list *mp_select_decoders(struct mp_log *log,
|
||||
@@ -83,7 +60,6 @@ struct mp_decoder_list *mp_select_decoders(struct mp_log *log,
|
||||
const char *selection)
|
||||
{
|
||||
struct mp_decoder_list *list = talloc_zero(NULL, struct mp_decoder_list);
|
||||
struct mp_decoder_list *remove = talloc_zero(NULL, struct mp_decoder_list);
|
||||
if (!codec)
|
||||
codec = "unknown";
|
||||
bool stop = false;
|
||||
@@ -96,28 +72,15 @@ struct mp_decoder_list *mp_select_decoders(struct mp_log *log,
|
||||
stop = true;
|
||||
break;
|
||||
}
|
||||
bool force = bstr_eatstart0(&entry, "+");
|
||||
bool exclude = !force && bstr_eatstart0(&entry, "-");
|
||||
if (exclude || force)
|
||||
mp_warn(log, "Forcing or excluding codecs is deprecated.\n");
|
||||
struct mp_decoder_list *dest = exclude ? remove : list;
|
||||
bstr family, decoder;
|
||||
if (bstr_split_tok(entry, ":", &family, &decoder)) {
|
||||
mp_warn(log, "Codec family selection is deprecated. "
|
||||
"Pass the codec name directly.\n");
|
||||
} else {
|
||||
family = bstr0("*");
|
||||
decoder = entry;
|
||||
if (bstr_find0(entry, ":") >= 0) {
|
||||
mp_err(log, "Codec family selection was removed. "
|
||||
"Pass the codec name directly.\n");
|
||||
break;
|
||||
}
|
||||
if (bstr_equals0(decoder, "*")) {
|
||||
for (int n = 0; n < all->num_entries; n++) {
|
||||
struct mp_decoder_entry *cur = &all->entries[n];
|
||||
if (bstr_equals0(family, cur->family))
|
||||
add_new(dest, cur, codec);
|
||||
}
|
||||
} else {
|
||||
add_new(dest, find_decoder(all, family, decoder),
|
||||
force ? NULL : codec);
|
||||
for (int n = 0; n < all->num_entries; n++) {
|
||||
struct mp_decoder_entry *cur = &all->entries[n];
|
||||
if (bstr_equals0(entry, cur->decoder))
|
||||
add_new(list, cur, codec);
|
||||
}
|
||||
}
|
||||
if (!stop) {
|
||||
@@ -125,16 +88,6 @@ struct mp_decoder_list *mp_select_decoders(struct mp_log *log,
|
||||
for (int n = 0; n < all->num_entries; n++)
|
||||
add_new(list, &all->entries[n], codec);
|
||||
}
|
||||
for (int n = 0; n < remove->num_entries; n++) {
|
||||
struct mp_decoder_entry *ex = &remove->entries[n];
|
||||
struct mp_decoder_entry *del =
|
||||
find_decoder(list, bstr0(ex->family), bstr0(ex->decoder));
|
||||
if (del) {
|
||||
int index = del - &list->entries[0];
|
||||
MP_TARRAY_REMOVE_AT(list->entries, list->num_entries, index);
|
||||
}
|
||||
}
|
||||
talloc_free(remove);
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
@@ -473,7 +473,7 @@ static void encode_2pass_prepare(struct encode_lavc_context *ctx,
|
||||
if (!(*bytebuf = stream_open(buf, ctx->global))) {
|
||||
MP_WARN(ctx, "%s: could not open '%s', "
|
||||
"disabling 2-pass encoding at pass 2\n", prefix, buf);
|
||||
codec->flags &= ~CODEC_FLAG_PASS2;
|
||||
codec->flags &= ~AV_CODEC_FLAG_PASS2;
|
||||
set_to_avdictionary(ctx, dictp, "flags", "-pass2");
|
||||
} else {
|
||||
struct bstr content = stream_read_complete(*bytebuf, NULL,
|
||||
|
||||
@@ -0,0 +1,384 @@
|
||||
/*
|
||||
* 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 <math.h>
|
||||
|
||||
#include <libavformat/avformat.h>
|
||||
|
||||
#include "common/av_common.h"
|
||||
#include "common/common.h"
|
||||
#include "common/global.h"
|
||||
#include "common/msg.h"
|
||||
#include "demux/packet.h"
|
||||
#include "demux/stheader.h"
|
||||
|
||||
#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.
|
||||
#define QUEUE_MAX_PACKETS 256
|
||||
// Number of packets we should buffer at least to determine timestamps (due to
|
||||
// codec delay and frame reordering, and potentially lack of DTS).
|
||||
// Keyframe flags can trigger this earlier.
|
||||
#define QUEUE_MIN_PACKETS 16
|
||||
|
||||
struct mp_recorder {
|
||||
struct mpv_global *global;
|
||||
struct mp_log *log;
|
||||
|
||||
struct mp_recorder_sink **streams;
|
||||
int num_streams;
|
||||
|
||||
bool opened; // mux context is valid
|
||||
bool muxing; // we're currently recording (instead of preparing)
|
||||
bool muxing_from_start; // no discontinuity at start
|
||||
bool dts_warning;
|
||||
|
||||
// The start timestamp of the currently recorded segment (the timestamp of
|
||||
// the first packet of the incoming packet stream).
|
||||
double base_ts;
|
||||
// The output packet timestamp corresponding to base_ts. It's the timestamp
|
||||
// of the first packet of the current segment written to the output.
|
||||
double rebase_ts;
|
||||
|
||||
AVFormatContext *mux;
|
||||
};
|
||||
|
||||
struct mp_recorder_sink {
|
||||
struct mp_recorder *owner;
|
||||
struct sh_stream *sh;
|
||||
AVStream *av_stream;
|
||||
double max_out_pts;
|
||||
bool discont;
|
||||
bool proper_eof;
|
||||
struct demux_packet **packets;
|
||||
int num_packets;
|
||||
};
|
||||
|
||||
static int add_stream(struct mp_recorder *priv, struct sh_stream *sh)
|
||||
{
|
||||
enum AVMediaType av_type = mp_to_av_stream_type(sh->type);
|
||||
if (av_type == AVMEDIA_TYPE_UNKNOWN)
|
||||
return -1;
|
||||
|
||||
struct mp_recorder_sink *rst = talloc(priv, struct mp_recorder_sink);
|
||||
*rst = (struct mp_recorder_sink) {
|
||||
.owner = priv,
|
||||
.sh = sh,
|
||||
.av_stream = avformat_new_stream(priv->mux, NULL),
|
||||
.max_out_pts = MP_NOPTS_VALUE,
|
||||
};
|
||||
|
||||
if (!rst->av_stream)
|
||||
return -1;
|
||||
|
||||
AVCodecParameters *avp = mp_codec_params_to_av(sh->codec);
|
||||
if (!avp)
|
||||
return -1;
|
||||
|
||||
#if LIBAVCODEC_VERSION_MICRO >= 100
|
||||
// We don't know the delay, so make something up. If the format requires
|
||||
// DTS, the result will probably be broken. FFmpeg provides nothing better
|
||||
// yet (unless you demux with libavformat, which contains tons of hacks
|
||||
// that try to determine a PTS).
|
||||
if (!sh->codec->lav_codecpar)
|
||||
avp->video_delay = 16;
|
||||
#endif
|
||||
|
||||
if (avp->codec_id == AV_CODEC_ID_NONE)
|
||||
return -1;
|
||||
|
||||
if (avcodec_parameters_copy(rst->av_stream->codecpar, avp) < 0)
|
||||
return -1;
|
||||
|
||||
rst->av_stream->time_base = mp_get_codec_timebase(sh->codec);
|
||||
|
||||
MP_TARRAY_APPEND(priv, priv->streams, priv->num_streams, rst);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct mp_recorder *mp_recorder_create(struct mpv_global *global,
|
||||
const char *target_file,
|
||||
struct sh_stream **streams,
|
||||
int num_streams)
|
||||
{
|
||||
struct mp_recorder *priv = talloc_zero(NULL, struct mp_recorder);
|
||||
|
||||
priv->global = global;
|
||||
priv->log = mp_log_new(priv, global->log, "recorder");
|
||||
|
||||
if (!num_streams) {
|
||||
MP_ERR(priv, "No streams.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
priv->mux = avformat_alloc_context();
|
||||
if (!priv->mux)
|
||||
goto error;
|
||||
|
||||
priv->mux->oformat = av_guess_format(NULL, target_file, NULL);
|
||||
if (!priv->mux->oformat) {
|
||||
MP_ERR(priv, "Output format not found.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (avio_open2(&priv->mux->pb, target_file, AVIO_FLAG_WRITE, NULL, NULL) < 0) {
|
||||
MP_ERR(priv, "Failed opening output file.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
for (int n = 0; n < num_streams; n++) {
|
||||
if (add_stream(priv, streams[n]) < 0) {
|
||||
MP_ERR(priv, "Can't mux one of the input streams.\n");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
// Not sure how to write this in a "standard" way. It appears only mkv
|
||||
// and mp4 support this directly.
|
||||
char version[200];
|
||||
snprintf(version, sizeof(version), "%s experimental stream recording "
|
||||
"feature (can generate broken files - please report bugs)",
|
||||
mpv_version);
|
||||
av_dict_set(&priv->mux->metadata, "encoding_tool", version, 0);
|
||||
|
||||
if (avformat_write_header(priv->mux, NULL) < 0) {
|
||||
MP_ERR(priv, "Write header failed.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
priv->opened = true;
|
||||
priv->muxing_from_start = true;
|
||||
|
||||
priv->base_ts = MP_NOPTS_VALUE;
|
||||
priv->rebase_ts = 0;
|
||||
|
||||
MP_WARN(priv, "This is an experimental feature. Output files might be "
|
||||
"broken or not play correctly with various players "
|
||||
"(including mpv itself).\n");
|
||||
|
||||
return priv;
|
||||
|
||||
error:
|
||||
mp_recorder_destroy(priv);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void flush_packets(struct mp_recorder *priv)
|
||||
{
|
||||
for (int n = 0; n < priv->num_streams; n++) {
|
||||
struct mp_recorder_sink *rst = priv->streams[n];
|
||||
for (int i = 0; i < rst->num_packets; i++)
|
||||
talloc_free(rst->packets[i]);
|
||||
rst->num_packets = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void mux_packet(struct mp_recorder_sink *rst,
|
||||
struct demux_packet *pkt)
|
||||
{
|
||||
struct mp_recorder *priv = rst->owner;
|
||||
struct demux_packet mpkt = *pkt;
|
||||
|
||||
double diff = priv->rebase_ts - priv->base_ts;
|
||||
mpkt.pts = PTS_ADD(mpkt.pts, diff);
|
||||
mpkt.dts = PTS_ADD(mpkt.dts, diff);
|
||||
|
||||
rst->max_out_pts = MPMAX(rst->max_out_pts, pkt->pts);
|
||||
|
||||
AVPacket avpkt;
|
||||
mp_set_av_packet(&avpkt, &mpkt, &rst->av_stream->time_base);
|
||||
|
||||
avpkt.stream_index = rst->av_stream->index;
|
||||
|
||||
if (avpkt.duration < 0 && rst->sh->type != STREAM_SUB)
|
||||
avpkt.duration = 0;
|
||||
|
||||
AVPacket *new_packet = av_packet_clone(&avpkt);
|
||||
if (!new_packet) {
|
||||
MP_ERR(priv, "Failed to allocate packet.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (av_interleaved_write_frame(priv->mux, new_packet) < 0)
|
||||
MP_ERR(priv, "Failed writing packet.\n");
|
||||
}
|
||||
|
||||
// Write all packets that currently can be written.
|
||||
static void mux_packets(struct mp_recorder_sink *rst, bool force)
|
||||
{
|
||||
struct mp_recorder *priv = rst->owner;
|
||||
if (!priv->muxing || !rst->num_packets)
|
||||
return;
|
||||
|
||||
int safe_count = 0;
|
||||
for (int n = 0; n < rst->num_packets; n++) {
|
||||
if (rst->packets[n]->keyframe)
|
||||
safe_count = n;
|
||||
}
|
||||
if (force)
|
||||
safe_count = rst->num_packets;
|
||||
|
||||
for (int n = 0; n < safe_count; n++) {
|
||||
mux_packet(rst, rst->packets[n]);
|
||||
talloc_free(rst->packets[n]);
|
||||
}
|
||||
|
||||
// Remove packets[0..safe_count]
|
||||
memmove(&rst->packets[0], &rst->packets[safe_count],
|
||||
(rst->num_packets - safe_count) * sizeof(rst->packets[0]));
|
||||
rst->num_packets -= safe_count;
|
||||
}
|
||||
|
||||
// If there was a discontinuity, check whether we can resume muxing (and from
|
||||
// where).
|
||||
static void check_restart(struct mp_recorder *priv)
|
||||
{
|
||||
if (priv->muxing)
|
||||
return;
|
||||
|
||||
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 = MPMAX(rebase_ts, rst->max_out_pts);
|
||||
|
||||
if (rst->num_packets < min_packets) {
|
||||
if (!rst->proper_eof && rst->sh->type != STREAM_SUB)
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int i = 0; i < min_packets; i++)
|
||||
min_ts = MPMIN(min_ts, rst->packets[i]->pts);
|
||||
}
|
||||
|
||||
// Subtitle only stream (wait longer) or stream without any PTS (fuck it).
|
||||
if (!isfinite(min_ts))
|
||||
return;
|
||||
|
||||
priv->rebase_ts = rebase_ts;
|
||||
priv->base_ts = min_ts;
|
||||
|
||||
for (int n = 0; n < priv->num_streams; n++) {
|
||||
struct mp_recorder_sink *rst = priv->streams[n];
|
||||
rst->max_out_pts = min_ts;
|
||||
}
|
||||
|
||||
priv->muxing = true;
|
||||
|
||||
if (!priv->muxing_from_start)
|
||||
MP_WARN(priv, "Discontinuity at timestamp %f.\n", priv->rebase_ts);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (av_write_trailer(priv->mux) < 0)
|
||||
MP_ERR(priv, "Writing trailer failed.\n");
|
||||
}
|
||||
|
||||
if (priv->mux) {
|
||||
if (avio_closep(&priv->mux->pb) < 0)
|
||||
MP_ERR(priv, "Closing file failed\n");
|
||||
|
||||
avformat_free_context(priv->mux);
|
||||
}
|
||||
|
||||
flush_packets(priv);
|
||||
talloc_free(priv);
|
||||
}
|
||||
|
||||
// This is called on a seek, or when recording was started mid-stream.
|
||||
void mp_recorder_mark_discontinuity(struct mp_recorder *priv)
|
||||
{
|
||||
flush_packets(priv);
|
||||
|
||||
for (int n = 0; n < priv->num_streams; n++) {
|
||||
struct mp_recorder_sink *rst = priv->streams[n];
|
||||
rst->discont = true;
|
||||
rst->proper_eof = false;
|
||||
}
|
||||
|
||||
priv->muxing = false;
|
||||
priv->muxing_from_start = false;
|
||||
}
|
||||
|
||||
// Get a stream for writing. The pointer is valid until mp_recorder is
|
||||
// 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)
|
||||
{
|
||||
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
|
||||
// can create a new reference to it if it needs to retain it. Can be NULL to
|
||||
// signal proper end of stream.
|
||||
void mp_recorder_feed_packet(struct mp_recorder_sink *rst,
|
||||
struct demux_packet *pkt)
|
||||
{
|
||||
struct mp_recorder *priv = rst->owner;
|
||||
|
||||
if (!pkt) {
|
||||
rst->proper_eof = true;
|
||||
check_restart(priv);
|
||||
mux_packets(rst, false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pkt->dts == MP_NOPTS_VALUE && !priv->dts_warning) {
|
||||
// No, FFmpeg has no actually usable helpers to generate correct DTS.
|
||||
// No, FFmpeg doesn't tell us which formats need DTS at all.
|
||||
// No, we can not shut up the FFmpeg warning, which will follow.
|
||||
MP_WARN(priv, "Source stream misses DTS on at least some packets!\n"
|
||||
"If the target file format requires DTS, the written\n"
|
||||
"file will be invalid.\n");
|
||||
priv->dts_warning = true;
|
||||
}
|
||||
|
||||
if (rst->discont && !pkt->keyframe)
|
||||
return;
|
||||
rst->discont = false;
|
||||
|
||||
if (rst->num_packets >= QUEUE_MAX_PACKETS) {
|
||||
MP_ERR(priv, "Stream %d has too many queued packets; dropping.\n",
|
||||
rst->av_stream->index);
|
||||
return;
|
||||
}
|
||||
|
||||
pkt = demux_copy_packet(pkt);
|
||||
if (!pkt)
|
||||
return;
|
||||
MP_TARRAY_APPEND(rst, rst->packets, rst->num_packets, pkt);
|
||||
|
||||
check_restart(priv);
|
||||
mux_packets(rst, false);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#ifndef MP_RECORDER_H_
|
||||
#define MP_RECORDER_H_
|
||||
|
||||
struct mp_recorder;
|
||||
struct mpv_global;
|
||||
struct demux_packet;
|
||||
struct sh_stream;
|
||||
struct mp_recorder_sink;
|
||||
|
||||
struct mp_recorder *mp_recorder_create(struct mpv_global *global,
|
||||
const char *target_file,
|
||||
struct sh_stream **streams,
|
||||
int num_streams);
|
||||
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, int stream);
|
||||
void mp_recorder_feed_packet(struct mp_recorder_sink *s,
|
||||
struct demux_packet *pkt);
|
||||
|
||||
#endif
|
||||
+1
-1
@@ -70,7 +70,7 @@ static enum cue_command read_cmd(struct bstr *data, struct bstr *out_params)
|
||||
return CUE_EMPTY;
|
||||
for (int n = 0; cue_command_strings[n].command != -1; n++) {
|
||||
struct bstr name = bstr0(cue_command_strings[n].text);
|
||||
if (bstr_startswith(line, name)) {
|
||||
if (bstr_case_startswith(line, name)) {
|
||||
struct bstr rest = bstr_cut(line, name.len);
|
||||
if (rest.len && !strchr(WHITESPACE, rest.start[0]))
|
||||
continue;
|
||||
|
||||
+73
-68
@@ -205,6 +205,9 @@ struct demux_stream {
|
||||
struct demux_packet *head;
|
||||
struct demux_packet *tail;
|
||||
|
||||
struct demux_packet *attached_picture;
|
||||
bool attached_picture_added;
|
||||
|
||||
// for closed captions (demuxer_feed_caption)
|
||||
struct sh_stream *cc;
|
||||
};
|
||||
@@ -243,6 +246,7 @@ static void ds_flush(struct demux_stream *ds)
|
||||
ds->last_pos = -1;
|
||||
ds->last_dts = MP_NOPTS_VALUE;
|
||||
ds->correct_dts = ds->correct_pos = true;
|
||||
ds->attached_picture_added = false;
|
||||
}
|
||||
|
||||
void demux_set_ts_offset(struct demuxer *demuxer, double offset)
|
||||
@@ -291,6 +295,8 @@ void demux_add_sh_stream(struct demuxer *demuxer, struct sh_stream *sh)
|
||||
if (!sh->codec->codec)
|
||||
sh->codec->codec = "";
|
||||
|
||||
sh->ds->attached_picture = sh->attached_picture;
|
||||
|
||||
sh->index = in->num_streams;
|
||||
if (sh->ff_index < 0)
|
||||
sh->ff_index = sh->index;
|
||||
@@ -655,7 +661,9 @@ static bool read_packet(struct demux_internal *in)
|
||||
demux->desc->seek(demux, seek_pts, SEEK_BACKWARD | SEEK_HR);
|
||||
}
|
||||
|
||||
bool eof = !demux->desc->fill_buffer || demux->desc->fill_buffer(demux) <= 0;
|
||||
bool eof = true;
|
||||
if (demux->desc->fill_buffer && !demux_cancel_test(demux))
|
||||
eof = demux->desc->fill_buffer(demux) <= 0;
|
||||
update_cache(in);
|
||||
|
||||
pthread_mutex_lock(&in->lock);
|
||||
@@ -677,28 +685,6 @@ static bool read_packet(struct demux_internal *in)
|
||||
return true;
|
||||
}
|
||||
|
||||
// must be called locked; may temporarily unlock
|
||||
static void ds_get_packets(struct demux_stream *ds)
|
||||
{
|
||||
const char *t = stream_type_name(ds->type);
|
||||
struct demux_internal *in = ds->in;
|
||||
MP_DBG(in, "reading packet for %s\n", t);
|
||||
in->eof = false; // force retry
|
||||
while (ds->selected && !ds->head) {
|
||||
ds->active = true;
|
||||
// Note: the following code marks EOF if it can't continue
|
||||
if (in->threading) {
|
||||
MP_VERBOSE(in, "waiting for demux thread (%s)\n", t);
|
||||
pthread_cond_signal(&in->wakeup);
|
||||
pthread_cond_wait(&in->wakeup, &in->lock);
|
||||
} else {
|
||||
read_packet(in);
|
||||
}
|
||||
if (ds->eof)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void execute_trackswitch(struct demux_internal *in)
|
||||
{
|
||||
in->tracks_switched = false;
|
||||
@@ -777,6 +763,13 @@ static void *demux_thread(void *pctx)
|
||||
|
||||
static struct demux_packet *dequeue_packet(struct demux_stream *ds)
|
||||
{
|
||||
if (ds->attached_picture) {
|
||||
ds->eof = true;
|
||||
if (ds->attached_picture_added)
|
||||
return NULL;
|
||||
ds->attached_picture_added = true;
|
||||
return demux_copy_packet(ds->attached_picture);
|
||||
}
|
||||
if (!ds->head)
|
||||
return NULL;
|
||||
struct demux_packet *pkt = ds->head;
|
||||
@@ -820,16 +813,23 @@ static struct demux_packet *dequeue_packet(struct demux_stream *ds)
|
||||
return pkt;
|
||||
}
|
||||
|
||||
// Whether to avoid actively demuxing new packets to find a new packet on the
|
||||
// given stream.
|
||||
// Attached pictures (cover art) should never actively read.
|
||||
// Sparse packets (Subtitles) interleaved with other non-sparse packets (video,
|
||||
// audio) should never be read actively, meaning the demuxer thread does not
|
||||
// try to exceed default readahead in order to find a new packet.
|
||||
static bool use_lazy_subtitle_reading(struct demux_stream *ds)
|
||||
static bool use_lazy_packet_reading(struct demux_stream *ds)
|
||||
{
|
||||
if (ds->attached_picture)
|
||||
return true;
|
||||
if (ds->type != STREAM_SUB)
|
||||
return false;
|
||||
// Subtitles are only lazily read if there's at least 1 other actively read
|
||||
// stream.
|
||||
for (int n = 0; n < ds->in->num_streams; n++) {
|
||||
struct demux_stream *s = ds->in->streams[n]->ds;
|
||||
if (s->type != STREAM_SUB && s->selected && !s->eof)
|
||||
if (s->type != STREAM_SUB && s->selected && !s->eof && !s->attached_picture)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -843,12 +843,29 @@ struct demux_packet *demux_read_packet(struct sh_stream *sh)
|
||||
struct demux_stream *ds = sh ? sh->ds : NULL;
|
||||
struct demux_packet *pkt = NULL;
|
||||
if (ds) {
|
||||
pthread_mutex_lock(&ds->in->lock);
|
||||
if (!use_lazy_subtitle_reading(ds))
|
||||
ds_get_packets(ds);
|
||||
struct demux_internal *in = ds->in;
|
||||
pthread_mutex_lock(&in->lock);
|
||||
if (!use_lazy_packet_reading(ds)) {
|
||||
const char *t = stream_type_name(ds->type);
|
||||
MP_DBG(in, "reading packet for %s\n", t);
|
||||
in->eof = false; // force retry
|
||||
while (ds->selected && !ds->head) {
|
||||
ds->active = true;
|
||||
// Note: the following code marks EOF if it can't continue
|
||||
if (in->threading) {
|
||||
MP_VERBOSE(in, "waiting for demux thread (%s)\n", t);
|
||||
pthread_cond_signal(&in->wakeup);
|
||||
pthread_cond_wait(&in->wakeup, &in->lock);
|
||||
} else {
|
||||
read_packet(in);
|
||||
}
|
||||
if (ds->eof)
|
||||
break;
|
||||
}
|
||||
}
|
||||
pkt = dequeue_packet(ds);
|
||||
pthread_cond_signal(&ds->in->wakeup); // possibly read more
|
||||
pthread_mutex_unlock(&ds->in->lock);
|
||||
pthread_cond_signal(&in->wakeup); // possibly read more
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
}
|
||||
return pkt;
|
||||
}
|
||||
@@ -874,7 +891,7 @@ int demux_read_packet_async(struct sh_stream *sh, struct demux_packet **out_pkt)
|
||||
if (ds->in->threading) {
|
||||
pthread_mutex_lock(&ds->in->lock);
|
||||
*out_pkt = dequeue_packet(ds);
|
||||
if (use_lazy_subtitle_reading(ds)) {
|
||||
if (use_lazy_packet_reading(ds)) {
|
||||
r = *out_pkt ? 1 : -1;
|
||||
} else {
|
||||
r = *out_pkt ? 1 : ((ds->eof || !ds->selected) ? -1 : 0);
|
||||
@@ -1231,8 +1248,7 @@ static struct demuxer *open_given_type(struct mpv_global *global,
|
||||
.events = DEMUX_EVENT_ALL,
|
||||
};
|
||||
demuxer->seekable = stream->seekable;
|
||||
if (demuxer->stream->uncached_stream &&
|
||||
!demuxer->stream->uncached_stream->seekable)
|
||||
if (demuxer->stream->underlying && !demuxer->stream->underlying->seekable)
|
||||
demuxer->seekable = false;
|
||||
|
||||
struct demux_internal *in = demuxer->in = talloc_ptrtype(demuxer, in);
|
||||
@@ -1249,9 +1265,6 @@ static struct demuxer *open_given_type(struct mpv_global *global,
|
||||
pthread_mutex_init(&in->lock, NULL);
|
||||
pthread_cond_init(&in->wakeup, NULL);
|
||||
|
||||
if (stream->uncached_stream)
|
||||
in->min_secs = MPMAX(in->min_secs, opts->min_secs_cache);
|
||||
|
||||
*in->d_thread = *demuxer;
|
||||
*in->d_buffer = *demuxer;
|
||||
|
||||
@@ -1290,7 +1303,8 @@ static struct demuxer *open_given_type(struct mpv_global *global,
|
||||
demux_init_cache(demuxer);
|
||||
demux_changed(in->d_thread, DEMUX_EVENT_ALL);
|
||||
demux_update(demuxer);
|
||||
stream_control(demuxer->stream, STREAM_CTRL_SET_READAHEAD, &(int){false});
|
||||
stream_control(demuxer->stream, STREAM_CTRL_SET_READAHEAD,
|
||||
&(int){params ? params->initial_readahead : false});
|
||||
if (!(params && params->disable_timeline)) {
|
||||
struct timeline *tl = timeline_load(global, log, demuxer);
|
||||
if (tl) {
|
||||
@@ -1299,11 +1313,15 @@ static struct demuxer *open_given_type(struct mpv_global *global,
|
||||
struct demuxer *sub =
|
||||
open_given_type(global, log, &demuxer_desc_timeline, stream,
|
||||
¶ms2, DEMUX_CHECK_FORCE);
|
||||
if (sub)
|
||||
return sub;
|
||||
timeline_destroy(tl);
|
||||
if (sub) {
|
||||
demuxer = sub;
|
||||
} else {
|
||||
timeline_destroy(tl);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (demuxer->is_network || stream->caching)
|
||||
in->min_secs = MPMAX(in->min_secs, opts->min_secs_cache);
|
||||
return demuxer;
|
||||
}
|
||||
|
||||
@@ -1382,12 +1400,6 @@ struct demuxer *demux_open_url(const char *url,
|
||||
cancel, global);
|
||||
if (!s)
|
||||
return NULL;
|
||||
if (params->allow_capture) {
|
||||
char *f;
|
||||
mp_read_option_raw(global, "stream-capture", &m_option_type_string, &f);
|
||||
stream_set_capture_file(s, f);
|
||||
talloc_free(f);
|
||||
}
|
||||
if (!params->disable_cache)
|
||||
stream_enable_cache_defaults(&s);
|
||||
struct demuxer *d = demux_open(s, params, global);
|
||||
@@ -1724,6 +1736,7 @@ static void thread_demux_control(void *p)
|
||||
int demux_control(demuxer_t *demuxer, int cmd, void *arg)
|
||||
{
|
||||
struct demux_internal *in = demuxer->in;
|
||||
assert(demuxer == in->d_user);
|
||||
|
||||
if (in->threading) {
|
||||
pthread_mutex_lock(&in->lock);
|
||||
@@ -1735,7 +1748,20 @@ int demux_control(demuxer_t *demuxer, int cmd, void *arg)
|
||||
|
||||
int r = 0;
|
||||
struct demux_control_args args = {demuxer, cmd, arg, &r};
|
||||
demux_run_on_thread(demuxer, thread_demux_control, &args);
|
||||
if (in->threading) {
|
||||
MP_VERBOSE(in, "blocking on demuxer thread\n");
|
||||
pthread_mutex_lock(&in->lock);
|
||||
while (in->run_fn)
|
||||
pthread_cond_wait(&in->wakeup, &in->lock);
|
||||
in->run_fn = thread_demux_control;
|
||||
in->run_fn_arg = &args;
|
||||
pthread_cond_signal(&in->wakeup);
|
||||
while (in->run_fn)
|
||||
pthread_cond_wait(&in->wakeup, &in->lock);
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
} else {
|
||||
thread_demux_control(&args);
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
@@ -1747,27 +1773,6 @@ int demux_stream_control(demuxer_t *demuxer, int ctrl, void *arg)
|
||||
return c.res;
|
||||
}
|
||||
|
||||
void demux_run_on_thread(struct demuxer *demuxer, void (*fn)(void *), void *ctx)
|
||||
{
|
||||
struct demux_internal *in = demuxer->in;
|
||||
assert(demuxer == in->d_user);
|
||||
|
||||
if (in->threading) {
|
||||
MP_VERBOSE(in, "blocking on demuxer thread\n");
|
||||
pthread_mutex_lock(&in->lock);
|
||||
while (in->run_fn)
|
||||
pthread_cond_wait(&in->wakeup, &in->lock);
|
||||
in->run_fn = fn;
|
||||
in->run_fn_arg = ctx;
|
||||
pthread_cond_signal(&in->wakeup);
|
||||
while (in->run_fn)
|
||||
pthread_cond_wait(&in->wakeup, &in->lock);
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
} else {
|
||||
fn(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
bool demux_cancel_test(struct demuxer *demuxer)
|
||||
{
|
||||
return mp_cancel_test(demuxer->stream->cancel);
|
||||
|
||||
+3
-3
@@ -162,9 +162,11 @@ struct demuxer_params {
|
||||
bool *matroska_was_valid;
|
||||
struct timeline *timeline;
|
||||
bool disable_timeline;
|
||||
bool initial_readahead;
|
||||
bstr init_fragment;
|
||||
bool skip_lavf_probing;
|
||||
// -- demux_open_url() only
|
||||
int stream_flags;
|
||||
bool allow_capture;
|
||||
bool disable_cache;
|
||||
// result
|
||||
bool demuxer_failed;
|
||||
@@ -287,8 +289,6 @@ double demuxer_get_time_length(struct demuxer *demuxer);
|
||||
|
||||
int demux_stream_control(demuxer_t *demuxer, int ctrl, void *arg);
|
||||
|
||||
void demux_run_on_thread(struct demuxer *demuxer, void (*fn)(void *), void *ctx);
|
||||
|
||||
void demux_changed(demuxer_t *demuxer, int events);
|
||||
void demux_update(demuxer_t *demuxer);
|
||||
|
||||
|
||||
+22
-6
@@ -43,6 +43,8 @@ struct priv {
|
||||
double base_dts; // packet DTS that maps to base_time
|
||||
double last_dts; // DTS of previously demuxed packet
|
||||
bool seek_reinit; // needs reinit after seek
|
||||
|
||||
bool is_dvd, is_cdda;
|
||||
};
|
||||
|
||||
// If the timestamp difference between subsequent packets is this big, assume
|
||||
@@ -65,7 +67,7 @@ static void reselect_streams(demuxer_t *demuxer)
|
||||
static void get_disc_lang(struct stream *stream, struct sh_stream *sh)
|
||||
{
|
||||
struct stream_lang_req req = {.type = sh->type, .id = sh->demuxer_id};
|
||||
if (stream->uncached_type == STREAMTYPE_DVD && sh->type == STREAM_SUB)
|
||||
if (sh->type == STREAM_SUB)
|
||||
req.id = req.id & 0x1F; // mpeg ID to index
|
||||
stream_control(stream, STREAM_CTRL_GET_LANG, &req);
|
||||
if (req.name[0])
|
||||
@@ -76,7 +78,7 @@ static void add_dvd_streams(demuxer_t *demuxer)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
struct stream *stream = demuxer->stream;
|
||||
if (stream->uncached_type != STREAMTYPE_DVD)
|
||||
if (!p->is_dvd)
|
||||
return;
|
||||
struct stream_dvd_info_req info;
|
||||
if (stream_control(stream, STREAM_CTRL_GET_DVD_INFO, &info) > 0) {
|
||||
@@ -162,7 +164,7 @@ static void d_seek(demuxer_t *demuxer, double seek_pts, int flags)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
|
||||
if (demuxer->stream->uncached_type == STREAMTYPE_CDDA) {
|
||||
if (p->is_cdda) {
|
||||
demux_seek(p->slave, seek_pts, flags);
|
||||
return;
|
||||
}
|
||||
@@ -222,7 +224,7 @@ static int d_fill_buffer(demuxer_t *demuxer)
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (demuxer->stream->uncached_type == STREAMTYPE_CDDA) {
|
||||
if (p->is_cdda) {
|
||||
demux_add_packet(sh, pkt);
|
||||
return 1;
|
||||
}
|
||||
@@ -285,7 +287,21 @@ static int d_open(demuxer_t *demuxer, enum demux_check check)
|
||||
|
||||
struct demuxer_params params = {.force_format = "+lavf"};
|
||||
|
||||
if (demuxer->stream->uncached_type == STREAMTYPE_CDDA)
|
||||
struct stream *cur = demuxer->stream;
|
||||
const char *sname = "";
|
||||
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 ||
|
||||
strcmp(sname, "ifo") == 0 ||
|
||||
strcmp(sname, "dvdnav") == 0 ||
|
||||
strcmp(sname, "ifo_dvdnav") == 0;
|
||||
|
||||
if (p->is_cdda)
|
||||
params.force_format = "+rawaudio";
|
||||
|
||||
char *t = NULL;
|
||||
@@ -311,7 +327,7 @@ static int d_open(demuxer_t *demuxer, enum demux_check check)
|
||||
// (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 (demuxer->stream->uncached_type != STREAMTYPE_CDDA) {
|
||||
if (!p->is_cdda) {
|
||||
demuxer->stream->seekable = false;
|
||||
|
||||
// Can be seekable even if the stream isn't.
|
||||
|
||||
+115
-39
@@ -44,12 +44,15 @@ struct tl_part {
|
||||
};
|
||||
|
||||
struct tl_parts {
|
||||
bool dash;
|
||||
char *init_fragment_url;
|
||||
struct tl_part *parts;
|
||||
int num_parts;
|
||||
};
|
||||
|
||||
struct priv {
|
||||
bstr data;
|
||||
bool allow_any;
|
||||
};
|
||||
|
||||
// Parse a time (absolute file time or duration). Currently equivalent to a
|
||||
@@ -64,6 +67,8 @@ static bool parse_time(bstr str, double *out_time)
|
||||
return true;
|
||||
}
|
||||
|
||||
#define MAX_PARAMS 10
|
||||
|
||||
/* Returns a list of parts, or NULL on parse error.
|
||||
* Syntax (without file header or URI prefix):
|
||||
* url ::= <entry> ( (';' | '\n') <entry> )*
|
||||
@@ -78,7 +83,10 @@ static struct tl_parts *parse_edl(bstr str)
|
||||
bstr_split_tok(str, "\n", &(bstr){0}, &str);
|
||||
if (bstr_eatstart0(&str, "\n") || bstr_eatstart0(&str, ";"))
|
||||
continue;
|
||||
bool is_header = bstr_eatstart0(&str, "!");
|
||||
struct tl_part p = { .length = -1 };
|
||||
bstr param_names[MAX_PARAMS];
|
||||
bstr param_vals[MAX_PARAMS];
|
||||
int nparam = 0;
|
||||
while (1) {
|
||||
bstr name, val;
|
||||
@@ -116,10 +124,25 @@ static struct tl_parts *parse_edl(bstr str)
|
||||
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 (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 (bstr_equals0(param_names[1], "init"))
|
||||
tl->init_fragment_url = bstrto0(tl, param_vals[1]);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!p.filename)
|
||||
goto error;
|
||||
MP_TARRAY_APPEND(tl, tl->parts, tl->num_parts, p);
|
||||
@@ -139,7 +162,10 @@ static struct demuxer *open_source(struct timeline *tl, char *filename)
|
||||
if (strcmp(d->stream->url, filename) == 0)
|
||||
return d;
|
||||
}
|
||||
struct demuxer *d = demux_open_url(filename, NULL, tl->cancel, tl->global);
|
||||
struct demuxer_params params = {
|
||||
.init_fragment = tl->init_fragment,
|
||||
};
|
||||
struct demuxer *d = demux_open_url(filename, ¶ms, tl->cancel, tl->global);
|
||||
if (d) {
|
||||
MP_TARRAY_APPEND(tl, tl->sources, tl->num_sources, d);
|
||||
} else {
|
||||
@@ -202,61 +228,110 @@ static void resolve_timestamps(struct tl_part *part, struct demuxer *demuxer)
|
||||
|
||||
static void build_timeline(struct timeline *tl, struct tl_parts *parts)
|
||||
{
|
||||
tl->track_layout = NULL;
|
||||
tl->dash = parts->dash;
|
||||
|
||||
if (parts->init_fragment_url && parts->init_fragment_url[0]) {
|
||||
MP_VERBOSE(tl, "Opening init fragment...\n");
|
||||
stream_t *s = stream_create(parts->init_fragment_url, STREAM_READ,
|
||||
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(tl, "Could not read init fragment.\n");
|
||||
goto error;
|
||||
}
|
||||
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) {
|
||||
free_stream(s);
|
||||
MP_ERR(tl, "Could not demux init fragment.\n");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
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];
|
||||
struct demuxer *source = open_source(tl, part->filename);
|
||||
if (!source)
|
||||
goto error;
|
||||
struct demuxer *source = NULL;
|
||||
|
||||
resolve_timestamps(part, source);
|
||||
if (tl->dash) {
|
||||
part->offset = starttime;
|
||||
if (part->length <= 0)
|
||||
MP_WARN(tl, "Segment %d has unknown duration.\n", n);
|
||||
if (part->offset_set)
|
||||
MP_WARN(tl, "Offsets are ignored.\n");
|
||||
tl->demuxer->is_network = true;
|
||||
|
||||
double end_time = source_get_length(source);
|
||||
if (end_time >= 0)
|
||||
end_time += source->start_time;
|
||||
|
||||
// Unknown length => use rest of the file. If duration is unknown, make
|
||||
// something up.
|
||||
if (part->length < 0) {
|
||||
if (end_time < 0) {
|
||||
MP_WARN(tl, "EDL: source file '%s' has unknown duration.\n",
|
||||
part->filename);
|
||||
end_time = 1;
|
||||
if (!tl->track_layout) {
|
||||
source = open_source(tl, part->filename);
|
||||
if (!source)
|
||||
goto error;
|
||||
}
|
||||
part->length = end_time - part->offset;
|
||||
} else if (end_time >= 0) {
|
||||
double end_part = part->offset + part->length;
|
||||
if (end_part > end_time) {
|
||||
MP_WARN(tl, "EDL: entry %d uses %f "
|
||||
"seconds, but file has only %f seconds.\n",
|
||||
n, end_part, end_time);
|
||||
} else {
|
||||
MP_VERBOSE(tl, "Opening segment %d...\n", n);
|
||||
|
||||
source = open_source(tl, part->filename);
|
||||
if (!source)
|
||||
goto error;
|
||||
|
||||
resolve_timestamps(part, source);
|
||||
|
||||
double end_time = source_get_length(source);
|
||||
if (end_time >= 0)
|
||||
end_time += source->start_time;
|
||||
|
||||
// Unknown length => use rest of the file. If duration is unknown, make
|
||||
// something up.
|
||||
if (part->length < 0) {
|
||||
if (end_time < 0) {
|
||||
MP_WARN(tl, "EDL: source file '%s' has unknown duration.\n",
|
||||
part->filename);
|
||||
end_time = 1;
|
||||
}
|
||||
part->length = end_time - part->offset;
|
||||
} else if (end_time >= 0) {
|
||||
double end_part = part->offset + part->length;
|
||||
if (end_part > end_time) {
|
||||
MP_WARN(tl, "EDL: entry %d uses %f "
|
||||
"seconds, but file has only %f seconds.\n",
|
||||
n, end_part, end_time);
|
||||
}
|
||||
}
|
||||
|
||||
// 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(&tl->chapters, &tl->num_chapters, source, part->offset,
|
||||
part->length, starttime);
|
||||
}
|
||||
|
||||
// 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(&tl->chapters, &tl->num_chapters, source, part->offset,
|
||||
part->length, starttime);
|
||||
|
||||
tl->parts[n] = (struct timeline_part) {
|
||||
.start = starttime,
|
||||
.source_start = part->offset,
|
||||
.source = source,
|
||||
.url = talloc_strdup(tl, part->filename),
|
||||
};
|
||||
|
||||
starttime += part->length;
|
||||
|
||||
if (source) {
|
||||
tl->demuxer->is_network |= source->is_network;
|
||||
|
||||
if (!tl->track_layout)
|
||||
tl->track_layout = source;
|
||||
}
|
||||
}
|
||||
tl->parts[parts->num_parts] = (struct timeline_part) {.start = starttime};
|
||||
tl->num_parts = parts->num_parts;
|
||||
tl->track_layout = tl->parts[0].source;
|
||||
return;
|
||||
|
||||
error:
|
||||
@@ -286,8 +361,7 @@ static void build_mpv_edl_timeline(struct timeline *tl)
|
||||
return;
|
||||
}
|
||||
MP_TARRAY_APPEND(tl, tl->sources, tl->num_sources, tl->demuxer);
|
||||
// Source is .edl and not edl:// => don't allow arbitrary paths
|
||||
if (tl->demuxer->stream->uncached_type != STREAMTYPE_EDL)
|
||||
if (!p->allow_any)
|
||||
fix_filenames(parts, tl->demuxer->filename);
|
||||
build_timeline(tl, parts);
|
||||
talloc_free(parts);
|
||||
@@ -303,8 +377,10 @@ static int try_open_file(struct demuxer *demuxer, enum demux_check check)
|
||||
demuxer->fully_read = true;
|
||||
|
||||
struct stream *s = demuxer->stream;
|
||||
if (s->uncached_type == STREAMTYPE_EDL) {
|
||||
if (s->info && strcmp(s->info->name, "edl") == 0) {
|
||||
p->data = bstr0(s->path);
|
||||
// Source is edl:// and not .edl => allow arbitrary paths
|
||||
p->allow_any = true;
|
||||
return 0;
|
||||
}
|
||||
if (check >= DEMUX_CHECK_UNSAFE) {
|
||||
|
||||
+39
-15
@@ -183,6 +183,8 @@ typedef struct lavf_priv {
|
||||
AVInputFormat *avif;
|
||||
int avif_flags;
|
||||
AVFormatContext *avfc;
|
||||
bstr init_fragment;
|
||||
int64_t stream_pos;
|
||||
AVIOContext *pb;
|
||||
struct sh_stream **streams; // NULL for unknown streams
|
||||
int num_streams;
|
||||
@@ -218,7 +220,14 @@ static int mp_read(void *opaque, uint8_t *buf, int size)
|
||||
struct stream *stream = priv->stream;
|
||||
int ret;
|
||||
|
||||
ret = stream_read(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(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);
|
||||
@@ -230,32 +239,44 @@ static int64_t mp_seek(void *opaque, int64_t pos, int whence)
|
||||
struct demuxer *demuxer = opaque;
|
||||
lavf_priv_t *priv = demuxer->priv;
|
||||
struct stream *stream = priv->stream;
|
||||
int64_t current_pos;
|
||||
|
||||
MP_TRACE(demuxer, "mp_seek(%p, %"PRId64", %s)\n", stream, pos,
|
||||
whence == SEEK_END ? "end" :
|
||||
whence == SEEK_CUR ? "cur" :
|
||||
whence == SEEK_SET ? "set" : "size");
|
||||
if (whence == SEEK_END || whence == AVSEEK_SIZE) {
|
||||
int64_t end = stream_get_size(stream);
|
||||
int64_t end = stream_get_size(stream) + priv->init_fragment.len;
|
||||
if (end < 0)
|
||||
return -1;
|
||||
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;
|
||||
}
|
||||
|
||||
if (pos < 0)
|
||||
return -1;
|
||||
current_pos = stream_tell(stream);
|
||||
if (stream_seek(stream, pos) == 0) {
|
||||
|
||||
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, 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;
|
||||
}
|
||||
|
||||
@@ -338,7 +359,7 @@ static int lavf_check_file(demuxer_t *demuxer, enum demux_check check)
|
||||
priv->filename = remove_prefix(s->url, prefixes);
|
||||
|
||||
char *avdevice_format = NULL;
|
||||
if (s->uncached_type == STREAMTYPE_AVDEVICE) {
|
||||
if (s->info && strcmp(s->info->name, "avdevice") == 0) {
|
||||
// always require filename in the form "format:filename"
|
||||
char *sep = strchr(priv->filename, ':');
|
||||
if (!sep) {
|
||||
@@ -376,7 +397,7 @@ static int lavf_check_file(demuxer_t *demuxer, enum demux_check check)
|
||||
// Disable file-extension matching with normal checks
|
||||
.filename = check <= DEMUX_CHECK_REQUEST ? priv->filename : "",
|
||||
.buf_size = 0,
|
||||
.buf = av_mallocz(PROBE_BUF_SIZE + FF_INPUT_BUFFER_PADDING_SIZE),
|
||||
.buf = av_mallocz(PROBE_BUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE),
|
||||
};
|
||||
if (!avpd.buf)
|
||||
return -1;
|
||||
@@ -771,6 +792,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;
|
||||
@@ -808,10 +832,7 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
|
||||
AVDictionary *dopts = NULL;
|
||||
|
||||
if ((priv->avif_flags & AVFMT_NOFILE) ||
|
||||
priv->stream->type == STREAMTYPE_AVDEVICE ||
|
||||
priv->format_hack.no_stream)
|
||||
{
|
||||
if ((priv->avif_flags & AVFMT_NOFILE) || priv->format_hack.no_stream) {
|
||||
mp_setup_av_network_options(&dopts, demuxer->global, demuxer->log);
|
||||
// This might be incorrect.
|
||||
demuxer->seekable = true;
|
||||
@@ -867,9 +888,12 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
av_dict_free(&dopts);
|
||||
|
||||
priv->avfc = avfc;
|
||||
if (avformat_find_stream_info(avfc, NULL) < 0) {
|
||||
MP_ERR(demuxer, "av_find_stream_info() failed\n");
|
||||
return -1;
|
||||
|
||||
if (!demuxer->params || !demuxer->params->skip_lavf_probing) {
|
||||
if (avformat_find_stream_info(avfc, NULL) < 0) {
|
||||
MP_ERR(demuxer, "av_find_stream_info() failed\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
MP_VERBOSE(demuxer, "avformat_find_stream_info() finished after %"PRId64
|
||||
|
||||
+3
-2
@@ -294,9 +294,10 @@ static int demux_open_mf(demuxer_t *demuxer, enum demux_check check)
|
||||
mf_t *mf;
|
||||
|
||||
if (strncmp(demuxer->stream->url, "mf://", 5) == 0 &&
|
||||
demuxer->stream->type == STREAMTYPE_MF)
|
||||
demuxer->stream->info && strcmp(demuxer->stream->info->name, "mf") == 0)
|
||||
{
|
||||
mf = open_mf_pattern(demuxer, demuxer->log, demuxer->stream->url + 5);
|
||||
else {
|
||||
} else {
|
||||
mf = open_mf_single(demuxer, demuxer->log, demuxer->stream->url);
|
||||
int bog = 0;
|
||||
MP_TARRAY_APPEND(mf, mf->streams, bog, demuxer->stream);
|
||||
|
||||
+28
-2
@@ -162,6 +162,7 @@ struct block_info {
|
||||
bstr data;
|
||||
void *alloc;
|
||||
int64_t filepos;
|
||||
struct ebml_block_additions *additions;
|
||||
};
|
||||
|
||||
typedef struct mkv_demuxer {
|
||||
@@ -2392,6 +2393,8 @@ static void free_block(struct block_info *block)
|
||||
free(block->alloc);
|
||||
block->alloc = NULL;
|
||||
block->data = (bstr){0};
|
||||
talloc_free(block->additions);
|
||||
block->additions = NULL;
|
||||
}
|
||||
|
||||
static void index_block(demuxer_t *demuxer, struct block_info *block)
|
||||
@@ -2552,6 +2555,15 @@ static int handle_block(demuxer_t *demuxer, struct block_info *block_info)
|
||||
block_info->discardpadding / 1e9 * srate);
|
||||
mkv_d->a_skip_preroll = 0;
|
||||
}
|
||||
if (block_info->additions) {
|
||||
for (int n = 0; n < block_info->additions->n_block_more; n++) {
|
||||
struct ebml_block_more *add =
|
||||
&block_info->additions->block_more[n];
|
||||
int64_t id = add->n_block_add_id ? add->block_add_id : 1;
|
||||
demux_packet_add_blockadditional(dp, id,
|
||||
add->block_additional.start, add->block_additional.len);
|
||||
}
|
||||
}
|
||||
|
||||
mkv_parse_and_add_packet(demuxer, track, dp);
|
||||
talloc_free_children(track->parser_tmp);
|
||||
@@ -2604,8 +2616,22 @@ static int read_block_group(demuxer_t *demuxer, int64_t end,
|
||||
int64_t num = ebml_read_int(s);
|
||||
if (num == EBML_INT_INVALID)
|
||||
goto error;
|
||||
if (num)
|
||||
block->keyframe = false;
|
||||
block->keyframe = false;
|
||||
break;
|
||||
|
||||
case MATROSKA_ID_BLOCKADDITIONS:;
|
||||
struct ebml_block_additions additions = {0};
|
||||
struct ebml_parse_ctx parse_ctx = {demuxer->log};
|
||||
if (ebml_read_element(s, &parse_ctx, &additions,
|
||||
&ebml_block_additions_desc) < 0)
|
||||
return -1;
|
||||
if (additions.n_block_more > 0) {
|
||||
block->additions =
|
||||
talloc_memdup(NULL, &additions, sizeof(additions));
|
||||
talloc_steal(block->additions, parse_ctx.talloc_ctx);
|
||||
parse_ctx.talloc_ctx = NULL;
|
||||
}
|
||||
talloc_free(parse_ctx.talloc_ctx);
|
||||
break;
|
||||
|
||||
case MATROSKA_ID_CLUSTER:
|
||||
|
||||
@@ -267,7 +267,7 @@ static void find_ordered_chapter_sources(struct tl_ctx *ctx)
|
||||
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);
|
||||
} else if (ctx->demuxer->stream->uncached_type != STREAMTYPE_FILE) {
|
||||
} else if (!ctx->demuxer->stream->is_local_file) {
|
||||
MP_WARN(ctx, "Playback source is not a "
|
||||
"normal disk file. Will not search for related files.\n");
|
||||
} else {
|
||||
|
||||
@@ -277,7 +277,7 @@ static int cmp_filename(const void *a, const void *b)
|
||||
|
||||
static int parse_dir(struct pl_parser *p)
|
||||
{
|
||||
if (p->real_stream->type != STREAMTYPE_DIR)
|
||||
if (!p->real_stream->is_directory)
|
||||
return -1;
|
||||
if (p->probing)
|
||||
return 0;
|
||||
|
||||
+122
-58
@@ -24,11 +24,14 @@
|
||||
#include "demux.h"
|
||||
#include "timeline.h"
|
||||
#include "stheader.h"
|
||||
#include "stream/stream.h"
|
||||
|
||||
struct segment {
|
||||
int index;
|
||||
double start, end;
|
||||
double d_start;
|
||||
char *url;
|
||||
bool lazy;
|
||||
struct demuxer *d;
|
||||
// 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
|
||||
@@ -51,6 +54,7 @@ struct priv {
|
||||
struct timeline *tl;
|
||||
|
||||
double duration;
|
||||
bool dash;
|
||||
|
||||
struct segment **segments;
|
||||
int num_segments;
|
||||
@@ -65,6 +69,50 @@ struct priv {
|
||||
int eos_packets;
|
||||
};
|
||||
|
||||
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] == target_index)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create mapping from segment streams to virtual timeline streams.
|
||||
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);
|
||||
// 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 ? other->index : -1);
|
||||
|
||||
counts[sh->type] += 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void reselect_streams(struct demuxer *demuxer)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
@@ -77,6 +125,9 @@ static void reselect_streams(struct demuxer *demuxer)
|
||||
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;
|
||||
|
||||
struct sh_stream *sh = demux_get_stream(seg->d, i);
|
||||
bool selected = false;
|
||||
if (seg->stream_map[i] >= 0)
|
||||
@@ -89,8 +140,42 @@ static void reselect_streams(struct demuxer *demuxer)
|
||||
}
|
||||
}
|
||||
|
||||
static void close_lazy_segments(struct demuxer *demuxer)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
|
||||
// unload previous segment
|
||||
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 priv *p = demuxer->priv;
|
||||
|
||||
if (p->current->d)
|
||||
return;
|
||||
|
||||
close_lazy_segments(demuxer);
|
||||
|
||||
struct demuxer_params params = {
|
||||
.init_fragment = p->tl->init_fragment,
|
||||
.skip_lavf_probing = true,
|
||||
};
|
||||
p->current->d = demux_open_url(p->current->url, ¶ms,
|
||||
demuxer->stream->cancel, demuxer->global);
|
||||
if (!p->current->d && !demux_cancel_test(demuxer))
|
||||
MP_ERR(demuxer, "failed to load segment\n");
|
||||
associate_streams(demuxer, p->current);
|
||||
}
|
||||
|
||||
static void switch_segment(struct demuxer *demuxer, struct segment *new,
|
||||
double start_pts, int flags)
|
||||
double start_pts, int flags, bool init)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
|
||||
@@ -100,9 +185,14 @@ static void switch_segment(struct demuxer *demuxer, struct segment *new,
|
||||
MP_VERBOSE(demuxer, "switch to segment %d\n", new->index);
|
||||
|
||||
p->current = new;
|
||||
reopen_lazy_segments(demuxer);
|
||||
if (!new->d)
|
||||
return;
|
||||
reselect_streams(demuxer);
|
||||
demux_set_ts_offset(new->d, new->start - new->d_start);
|
||||
demux_seek(new->d, start_pts, flags);
|
||||
if (!p->dash)
|
||||
demux_set_ts_offset(new->d, new->start - new->d_start);
|
||||
if (!p->dash || !init)
|
||||
demux_seek(new->d, start_pts, flags);
|
||||
|
||||
for (int n = 0; n < p->num_streams; n++) {
|
||||
struct virtual_stream *vs = &p->streams[n];
|
||||
@@ -129,7 +219,7 @@ static void d_seek(struct demuxer *demuxer, double seek_pts, int flags)
|
||||
}
|
||||
}
|
||||
|
||||
switch_segment(demuxer, new, pts, flags);
|
||||
switch_segment(demuxer, new, pts, flags, false);
|
||||
}
|
||||
|
||||
static int d_fill_buffer(struct demuxer *demuxer)
|
||||
@@ -137,9 +227,11 @@ static int d_fill_buffer(struct demuxer *demuxer)
|
||||
struct priv *p = demuxer->priv;
|
||||
|
||||
if (!p->current)
|
||||
switch_segment(demuxer, p->segments[0], 0, 0);
|
||||
switch_segment(demuxer, p->segments[0], 0, 0, true);
|
||||
|
||||
struct segment *seg = p->current;
|
||||
if (!seg || !seg->d)
|
||||
return 0;
|
||||
|
||||
struct demux_packet *pkt = demux_read_any_packet(seg->d);
|
||||
if (!pkt || pkt->pts >= seg->end)
|
||||
@@ -176,20 +268,21 @@ static int d_fill_buffer(struct demuxer *demuxer)
|
||||
}
|
||||
if (!next)
|
||||
return 0;
|
||||
switch_segment(demuxer, next, next->start, 0);
|
||||
switch_segment(demuxer, next, next->start, 0, true);
|
||||
return 1; // reader will retry
|
||||
}
|
||||
|
||||
if (pkt->stream < 0 || pkt->stream > seg->num_stream_map)
|
||||
goto drop;
|
||||
|
||||
if (!pkt->codec)
|
||||
pkt->codec = demux_get_stream(seg->d, pkt->stream)->codec;
|
||||
|
||||
if (pkt->start == MP_NOPTS_VALUE || pkt->start < seg->start)
|
||||
pkt->start = seg->start;
|
||||
if (pkt->end == MP_NOPTS_VALUE || pkt->end > seg->end)
|
||||
pkt->end = seg->end;
|
||||
if (!p->dash) {
|
||||
if (!pkt->codec)
|
||||
pkt->codec = demux_get_stream(seg->d, pkt->stream)->codec;
|
||||
if (pkt->start == MP_NOPTS_VALUE || pkt->start < seg->start)
|
||||
pkt->start = seg->start;
|
||||
if (pkt->end == MP_NOPTS_VALUE || pkt->end > seg->end)
|
||||
pkt->end = seg->end;
|
||||
}
|
||||
|
||||
pkt->stream = seg->stream_map[pkt->stream];
|
||||
if (pkt->stream < 0)
|
||||
@@ -214,7 +307,8 @@ static int d_fill_buffer(struct demuxer *demuxer)
|
||||
}
|
||||
}
|
||||
|
||||
pkt->new_segment |= vs->new_segment;
|
||||
if (!p->dash)
|
||||
pkt->new_segment |= vs->new_segment;
|
||||
vs->new_segment = false;
|
||||
|
||||
demux_add_packet(vs->sh, pkt);
|
||||
@@ -232,9 +326,9 @@ static void print_timeline(struct demuxer *demuxer)
|
||||
MP_VERBOSE(demuxer, "Timeline segments:\n");
|
||||
for (int n = 0; n < p->num_segments; n++) {
|
||||
struct segment *seg = p->segments[n];
|
||||
int src_num = -1;
|
||||
for (int i = 0; i < p->tl->num_sources; i++) {
|
||||
if (p->tl->sources[i] == seg->d) {
|
||||
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;
|
||||
}
|
||||
@@ -242,50 +336,12 @@ static void print_timeline(struct demuxer *demuxer)
|
||||
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->d->filename);
|
||||
MP_VERBOSE(demuxer, ") %d:'%s'\n", src_num, seg->url);
|
||||
}
|
||||
MP_VERBOSE(demuxer, "Total duration: %f\n", p->duration);
|
||||
}
|
||||
|
||||
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] == target_index)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create mapping from segment streams to virtual timeline streams.
|
||||
static void associate_streams(struct demuxer *demuxer, struct segment *seg)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
|
||||
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);
|
||||
// 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 ? other->index : -1);
|
||||
|
||||
counts[sh->type] += 1;
|
||||
}
|
||||
if (p->dash)
|
||||
MP_VERBOSE(demuxer, "Durations and offsets are non-authoritative.\n");
|
||||
}
|
||||
|
||||
static int d_open(struct demuxer *demuxer, enum demux_check check)
|
||||
@@ -334,6 +390,8 @@ static int d_open(struct demuxer *demuxer, enum demux_check check)
|
||||
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 = next->start,
|
||||
@@ -345,6 +403,8 @@ static int d_open(struct demuxer *demuxer, enum demux_check check)
|
||||
MP_TARRAY_APPEND(p, p->segments, p->num_segments, seg);
|
||||
}
|
||||
|
||||
p->dash = p->tl->dash;
|
||||
|
||||
print_timeline(demuxer);
|
||||
|
||||
demuxer->seekable = true;
|
||||
@@ -352,6 +412,8 @@ static int d_open(struct demuxer *demuxer, enum demux_check check)
|
||||
|
||||
demuxer->filetype = meta->filetype ? meta->filetype : meta->desc->name;
|
||||
|
||||
demuxer->is_network = p->tl->demuxer->is_network;
|
||||
|
||||
reselect_streams(demuxer);
|
||||
|
||||
return 0;
|
||||
@@ -361,6 +423,8 @@ static void d_close(struct demuxer *demuxer)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
struct demuxer *master = p->tl->demuxer;
|
||||
p->current = NULL;
|
||||
close_lazy_segments(demuxer);
|
||||
timeline_destroy(p->tl);
|
||||
free_demuxer(master);
|
||||
}
|
||||
|
||||
+5
-2
@@ -22,7 +22,10 @@ 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 || demuxer->stream->type != STREAMTYPE_TV)
|
||||
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,
|
||||
@@ -31,7 +34,7 @@ static int demux_open_tv(demuxer_t *demuxer, enum demux_check check)
|
||||
bstr channel, input;
|
||||
bstr_split_tok(urlparams, "/", &channel, &input);
|
||||
if (channel.len) {
|
||||
talloc_free(params->channels);
|
||||
talloc_free(params->channel);
|
||||
params->channel = bstrto0(NULL, channel);
|
||||
}
|
||||
if (input.len) {
|
||||
|
||||
+1
-1
@@ -120,7 +120,7 @@ int64_t ebml_read_vlen_int(bstr *buffer)
|
||||
return EBML_INT_INVALID;
|
||||
l = len - buffer->len;
|
||||
|
||||
return unum - ((1 << ((7 * l) - 1)) - 1);
|
||||
return unum - ((1LL << ((7 * l) - 1)) - 1);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
+21
-3
@@ -94,7 +94,7 @@ void demux_packet_shorten(struct demux_packet *dp, size_t len)
|
||||
{
|
||||
assert(len <= dp->len);
|
||||
dp->len = len;
|
||||
memset(dp->buffer + dp->len, 0, FF_INPUT_BUFFER_PADDING_SIZE);
|
||||
memset(dp->buffer + dp->len, 0, AV_INPUT_BUFFER_PADDING_SIZE);
|
||||
}
|
||||
|
||||
void free_demux_packet(struct demux_packet *dp)
|
||||
@@ -132,8 +132,8 @@ struct demux_packet *demux_copy_packet(struct demux_packet *dp)
|
||||
|
||||
int demux_packet_set_padding(struct demux_packet *dp, int start, int end)
|
||||
{
|
||||
#if HAVE_AVFRAME_SKIP_SAMPLES
|
||||
if (!start && !end)
|
||||
#if LIBAVCODEC_VERSION_MICRO >= 100
|
||||
if (!start && !end)
|
||||
return 0;
|
||||
if (!dp->avpacket)
|
||||
return -1;
|
||||
@@ -146,3 +146,21 @@ int demux_packet_set_padding(struct demux_packet *dp, int start, int end)
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
int demux_packet_add_blockadditional(struct demux_packet *dp, uint64_t id,
|
||||
void *data, size_t size)
|
||||
{
|
||||
#if LIBAVCODEC_VERSION_MICRO >= 100
|
||||
if (!dp->avpacket)
|
||||
return -1;
|
||||
uint8_t *sd = av_packet_new_side_data(dp->avpacket,
|
||||
AV_PKT_DATA_MATROSKA_BLOCKADDITIONAL,
|
||||
8 + size);
|
||||
if (!sd)
|
||||
return -1;
|
||||
AV_WB64(sd, id);
|
||||
if (size > 0)
|
||||
memcpy(sd + 8, data, size);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
@@ -55,5 +55,7 @@ struct demux_packet *demux_copy_packet(struct demux_packet *dp);
|
||||
void demux_packet_copy_attribs(struct demux_packet *dst, struct demux_packet *src);
|
||||
|
||||
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);
|
||||
|
||||
#endif /* MPLAYER_DEMUX_PACKET_H */
|
||||
+3
-1
@@ -33,8 +33,10 @@ void timeline_destroy(struct timeline *tl)
|
||||
return;
|
||||
for (int n = 0; n < tl->num_sources; n++) {
|
||||
struct demuxer *d = tl->sources[n];
|
||||
if (d != tl->demuxer)
|
||||
if (d != tl->demuxer && d != tl->track_layout)
|
||||
free_demuxer_and_stream(d);
|
||||
}
|
||||
if (tl->track_layout && tl->track_layout != tl->demuxer)
|
||||
free_demuxer_and_stream(tl->track_layout);
|
||||
talloc_free(tl);
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
struct timeline_part {
|
||||
double start;
|
||||
double source_start;
|
||||
char *url;
|
||||
struct demuxer *source;
|
||||
};
|
||||
|
||||
@@ -15,6 +16,9 @@ struct timeline {
|
||||
// main source
|
||||
struct demuxer *demuxer;
|
||||
|
||||
bstr init_fragment;
|
||||
bool dash;
|
||||
|
||||
// All referenced files. The source file must be at sources[0].
|
||||
struct demuxer **sources;
|
||||
int num_sources;
|
||||
|
||||
+1
-1
@@ -143,7 +143,7 @@
|
||||
#CLOSE_WIN {encode} quit 4
|
||||
#E cycle edition # next edition
|
||||
#l ab-loop # Set/clear A-B loop points
|
||||
#L cycle-values loop "inf" "no" # toggle infinite looping
|
||||
#L cycle-values loop-file "inf" "no" # toggle infinite looping
|
||||
#ctrl+c quit 4
|
||||
|
||||
# Apple Remote section
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
#
|
||||
# Older installations use ~/.mpv/input.conf instead.
|
||||
|
||||
# changed in mpv 0.24.0
|
||||
|
||||
L cycle-values loop "inf" "no"
|
||||
|
||||
# changed in mpv 0.10.0
|
||||
|
||||
O osd
|
||||
|
||||
+6
-4
@@ -144,7 +144,8 @@ struct input_ctx {
|
||||
|
||||
struct cmd_queue cmd_queue;
|
||||
|
||||
struct mp_cancel *cancel;
|
||||
void (*cancel)(void *cancel_ctx);
|
||||
void *cancel_ctx;
|
||||
|
||||
void (*wakeup_cb)(void *ctx);
|
||||
void *wakeup_ctx;
|
||||
@@ -809,7 +810,7 @@ int mp_input_queue_cmd(struct input_ctx *ictx, mp_cmd_t *cmd)
|
||||
input_lock(ictx);
|
||||
if (cmd) {
|
||||
if (ictx->cancel && test_abort_cmd(ictx, cmd))
|
||||
mp_cancel_trigger(ictx->cancel);
|
||||
ictx->cancel(ictx->cancel_ctx);
|
||||
queue_add_tail(&ictx->cmd_queue, cmd);
|
||||
mp_input_wakeup(ictx);
|
||||
}
|
||||
@@ -1335,10 +1336,11 @@ void mp_input_uninit(struct input_ctx *ictx)
|
||||
talloc_free(ictx);
|
||||
}
|
||||
|
||||
void mp_input_set_cancel(struct input_ctx *ictx, struct mp_cancel *cancel)
|
||||
void mp_input_set_cancel(struct input_ctx *ictx, void (*cb)(void *c), void *c)
|
||||
{
|
||||
input_lock(ictx);
|
||||
ictx->cancel = cancel;
|
||||
ictx->cancel = cb;
|
||||
ictx->cancel_ctx = c;
|
||||
input_unlock(ictx);
|
||||
}
|
||||
|
||||
|
||||
+1
-2
@@ -242,8 +242,7 @@ void mp_input_wakeup(struct input_ctx *ictx);
|
||||
|
||||
// Used to asynchronously abort playback. Needed because the core still can
|
||||
// block on network in some situations.
|
||||
struct mp_cancel;
|
||||
void mp_input_set_cancel(struct input_ctx *ictx, struct mp_cancel *cancel);
|
||||
void mp_input_set_cancel(struct input_ctx *ictx, void (*cb)(void *c), void *c);
|
||||
|
||||
// If this returns true, use Right Alt key as Alt Gr to produce special
|
||||
// characters. If false, count Right Alt as the modifier Alt key.
|
||||
|
||||
+1
-2
@@ -864,8 +864,7 @@ int mpv_command(mpv_handle *ctx, const char **args);
|
||||
* function succeeds, this is set to command-specific return
|
||||
* data. You must call mpv_free_node_contents() to free it
|
||||
* (again, only if the command actually succeeds).
|
||||
* Currently, no command uses this, but that can change in
|
||||
* the future.
|
||||
* Not many commands actually use this at all.
|
||||
* @return error code (the result parameter is not set on error)
|
||||
*/
|
||||
int mpv_command_node(mpv_handle *ctx, mpv_node *args, mpv_node *result);
|
||||
|
||||
+6
-3
@@ -117,14 +117,17 @@ extern "C" {
|
||||
*
|
||||
* While "normal" mpv loads the OpenGL hardware decoding interop on demand,
|
||||
* this can't be done with opengl_cb for internal technical reasons. Instead,
|
||||
* make it load the interop at load time by setting the "hwdec-preload"="auto"
|
||||
* option before calling mpv_opengl_cb_init_gl().
|
||||
* make it load the interop at load time by setting the
|
||||
* "opengl-hwdec-interop"="auto" option before calling mpv_opengl_cb_init_gl()
|
||||
* ("hwdec-preload" in older mpv releases).
|
||||
*
|
||||
* There may be certain requirements on the OpenGL implementation:
|
||||
* - Windows: ANGLE is required (although in theory GL/DX interop could be used)
|
||||
* - Intel/Linux: EGL is required, and also a glMPGetNativeDisplay() callback
|
||||
* must be provided (see sections below)
|
||||
* - nVidia/Linux: GLX is required
|
||||
* - nVidia/Linux: GLX is required (if you force "cuda", it should work on EGL
|
||||
* as well, if you have recent enough drivers and the
|
||||
* "hwaccel" option is set to "cuda" as well)
|
||||
* - OSX: CGL is required (CGLGetCurrentContext() returning non-NULL)
|
||||
*
|
||||
* Once these things are setup, hardware decoding can be enabled/disabled at
|
||||
|
||||
+17
-72
@@ -51,28 +51,6 @@ bool mp_charset_is_utf16(const char *user_cp)
|
||||
bstr_case_startswith(s, bstr0("utf-16"));
|
||||
}
|
||||
|
||||
// Split the string on ':' into components.
|
||||
// out_arr is at least max entries long.
|
||||
// Return number of out_arr entries filled.
|
||||
static int split_colon(const char *user_cp, int max, bstr *out_arr)
|
||||
{
|
||||
if (!user_cp || max < 1)
|
||||
return 0;
|
||||
|
||||
int count = 0;
|
||||
while (1) {
|
||||
const char *next = strchr(user_cp, ':');
|
||||
if (next && max - count > 1) {
|
||||
out_arr[count++] = (bstr){(char *)user_cp, next - user_cp};
|
||||
user_cp = next + 1;
|
||||
} else {
|
||||
out_arr[count++] = (bstr){(char *)user_cp, strlen(user_cp)};
|
||||
break;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static const char *const utf_bom[3] = {"\xEF\xBB\xBF", "\xFF\xFE", "\xFE\xFF"};
|
||||
static const char *const utf_enc[3] = {"utf-8", "utf-16le", "utf-16be"};
|
||||
|
||||
@@ -120,59 +98,20 @@ static const char *mp_uchardet(void *talloc_ctx, struct mp_log *log, bstr buf)
|
||||
// it's a real iconv codepage), user_cp is returned without even looking at
|
||||
// the buf data.
|
||||
// The return value may (but doesn't have to) be allocated under talloc_ctx.
|
||||
static const char *mp_charset_guess_compat(void *talloc_ctx, struct mp_log *log,
|
||||
bstr buf, const char *user_cp,
|
||||
int flags)
|
||||
{
|
||||
mp_warn(log, "This syntax for the --sub-codepage option is deprecated.\n");
|
||||
|
||||
bstr params[3] = {{0}};
|
||||
split_colon(user_cp, 3, params);
|
||||
|
||||
bstr type = params[0];
|
||||
char lang[100];
|
||||
snprintf(lang, sizeof(lang), "%.*s", BSTR_P(params[1]));
|
||||
const char *fallback = params[2].start; // last item, already 0-terminated
|
||||
|
||||
const char *res = NULL;
|
||||
|
||||
#if HAVE_UCHARDET
|
||||
if (bstrcasecmp0(type, "uchardet") == 0) {
|
||||
res = mp_uchardet(talloc_ctx, log, buf);
|
||||
if (!res && bstr_validate_utf8(buf) >= 0)
|
||||
res = "utf-8";
|
||||
}
|
||||
#endif
|
||||
|
||||
if (bstrcasecmp0(type, "utf8") == 0 || bstrcasecmp0(type, "utf-8") == 0) {
|
||||
if (!fallback)
|
||||
fallback = params[1].start; // must be already 0-terminated
|
||||
int r = bstr_validate_utf8(buf);
|
||||
if (r >= 0 || (r > -8 && (flags & MP_ICONV_ALLOW_CUTOFF)))
|
||||
res = "utf-8";
|
||||
}
|
||||
|
||||
if (res) {
|
||||
mp_dbg(log, "%.*s detected charset: '%s'\n", BSTR_P(type), res);
|
||||
} else {
|
||||
res = fallback;
|
||||
mp_dbg(log, "Detection with %.*s failed: fallback to %s\n",
|
||||
BSTR_P(type), res && res[0] ? res : "broken UTF-8/Latin1");
|
||||
}
|
||||
|
||||
if (!res && !(flags & MP_STRICT_UTF8))
|
||||
res = "UTF-8-BROKEN";
|
||||
|
||||
mp_verbose(log, "Using charset '%s'.\n", res);
|
||||
return res;
|
||||
}
|
||||
|
||||
const char *mp_charset_guess(void *talloc_ctx, struct mp_log *log, bstr buf,
|
||||
const char *user_cp, int flags)
|
||||
{
|
||||
if (strcasecmp(user_cp, "enca") == 0 || strcasecmp(user_cp, "guess") == 0 ||
|
||||
strcasecmp(user_cp, "uchardet") == 0 || strchr(user_cp, ':'))
|
||||
return mp_charset_guess_compat(talloc_ctx, log, buf, user_cp, flags);
|
||||
{
|
||||
mp_err(log, "This syntax for the --sub-codepage option was deprecated "
|
||||
"and has been removed.\n");
|
||||
if (strncasecmp(user_cp, "utf8:", 5) == 0) {
|
||||
user_cp = user_cp + 5;
|
||||
} else {
|
||||
user_cp = "";
|
||||
}
|
||||
}
|
||||
|
||||
if (user_cp[0] == '+') {
|
||||
mp_verbose(log, "Forcing charset '%s'.\n", user_cp + 1);
|
||||
@@ -191,7 +130,7 @@ const char *mp_charset_guess(void *talloc_ctx, struct mp_log *log, bstr buf,
|
||||
return "utf-8";
|
||||
}
|
||||
|
||||
const char *res = user_cp;
|
||||
const char *res = NULL;
|
||||
if (strcasecmp(user_cp, "auto") == 0) {
|
||||
#if HAVE_UCHARDET
|
||||
res = mp_uchardet(talloc_ctx, log, buf);
|
||||
@@ -200,6 +139,8 @@ const char *mp_charset_guess(void *talloc_ctx, struct mp_log *log, bstr buf,
|
||||
mp_verbose(log, "Charset auto-detection failed.\n");
|
||||
res = "UTF-8-BROKEN";
|
||||
}
|
||||
} else {
|
||||
res = user_cp;
|
||||
}
|
||||
|
||||
mp_verbose(log, "Using charset '%s'.\n", res);
|
||||
@@ -293,5 +234,9 @@ bstr mp_iconv_to_utf8(struct mp_log *log, bstr buf, const char *cp, int flags)
|
||||
#endif
|
||||
|
||||
failure:
|
||||
return (bstr){0};
|
||||
if (flags & MP_NO_LATIN1_FALLBACK) {
|
||||
return buf;
|
||||
} else {
|
||||
return bstr_sanitize_utf8_latin1(NULL, buf);
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ enum {
|
||||
MP_ICONV_VERBOSE = 1, // print errors instead of failing silently
|
||||
MP_ICONV_ALLOW_CUTOFF = 2, // allow partial input data
|
||||
MP_STRICT_UTF8 = 4, // don't fall back to UTF-8-BROKEN when guessing
|
||||
MP_NO_LATIN1_FALLBACK = 8, // fall back to input buffer instead of latin1
|
||||
};
|
||||
|
||||
bool mp_charset_is_utf8(const char *user_cp);
|
||||
|
||||
+26
-4
@@ -40,6 +40,7 @@
|
||||
#include "stream/stream.h"
|
||||
#include "video/csputils.h"
|
||||
#include "video/hwdec.h"
|
||||
#include "video/out/opengl/hwdec.h"
|
||||
#include "video/image_writer.h"
|
||||
#include "sub/osd.h"
|
||||
#include "audio/filter/af.h"
|
||||
@@ -87,6 +88,8 @@ extern const struct m_obj_list af_obj_list;
|
||||
extern const struct m_obj_list vo_obj_list;
|
||||
extern const struct m_obj_list ao_obj_list;
|
||||
|
||||
extern const struct m_sub_options angle_conf;
|
||||
|
||||
const struct m_opt_choice_alternatives mp_hwdec_names[] = {
|
||||
{"no", HWDEC_NONE},
|
||||
{"auto", HWDEC_AUTO},
|
||||
@@ -150,9 +153,9 @@ const struct m_sub_options stream_cache_conf = {
|
||||
|
||||
static const m_option_t mp_vo_opt_list[] = {
|
||||
OPT_SETTINGSLIST("vo", video_driver_list, 0, &vo_obj_list, ),
|
||||
OPT_CHOICE_C("hwdec-preload", hwdec_preload_api, 0, mp_hwdec_names),
|
||||
OPT_SUBSTRUCT("sws", sws_opts, sws_conf, 0),
|
||||
OPT_FLAG("taskbar-progress", taskbar_progress, 0),
|
||||
OPT_FLAG("snap-window", snap_window, 0),
|
||||
OPT_FLAG("ontop", ontop, 0),
|
||||
OPT_FLAG("border", border, 0),
|
||||
OPT_FLAG("fit-border", fit_border, 0),
|
||||
@@ -199,7 +202,11 @@ static const m_option_t mp_vo_opt_list[] = {
|
||||
0, drm_validate_connector_opt),
|
||||
OPT_INT("drm-mode", drm_mode_id, 0),
|
||||
#endif
|
||||
|
||||
#if HAVE_GL
|
||||
OPT_STRING_VALIDATE("opengl-hwdec-interop", gl_hwdec_interop, 0,
|
||||
gl_hwdec_validate_opt),
|
||||
OPT_REPLACED("hwdec-preload", "opengl-hwdec-interop"),
|
||||
#endif
|
||||
{0}
|
||||
};
|
||||
|
||||
@@ -216,6 +223,7 @@ const struct m_sub_options vo_sub_opts = {
|
||||
.keepaspect_window = 1,
|
||||
.hidpi_window_scale = 1,
|
||||
.taskbar_progress = 1,
|
||||
.snap_window = 0,
|
||||
.border = 1,
|
||||
.fit_border = 1,
|
||||
.WinID = -1,
|
||||
@@ -379,6 +387,7 @@ const m_option_t mp_opts[] = {
|
||||
OPT_STRING("audio-demuxer", audio_demuxer_name, 0),
|
||||
OPT_STRING("sub-demuxer", sub_demuxer_name, 0),
|
||||
OPT_FLAG("demuxer-thread", demuxer_thread, 0),
|
||||
OPT_FLAG("prefetch-playlist", prefetch_open, 0),
|
||||
OPT_FLAG("cache-pause", cache_pausing, 0),
|
||||
|
||||
OPT_DOUBLE("mf-fps", mf_fps, 0),
|
||||
@@ -466,6 +475,7 @@ const m_option_t mp_opts[] = {
|
||||
OPT_FLAG("sub-forced-only", forced_subs_only, UPDATE_OSD),
|
||||
OPT_FLAG("stretch-dvd-subs", stretch_dvd_subs, UPDATE_OSD),
|
||||
OPT_FLAG("stretch-image-subs-to-screen", stretch_image_subs, UPDATE_OSD),
|
||||
OPT_FLAG("image-subs-video-resolution", image_subs_video_res, UPDATE_OSD),
|
||||
OPT_FLAG("sub-fix-timing", sub_fix_timing, 0),
|
||||
OPT_CHOICE("sub-auto", sub_auto, 0,
|
||||
({"no", -1}, {"exact", 0}, {"fuzzy", 1}, {"all", 2})),
|
||||
@@ -490,6 +500,7 @@ const m_option_t mp_opts[] = {
|
||||
({"none", 0}, {"light", 1}, {"normal", 2}, {"native", 3})),
|
||||
OPT_CHOICE("sub-ass-shaper", ass_shaper, UPDATE_OSD,
|
||||
({"simple", 0}, {"complex", 1})),
|
||||
OPT_FLAG("sub-ass-justify", ass_justify, 0),
|
||||
OPT_CHOICE("sub-ass-style-override", ass_style_override, UPDATE_OSD,
|
||||
({"no", 0}, {"yes", 1}, {"force", 3}, {"signfs", 4}, {"strip", 5})),
|
||||
OPT_FLAG("sub-scale-by-window", sub_scale_by_window, UPDATE_OSD),
|
||||
@@ -583,7 +594,6 @@ const m_option_t mp_opts[] = {
|
||||
|
||||
OPT_FLAG("untimed", untimed, 0),
|
||||
|
||||
OPT_STRING("stream-capture", stream_capture, M_OPT_FILE),
|
||||
OPT_STRING("stream-dump", stream_dump, M_OPT_FILE),
|
||||
|
||||
OPT_FLAG("stop-playback-on-init-failure", stop_playback_on_init_failure, 0),
|
||||
@@ -671,6 +681,8 @@ const m_option_t mp_opts[] = {
|
||||
OPT_STRING("screenshot-template", screenshot_template, 0),
|
||||
OPT_STRING("screenshot-directory", screenshot_directory, 0),
|
||||
|
||||
OPT_STRING("record-file", record_file, M_OPT_FILE),
|
||||
|
||||
OPT_SUBSTRUCT("", input_opts, input_config, 0),
|
||||
|
||||
OPT_PRINT("list-protocols", stream_print_proto_list),
|
||||
@@ -686,6 +698,15 @@ const m_option_t mp_opts[] = {
|
||||
OPT_SUBSTRUCT("", gl_video_opts, gl_video_conf, 0),
|
||||
#endif
|
||||
|
||||
#if HAVE_EGL_ANGLE
|
||||
OPT_SUBSTRUCT("", angle_opts, angle_conf, 0),
|
||||
#endif
|
||||
|
||||
#if HAVE_GL_WIN32
|
||||
OPT_CHOICE("opengl-dwmflush", wingl_dwm_flush, 0,
|
||||
({"no", -1}, {"auto", 0}, {"windowed", 1}, {"yes", 2})),
|
||||
#endif
|
||||
|
||||
#if HAVE_ENCODING
|
||||
OPT_SUBSTRUCT("", encode_opts, encode_config, 0),
|
||||
#endif
|
||||
@@ -697,7 +718,8 @@ const m_option_t mp_opts[] = {
|
||||
OPT_REPLACED("ass", "sub-ass"),
|
||||
OPT_REPLACED("audiofile", "audio-file"),
|
||||
OPT_REMOVED("benchmark", "use --untimed (no stats)"),
|
||||
OPT_REMOVED("capture", "use --stream-capture=<filename>"),
|
||||
OPT_REMOVED("capture", NULL),
|
||||
OPT_REMOVED("stream-capture", NULL),
|
||||
OPT_REMOVED("channels", "use --audio-channels (changed semantics)"),
|
||||
OPT_REPLACED("cursor-autohide-delay", "cursor-autohide"),
|
||||
OPT_REPLACED("delay", "audio-delay"),
|
||||
|
||||
+9
-2
@@ -10,6 +10,7 @@ typedef struct mp_vo_opts {
|
||||
struct m_obj_settings *video_driver_list;
|
||||
|
||||
int taskbar_progress;
|
||||
int snap_window;
|
||||
int ontop;
|
||||
int fullscreen;
|
||||
int border;
|
||||
@@ -50,7 +51,7 @@ typedef struct mp_vo_opts {
|
||||
// vo_wayland, vo_drm
|
||||
struct sws_opts *sws_opts;
|
||||
// vo_opengl, vo_opengl_cb
|
||||
int hwdec_preload_api;
|
||||
char *gl_hwdec_interop;
|
||||
// vo_drm
|
||||
char *drm_connector_spec;
|
||||
int drm_mode_id;
|
||||
@@ -136,8 +137,8 @@ typedef struct MPOpts {
|
||||
int video_osd;
|
||||
|
||||
int untimed;
|
||||
char *stream_capture;
|
||||
char *stream_dump;
|
||||
char *record_file;
|
||||
int stop_playback_on_init_failure;
|
||||
int loop_times;
|
||||
int loop_file;
|
||||
@@ -213,12 +214,14 @@ typedef struct MPOpts {
|
||||
int forced_subs_only;
|
||||
int stretch_dvd_subs;
|
||||
int stretch_image_subs;
|
||||
int image_subs_video_res;
|
||||
|
||||
int sub_fix_timing;
|
||||
|
||||
char **audio_files;
|
||||
char *demuxer_name;
|
||||
int demuxer_thread;
|
||||
int prefetch_open;
|
||||
char *audio_demuxer_name;
|
||||
char *sub_demuxer_name;
|
||||
|
||||
@@ -279,6 +282,7 @@ typedef struct MPOpts {
|
||||
int ass_style_override;
|
||||
int ass_hinting;
|
||||
int ass_shaper;
|
||||
int ass_justify;
|
||||
int sub_clear_on_seek;
|
||||
int teletext_page;
|
||||
|
||||
@@ -318,7 +322,10 @@ typedef struct MPOpts {
|
||||
char *ipc_path;
|
||||
char *input_file;
|
||||
|
||||
int wingl_dwm_flush;
|
||||
|
||||
struct gl_video_opts *gl_video_opts;
|
||||
struct angle_opts *angle_opts;
|
||||
struct dvd_opts *dvd_opts;
|
||||
} MPOpts;
|
||||
|
||||
|
||||
@@ -150,7 +150,8 @@ int m_config_parse(m_config_t *config, const char *location, bstr data,
|
||||
}
|
||||
}
|
||||
|
||||
m_config_finish_default_profile(config, flags);
|
||||
if (config->recursion_depth == 0)
|
||||
m_config_finish_default_profile(config, flags);
|
||||
|
||||
talloc_free(tmp);
|
||||
return 1;
|
||||
|
||||
@@ -61,25 +61,6 @@ typedef struct { volatile unsigned long long v; } atomic_ullong;
|
||||
__atomic_compare_exchange_n(&(a)->v, b, c, 0, __ATOMIC_SEQ_CST, \
|
||||
__ATOMIC_SEQ_CST)
|
||||
|
||||
#elif HAVE_SYNC_BUILTINS
|
||||
|
||||
#define atomic_load(p) \
|
||||
__sync_fetch_and_add(&(p)->v, 0)
|
||||
#define atomic_store(p, val) \
|
||||
(__sync_synchronize(), (p)->v = (val), __sync_synchronize())
|
||||
#define atomic_fetch_add(a, b) \
|
||||
__sync_fetch_and_add(&(a)->v, b)
|
||||
#define atomic_fetch_and(a, b) \
|
||||
__sync_fetch_and_and(&(a)->v, b)
|
||||
#define atomic_fetch_or(a, b) \
|
||||
__sync_fetch_and_or(&(a)->v, b)
|
||||
// Assumes __sync_val_compare_and_swap is "strong" (using the C11 meaning).
|
||||
#define atomic_compare_exchange_strong(p, old, new) \
|
||||
({ __typeof__((p)->v) val_ = __sync_val_compare_and_swap(&(p)->v, *(old), new); \
|
||||
bool ok_ = val_ == *(old); \
|
||||
if (!ok_) *(old) = val_; \
|
||||
ok_; })
|
||||
|
||||
#elif defined(__GNUC__)
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ VS_VERSION_INFO VERSIONINFO
|
||||
VALUE "CompanyName", "mpv"
|
||||
VALUE "FileDescription", "mpv"
|
||||
VALUE "FileVersion", "2.0.0.0"
|
||||
VALUE "LegalCopyright", "(C) 2000-2016 mpv/mplayer2/MPlayer"
|
||||
VALUE "LegalCopyright", "(C) 2000-2017 mpv/mplayer2/MPlayer"
|
||||
VALUE "OriginalFilename", "mpv.exe"
|
||||
VALUE "ProductName", "mpv"
|
||||
VALUE "ProductVersion", "2.0.0.0"
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <errors.h>
|
||||
#include <audioclient.h>
|
||||
#include <d3d9.h>
|
||||
#include <dxgi1_2.h>
|
||||
|
||||
#include "windows_utils.h"
|
||||
|
||||
@@ -118,6 +119,13 @@ static char *hresult_to_str(const HRESULT hr)
|
||||
E(D3DERR_CANNOTPROTECTCONTENT)
|
||||
E(D3DERR_UNSUPPORTEDCRYPTO)
|
||||
E(D3DERR_PRESENT_STATISTICS_DISJOINT)
|
||||
E(DXGI_ERROR_DEVICE_HUNG)
|
||||
E(DXGI_ERROR_DEVICE_REMOVED)
|
||||
E(DXGI_ERROR_DEVICE_RESET)
|
||||
E(DXGI_ERROR_DRIVER_INTERNAL_ERROR)
|
||||
E(DXGI_ERROR_INVALID_CALL)
|
||||
E(DXGI_ERROR_WAS_STILL_DRAWING)
|
||||
E(DXGI_STATUS_OCCLUDED)
|
||||
default:
|
||||
return "<Unknown>";
|
||||
}
|
||||
|
||||
@@ -20,6 +20,10 @@
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
// Conditionally release a COM interface and set the pointer to NULL
|
||||
#define SAFE_RELEASE(u) \
|
||||
do { if ((u) != NULL) (u)->lpVtbl->Release(u); (u) = NULL; } while(0)
|
||||
|
||||
char *mp_GUID_to_str_buf(char *buf, size_t buf_size, const GUID *guid);
|
||||
#define mp_GUID_to_str(guid) mp_GUID_to_str_buf((char[40]){0}, 40, (guid))
|
||||
char *mp_HRESULT_to_str_buf(char *buf, size_t buf_size, HRESULT hr);
|
||||
|
||||
+1
-1
@@ -817,7 +817,7 @@ static int filter_audio(struct MPContext *mpctx, struct mp_audio_buffer *outbuf,
|
||||
break;
|
||||
|
||||
res = decode_new_frame(ao_c);
|
||||
if (res == AD_NO_PROGRESS)
|
||||
if (res == AD_NO_PROGRESS || res == AD_WAIT)
|
||||
break;
|
||||
if (res < 0) {
|
||||
// drain filters first (especially for true EOF case)
|
||||
|
||||
+17
-2
@@ -359,6 +359,8 @@ void mpv_detach_destroy(mpv_handle *ctx)
|
||||
if (!ctx)
|
||||
return;
|
||||
|
||||
MP_VERBOSE(ctx, "Exiting...\n");
|
||||
|
||||
// reserved_events equals the number of asynchronous requests that weren't
|
||||
// yet replied. In order to avoid that trying to reply to a removed client
|
||||
// causes a crash, block until all asynchronous requests were served.
|
||||
@@ -407,7 +409,13 @@ void mpv_terminate_destroy(mpv_handle *ctx)
|
||||
if (!ctx)
|
||||
return;
|
||||
|
||||
mpv_command(ctx, (const char*[]){"quit", NULL});
|
||||
if (ctx->mpctx->initialized) {
|
||||
mpv_command(ctx, (const char*[]){"quit", NULL});
|
||||
} else {
|
||||
mp_dispatch_lock(ctx->mpctx->dispatch);
|
||||
ctx->mpctx->stop_play = PT_QUIT;
|
||||
mp_dispatch_unlock(ctx->mpctx->dispatch);
|
||||
}
|
||||
|
||||
if (!ctx->owner) {
|
||||
mpv_detach_destroy(ctx);
|
||||
@@ -851,6 +859,12 @@ static bool compare_value(void *a, void *b, mpv_format format)
|
||||
return false;
|
||||
return compare_value(&a_n->u, &b_n->u, a_n->format);
|
||||
}
|
||||
case MPV_FORMAT_BYTE_ARRAY: {
|
||||
struct mpv_byte_array *a_r = a, *b_r = b;
|
||||
if (a_r->size != b_r->size)
|
||||
return false;
|
||||
return memcmp(a_r->data, b_r->data, a_r->size) == 0;
|
||||
}
|
||||
case MPV_FORMAT_NODE_ARRAY:
|
||||
case MPV_FORMAT_NODE_MAP:
|
||||
{
|
||||
@@ -968,7 +982,7 @@ static int run_client_command(mpv_handle *ctx, struct mp_cmd *cmd, mpv_node *res
|
||||
return MPV_ERROR_INVALID_PARAMETER;
|
||||
|
||||
if (mp_input_is_abort_cmd(cmd))
|
||||
mp_cancel_trigger(ctx->mpctx->playback_abort);
|
||||
mp_abort_playback_async(ctx->mpctx);
|
||||
|
||||
cmd->sender = ctx->name;
|
||||
|
||||
@@ -1088,6 +1102,7 @@ int mpv_set_property(mpv_handle *ctx, const char *name, mpv_format format,
|
||||
mp_get_property_id(ctx->mpctx, name) >= 0)
|
||||
return MPV_ERROR_PROPERTY_UNAVAILABLE;
|
||||
switch (r) {
|
||||
case MPV_ERROR_SUCCESS: return MPV_ERROR_SUCCESS;
|
||||
case MPV_ERROR_OPTION_FORMAT: return MPV_ERROR_PROPERTY_FORMAT;
|
||||
case MPV_ERROR_OPTION_NOT_FOUND: return MPV_ERROR_PROPERTY_NOT_FOUND;
|
||||
default: return MPV_ERROR_PROPERTY_ERROR;
|
||||
|
||||
+119
-43
@@ -243,6 +243,78 @@ void mark_seek(struct MPContext *mpctx)
|
||||
cmd->last_seek_time = now;
|
||||
}
|
||||
|
||||
static char *skip_n_lines(char *text, int lines)
|
||||
{
|
||||
while (text && lines > 0) {
|
||||
char *next = strchr(text, '\n');
|
||||
text = next ? next + 1 : NULL;
|
||||
lines--;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
static int count_lines(char *text)
|
||||
{
|
||||
int count = 0;
|
||||
while (text) {
|
||||
char *next = strchr(text, '\n');
|
||||
if (!next || (next[0] == '\n' && !next[1]))
|
||||
break;
|
||||
text = next + 1;
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// Given a huge string separated by new lines, attempts to cut off text above
|
||||
// the current line to keep the line visible, and below to keep rendering
|
||||
// performance up. pos gives the current line (0 for the first line).
|
||||
// "text" might be returned as is, or it can be freed and a new allocation is
|
||||
// returned.
|
||||
// This is only a heuristic - we can't deal with line breaking.
|
||||
static char *cut_osd_list(struct MPContext *mpctx, char *text, int pos)
|
||||
{
|
||||
int screen_h, font_h;
|
||||
osd_get_text_size(mpctx->osd, &screen_h, &font_h);
|
||||
int max_lines = screen_h / MPMAX(font_h, 1) - 1;
|
||||
|
||||
if (!text || max_lines < 5)
|
||||
return text;
|
||||
|
||||
int count = count_lines(text);
|
||||
if (count <= max_lines)
|
||||
return text;
|
||||
|
||||
char *new = talloc_strdup(NULL, "");
|
||||
|
||||
int start = pos - max_lines / 2;
|
||||
if (start == 1)
|
||||
start = 0; // avoid weird transition when pad_h becomes visible
|
||||
int pad_h = start > 0;
|
||||
if (pad_h)
|
||||
new = talloc_strdup_append_buffer(new, "\342\206\221 (hidden items)\n");
|
||||
|
||||
int space = max_lines - pad_h - 1;
|
||||
int pad_t = count - start > space;
|
||||
if (!pad_t)
|
||||
start = count - space;
|
||||
|
||||
char *head = skip_n_lines(text, start);
|
||||
if (!head) {
|
||||
talloc_free(new);
|
||||
return text;
|
||||
}
|
||||
|
||||
char *tail = skip_n_lines(head, max_lines - pad_h - pad_t);
|
||||
new = talloc_asprintf_append_buffer(new, "%.*s",
|
||||
(int)(tail ? tail - head : strlen(head)), head);
|
||||
if (pad_t)
|
||||
new = talloc_strdup_append_buffer(new, "\342\206\223 (hidden items)\n");
|
||||
|
||||
talloc_free(text);
|
||||
return new;
|
||||
}
|
||||
|
||||
static char *format_file_size(int64_t size)
|
||||
{
|
||||
double s = size;
|
||||
@@ -515,29 +587,6 @@ static int mp_property_stream_path(void *ctx, struct m_property *prop,
|
||||
return m_property_strdup_ro(action, arg, mpctx->demuxer->filename);
|
||||
}
|
||||
|
||||
struct change_stream_capture_args {
|
||||
char *filename;
|
||||
struct demuxer *demux;
|
||||
};
|
||||
|
||||
static void do_change_stream_capture(void *p)
|
||||
{
|
||||
struct change_stream_capture_args *args = p;
|
||||
stream_set_capture_file(args->demux->stream, args->filename);
|
||||
}
|
||||
|
||||
static int mp_property_stream_capture(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
if (mpctx->demuxer && action == M_PROPERTY_SET) {
|
||||
struct change_stream_capture_args args = {*(char **)arg, mpctx->demuxer};
|
||||
demux_run_on_thread(mpctx->demuxer, do_change_stream_capture, &args);
|
||||
// fall through to mp_property_generic_option
|
||||
}
|
||||
return mp_property_generic_option(mpctx, prop, action, arg);
|
||||
}
|
||||
|
||||
/// Demuxer name (RO)
|
||||
static int mp_property_demuxer(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
@@ -650,7 +699,7 @@ static int mp_property_total_avsync_change(void *ctx, struct m_property *prop,
|
||||
return m_property_double_ro(action, arg, mpctx->total_avsync_change);
|
||||
}
|
||||
|
||||
static int mp_property_drop_frame_cnt(void *ctx, struct m_property *prop,
|
||||
static int mp_property_frame_drop_dec(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
@@ -692,8 +741,8 @@ static int mp_property_vsync_ratio(void *ctx, struct m_property *prop,
|
||||
return m_property_double_ro(action, arg, vsyncs / (double)frames);
|
||||
}
|
||||
|
||||
static int mp_property_vo_drop_frame_count(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
static int mp_property_frame_drop_vo(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
if (!mpctx->vo_chain)
|
||||
@@ -3344,9 +3393,10 @@ static int mp_property_playlist(void *ctx, struct m_property *prop,
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
if (action == M_PROPERTY_PRINT) {
|
||||
struct playlist *pl = mpctx->playlist;
|
||||
char *res = talloc_strdup(NULL, "");
|
||||
|
||||
for (struct playlist_entry *e = mpctx->playlist->first; e; e = e->next)
|
||||
for (struct playlist_entry *e = pl->first; e; e = e->next)
|
||||
{
|
||||
char *p = e->filename;
|
||||
if (!mp_is_url(bstr0(p))) {
|
||||
@@ -3354,12 +3404,12 @@ static int mp_property_playlist(void *ctx, struct m_property *prop,
|
||||
if (s[0])
|
||||
p = s;
|
||||
}
|
||||
const char *m = mpctx->playlist->current == e ?
|
||||
list_current : list_normal;
|
||||
const char *m = pl->current == e ? list_current : list_normal;
|
||||
res = talloc_asprintf_append(res, "%s%s\n", m, p);
|
||||
}
|
||||
|
||||
*(char **)arg = res;
|
||||
*(char **)arg =
|
||||
cut_osd_list(mpctx, res, playlist_entry_to_index(pl, pl->current));
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
|
||||
@@ -3499,6 +3549,26 @@ static int mp_property_cwd(void *ctx, struct m_property *prop,
|
||||
return M_PROPERTY_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
static int mp_property_record_file(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
struct MPContext *mpctx = ctx;
|
||||
struct MPOpts *opts = mpctx->opts;
|
||||
if (action == M_PROPERTY_SET) {
|
||||
char *new = *(char **)arg;
|
||||
if (!bstr_equals(bstr0(new), bstr0(opts->record_file))) {
|
||||
talloc_free(opts->record_file);
|
||||
opts->record_file = talloc_strdup(NULL, new);
|
||||
open_recorder(mpctx, false);
|
||||
// open_recorder() unsets it on failure.
|
||||
if (new && !opts->record_file)
|
||||
return M_PROPERTY_ERROR;
|
||||
}
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
return mp_property_generic_option(mpctx, prop, action, arg);
|
||||
}
|
||||
|
||||
static int mp_property_protocols(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
@@ -3789,7 +3859,6 @@ static const struct m_property mp_properties_base[] = {
|
||||
{"path", mp_property_path},
|
||||
{"media-title", mp_property_media_title},
|
||||
{"stream-path", mp_property_stream_path},
|
||||
{"stream-capture", mp_property_stream_capture},
|
||||
{"current-demuxer", mp_property_demuxer},
|
||||
{"file-format", mp_property_file_format},
|
||||
{"stream-pos", mp_property_stream_pos},
|
||||
@@ -3797,10 +3866,10 @@ static const struct m_property mp_properties_base[] = {
|
||||
{"duration", mp_property_duration},
|
||||
{"avsync", mp_property_avsync},
|
||||
{"total-avsync-change", mp_property_total_avsync_change},
|
||||
{"drop-frame-count", mp_property_drop_frame_cnt},
|
||||
{"mistimed-frame-count", mp_property_mistimed_frame_count},
|
||||
{"vsync-ratio", mp_property_vsync_ratio},
|
||||
{"vo-drop-frame-count", mp_property_vo_drop_frame_count},
|
||||
{"decoder-frame-drop-count", mp_property_frame_drop_dec},
|
||||
{"frame-drop-count", mp_property_frame_drop_vo},
|
||||
{"vo-delayed-frame-count", mp_property_vo_delayed_frame_count},
|
||||
{"percent-pos", mp_property_percent_pos},
|
||||
{"time-start", mp_property_time_start},
|
||||
@@ -3971,6 +4040,8 @@ static const struct m_property mp_properties_base[] = {
|
||||
|
||||
{"working-directory", mp_property_cwd},
|
||||
|
||||
{"record-file", mp_property_record_file},
|
||||
|
||||
{"protocol-list", mp_property_protocols},
|
||||
{"decoder-list", mp_property_decoders},
|
||||
{"encoder-list", mp_property_encoders},
|
||||
@@ -3994,6 +4065,9 @@ static const struct m_property mp_properties_base[] = {
|
||||
M_PROPERTY_ALIAS("colormatrix-input-range", "video-params/colorlevels"),
|
||||
M_PROPERTY_ALIAS("colormatrix-primaries", "video-params/primaries"),
|
||||
M_PROPERTY_ALIAS("colormatrix-gamma", "video-params/gamma"),
|
||||
|
||||
M_PROPERTY_DEPRECATED_ALIAS("drop-frame-count", "decoder-frame-drop-count"),
|
||||
M_PROPERTY_DEPRECATED_ALIAS("vo-drop-frame-count", "frame-drop-count"),
|
||||
};
|
||||
|
||||
// Each entry describes which properties an event (possibly) changes.
|
||||
@@ -4015,7 +4089,8 @@ static const char *const *const mp_event_property_change[] = {
|
||||
"total-avsync-change", "audio-speed-correction", "video-speed-correction",
|
||||
"vo-delayed-frame-count", "mistimed-frame-count", "vsync-ratio",
|
||||
"estimated-display-fps", "vsync-jitter", "sub-text", "audio-bitrate",
|
||||
"video-bitrate", "sub-bitrate"),
|
||||
"video-bitrate", "sub-bitrate", "decoder-frame-drop-count",
|
||||
"frame-drop-count"),
|
||||
E(MPV_EVENT_VIDEO_RECONFIG, "video-out-params", "video-params",
|
||||
"video-format", "video-codec", "video-bitrate", "dwidth", "dheight",
|
||||
"width", "height", "fps", "aspect", "vo-configured", "current-vo",
|
||||
@@ -4197,9 +4272,9 @@ static const struct property_osd_display {
|
||||
} property_osd_display[] = {
|
||||
// general
|
||||
{ "loop", "Loop" },
|
||||
{ "loop-file", "Loop current file" },
|
||||
{ "chapter", .seek_msg = OSD_SEEK_INFO_CHAPTER_TEXT,
|
||||
.seek_bar = OSD_SEEK_INFO_BAR },
|
||||
{ "edition", .seek_msg = OSD_SEEK_INFO_EDITION },
|
||||
{ "hr-seek", "hr-seek" },
|
||||
{ "speed", "Speed" },
|
||||
{ "clock", "Clock" },
|
||||
@@ -4218,6 +4293,7 @@ static const struct property_osd_display {
|
||||
// video
|
||||
{ "panscan", "Panscan", .osd_progbar = OSD_PANSCAN },
|
||||
{ "taskbar-progress", "Progress in taskbar" },
|
||||
{ "snap-window", "Snap to screen edges" },
|
||||
{ "ontop", "Stay on top" },
|
||||
{ "border", "Border" },
|
||||
{ "framedrop", "Framedrop" },
|
||||
@@ -4960,7 +5036,7 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
|
||||
int dir = cmd->id == MP_CMD_PLAYLIST_PREV ? -1 : +1;
|
||||
int force = cmd->args[0].v.i;
|
||||
|
||||
struct playlist_entry *e = mp_next_file(mpctx, dir, force);
|
||||
struct playlist_entry *e = mp_next_file(mpctx, dir, force, true);
|
||||
if (!e && !force)
|
||||
return -1;
|
||||
mp_set_playlist_entry(mpctx, e);
|
||||
@@ -5309,20 +5385,19 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
|
||||
|
||||
case MP_CMD_AB_LOOP: {
|
||||
double now = get_current_time(mpctx);
|
||||
int r = 0;
|
||||
if (opts->ab_loop[0] == MP_NOPTS_VALUE) {
|
||||
r = mp_property_do("ab-loop-a", M_PROPERTY_SET, &now, mpctx);
|
||||
mp_property_do("ab-loop-a", M_PROPERTY_SET, &now, mpctx);
|
||||
show_property_osd(mpctx, "ab-loop-a", on_osd);
|
||||
} else if (opts->ab_loop[1] == MP_NOPTS_VALUE) {
|
||||
r = mp_property_do("ab-loop-b", M_PROPERTY_SET, &now, mpctx);
|
||||
mp_property_do("ab-loop-b", M_PROPERTY_SET, &now, mpctx);
|
||||
show_property_osd(mpctx, "ab-loop-b", on_osd);
|
||||
} else {
|
||||
now = MP_NOPTS_VALUE;
|
||||
r = mp_property_do("ab-loop-a", M_PROPERTY_SET, &now, mpctx);
|
||||
r = mp_property_do("ab-loop-b", M_PROPERTY_SET, &now, mpctx);
|
||||
mp_property_do("ab-loop-a", M_PROPERTY_SET, &now, mpctx);
|
||||
mp_property_do("ab-loop-b", M_PROPERTY_SET, &now, mpctx);
|
||||
set_osd_msg(mpctx, osdl, osd_duration, "Clear A-B loop");
|
||||
}
|
||||
return r > 0;
|
||||
break;
|
||||
}
|
||||
|
||||
case MP_CMD_DROP_BUFFERS: {
|
||||
@@ -5376,7 +5451,8 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
|
||||
if (cmd->is_up_down)
|
||||
state[0] = cmd->repeated ? 'r' : (cmd->is_up ? 'u' : 'd');
|
||||
event.num_args = 4;
|
||||
event.args = (const char*[4]){"key-binding", name, state, cmd->key_name};
|
||||
event.args = (const char*[4]){"key-binding", name, state,
|
||||
cmd->key_name ? cmd->key_name : ""};
|
||||
if (mp_client_send_event_dup(mpctx, target,
|
||||
MPV_EVENT_CLIENT_MESSAGE, &event) < 0)
|
||||
{
|
||||
|
||||
+35
-5
@@ -52,8 +52,7 @@ enum mp_osd_seek_info {
|
||||
OSD_SEEK_INFO_BAR = 1,
|
||||
OSD_SEEK_INFO_TEXT = 2,
|
||||
OSD_SEEK_INFO_CHAPTER_TEXT = 4,
|
||||
OSD_SEEK_INFO_EDITION = 8,
|
||||
OSD_SEEK_INFO_CURRENT_FILE = 16,
|
||||
OSD_SEEK_INFO_CURRENT_FILE = 8,
|
||||
};
|
||||
|
||||
|
||||
@@ -154,6 +153,9 @@ struct track {
|
||||
struct vo_chain *vo_c;
|
||||
struct ao_chain *ao_c;
|
||||
struct lavfi_pad *sink;
|
||||
|
||||
// For stream recording (remuxing mode).
|
||||
struct mp_recorder_sink *remux_sink;
|
||||
};
|
||||
|
||||
// Summarizes video filtering and output.
|
||||
@@ -179,6 +181,8 @@ struct vo_chain {
|
||||
// - video consists of a single picture, which should be shown only once
|
||||
// - do not sync audio to video in any way
|
||||
bool is_coverart;
|
||||
// Just to avoid decoding the coverart picture again after a seek.
|
||||
struct mp_image *cached_coverart;
|
||||
};
|
||||
|
||||
// Like vo_chain, for audio.
|
||||
@@ -420,6 +424,8 @@ typedef struct MPContext {
|
||||
// playback rate. Used to avoid showing it multiple times.
|
||||
bool drop_message_shown;
|
||||
|
||||
struct mp_recorder *recorder;
|
||||
|
||||
char *cached_watch_later_configdir;
|
||||
|
||||
struct screenshot_ctx *screenshot_ctx;
|
||||
@@ -429,6 +435,26 @@ typedef struct MPContext {
|
||||
struct mp_ipc_ctx *ipc_ctx;
|
||||
|
||||
struct mpv_opengl_cb_context *gl_cb_ctx;
|
||||
|
||||
pthread_mutex_t lock;
|
||||
|
||||
// --- The following fields are protected by lock
|
||||
struct mp_cancel *demuxer_cancel; // cancel handle for MPContext.demuxer
|
||||
|
||||
// --- Owned by MPContext
|
||||
pthread_t open_thread;
|
||||
bool open_active; // open_thread is a valid thread handle, all setup
|
||||
atomic_bool open_done;
|
||||
// --- All fields below are immutable while open_active is true.
|
||||
// Otherwise, they're owned by MPContext.
|
||||
struct mp_cancel *open_cancel;
|
||||
char *open_url;
|
||||
char *open_format;
|
||||
int open_url_flags;
|
||||
// --- All fields below are owned by open_thread, unless open_done was set
|
||||
// to true.
|
||||
struct demuxer *open_res_demuxer;
|
||||
int open_res_error;
|
||||
} MPContext;
|
||||
|
||||
// audio.c
|
||||
@@ -459,6 +485,7 @@ struct playlist_entry *mp_check_playlist_resume(struct MPContext *mpctx,
|
||||
struct playlist *playlist);
|
||||
|
||||
// loadfile.c
|
||||
void mp_abort_playback_async(struct MPContext *mpctx);
|
||||
void uninit_player(struct MPContext *mpctx, unsigned int mask);
|
||||
struct track *mp_add_external_file(struct MPContext *mpctx, char *filename,
|
||||
enum stream_type filter);
|
||||
@@ -473,7 +500,7 @@ struct track *mp_track_by_tid(struct MPContext *mpctx, enum stream_type type,
|
||||
void add_demuxer_tracks(struct MPContext *mpctx, struct demuxer *demuxer);
|
||||
bool mp_remove_track(struct MPContext *mpctx, struct track *track);
|
||||
struct playlist_entry *mp_next_file(struct MPContext *mpctx, int direction,
|
||||
bool force);
|
||||
bool force, bool mutate);
|
||||
void mp_set_playlist_entry(struct MPContext *mpctx, struct playlist_entry *e);
|
||||
void mp_play_files(struct MPContext *mpctx);
|
||||
void update_demuxer_properties(struct MPContext *mpctx);
|
||||
@@ -483,6 +510,10 @@ void prepare_playlist(struct MPContext *mpctx, struct playlist *pl);
|
||||
void autoload_external_files(struct MPContext *mpctx);
|
||||
struct track *select_default_track(struct MPContext *mpctx, int order,
|
||||
enum stream_type type);
|
||||
void prefetch_next(struct MPContext *mpctx);
|
||||
void close_recorder(struct MPContext *mpctx);
|
||||
void close_recorder_and_error(struct MPContext *mpctx);
|
||||
void open_recorder(struct MPContext *mpctx, bool on_init);
|
||||
|
||||
// main.c
|
||||
int mp_initialize(struct MPContext *mpctx, char **argv);
|
||||
@@ -501,8 +532,6 @@ void update_vo_playback_state(struct MPContext *mpctx);
|
||||
void update_window_title(struct MPContext *mpctx, bool force);
|
||||
void error_on_track(struct MPContext *mpctx, struct track *track);
|
||||
int stream_dump(struct MPContext *mpctx, const char *source_filename);
|
||||
int mpctx_run_reentrant(struct MPContext *mpctx, void (*thread_fn)(void *arg),
|
||||
void *thread_arg);
|
||||
double get_track_seek_offset(struct MPContext *mpctx, struct track *track);
|
||||
|
||||
// osd.c
|
||||
@@ -549,6 +578,7 @@ void update_screensaver_state(struct MPContext *mpctx);
|
||||
|
||||
// scripting.c
|
||||
struct mp_scripting {
|
||||
const char *name; // e.g. "lua script"
|
||||
const char *file_ext; // e.g. "lua"
|
||||
int (*load)(struct mpv_handle *client, const char *filename);
|
||||
};
|
||||
|
||||
+16
-5
@@ -10,13 +10,15 @@
|
||||
#include "common/global.h"
|
||||
#include "common/msg.h"
|
||||
#include "misc/ctype.h"
|
||||
#include "misc/charset_conv.h"
|
||||
#include "options/options.h"
|
||||
#include "options/path.h"
|
||||
#include "external_files.h"
|
||||
|
||||
static const char *const sub_exts[] = {"utf", "utf8", "utf-8", "idx", "sub", "srt",
|
||||
"smi", "rt", "txt", "ssa", "aqt", "jss",
|
||||
"js", "ass", "mks", "vtt", "sup", NULL};
|
||||
"smi", "rt", "ssa", "aqt", "jss",
|
||||
"js", "ass", "mks", "vtt", "sup", "scc",
|
||||
NULL};
|
||||
|
||||
static const char *const audio_exts[] = {"mp3", "aac", "mka", "dts", "flac",
|
||||
"ogg", "m4a", "ac3", "opus", "wav",
|
||||
@@ -97,11 +99,16 @@ static void append_dir_subtitles(struct mpv_global *global,
|
||||
if (mp_is_url(bstr0(fname)))
|
||||
goto out;
|
||||
|
||||
struct bstr f_fname = bstr0(mp_basename(fname));
|
||||
struct bstr f_fbname = bstr0(mp_basename(fname));
|
||||
struct bstr f_fname = mp_iconv_to_utf8(log, f_fbname,
|
||||
"UTF-8-MAC", MP_NO_LATIN1_FALLBACK);
|
||||
struct bstr f_fname_noext = bstrdup(tmpmem, bstr_strip_ext(f_fname));
|
||||
bstr_lower(f_fname_noext);
|
||||
struct bstr f_fname_trim = bstr_strip(f_fname_noext);
|
||||
|
||||
if (f_fbname.start != f_fname.start)
|
||||
talloc_steal(tmpmem, f_fname.start);
|
||||
|
||||
// 0 = nothing
|
||||
// 1 = any subtitle file
|
||||
// 2 = any sub file containing movie name
|
||||
@@ -113,15 +120,19 @@ static void append_dir_subtitles(struct mpv_global *global,
|
||||
mp_verbose(log, "Loading external files in %.*s\n", BSTR_P(path));
|
||||
struct dirent *de;
|
||||
while ((de = readdir(d))) {
|
||||
struct bstr dename = bstr0(de->d_name);
|
||||
void *tmpmem2 = talloc_new(tmpmem);
|
||||
|
||||
struct bstr den = bstr0(de->d_name);
|
||||
struct bstr dename = mp_iconv_to_utf8(log, den,
|
||||
"UTF-8-MAC", MP_NO_LATIN1_FALLBACK);
|
||||
// retrieve various parts of the filename
|
||||
struct bstr tmp_fname_noext = bstrdup(tmpmem2, bstr_strip_ext(dename));
|
||||
bstr_lower(tmp_fname_noext);
|
||||
struct bstr tmp_fname_ext = bstr_get_ext(dename);
|
||||
struct bstr tmp_fname_trim = bstr_strip(tmp_fname_noext);
|
||||
|
||||
if (den.start != dename.start)
|
||||
talloc_steal(tmpmem2, dename.start);
|
||||
|
||||
// check what it is (most likely)
|
||||
int type = test_ext(tmp_fname_ext);
|
||||
char **langs = NULL;
|
||||
|
||||
+17
-9
@@ -43,6 +43,11 @@
|
||||
|
||||
#include "lavfi.h"
|
||||
|
||||
#if LIBAVFILTER_VERSION_MICRO < 100
|
||||
#define av_buffersink_get_frame_flags(a, b, c) av_buffersink_get_frame(a, b)
|
||||
#define AV_BUFFERSINK_FLAG_NO_REQUEST 0
|
||||
#endif
|
||||
|
||||
struct lavfi {
|
||||
struct mp_log *log;
|
||||
char *graph_string;
|
||||
@@ -266,6 +271,10 @@ enum stream_type lavfi_pad_type(struct lavfi_pad *pad)
|
||||
void lavfi_set_connected(struct lavfi_pad *pad, bool connected)
|
||||
{
|
||||
pad->connected = connected;
|
||||
if (!pad->connected) {
|
||||
pad->output_needed = false;
|
||||
drop_pad_data(pad);
|
||||
}
|
||||
}
|
||||
|
||||
bool lavfi_get_connected(struct lavfi_pad *pad)
|
||||
@@ -545,19 +554,17 @@ static void read_output_pads(struct lavfi *c)
|
||||
if (pad->dir != LAVFI_OUT)
|
||||
continue;
|
||||
|
||||
// If disconnected, read and discard everything.
|
||||
if (!pad->pending_v && !pad->pending_a && !pad->connected)
|
||||
pad->output_needed = true;
|
||||
|
||||
if (!pad->output_needed)
|
||||
// If disconnected, read and discard everything (passively).
|
||||
if (pad->connected && !pad->output_needed)
|
||||
continue;
|
||||
|
||||
assert(pad->buffer);
|
||||
assert(!pad->pending_v && !pad->pending_a);
|
||||
|
||||
int flags = pad->output_needed ? 0 : AV_BUFFERSINK_FLAG_NO_REQUEST;
|
||||
int r = AVERROR_EOF;
|
||||
if (!pad->buffer_is_eof)
|
||||
r = av_buffersink_get_frame(pad->buffer, pad->tmp_frame);
|
||||
r = av_buffersink_get_frame_flags(pad->buffer, pad->tmp_frame, flags);
|
||||
if (r >= 0) {
|
||||
pad->output_needed = false;
|
||||
double pts = mp_pts_from_av(pad->tmp_frame->pts, &pad->timebase);
|
||||
@@ -583,6 +590,7 @@ static void read_output_pads(struct lavfi *c)
|
||||
// input pads (via av_buffersrc_get_nb_failed_requests()).
|
||||
pad->output_eof = false;
|
||||
} else if (r == AVERROR_EOF) {
|
||||
pad->output_needed = false;
|
||||
pad->buffer_is_eof = true;
|
||||
if (!c->draining_recover_eof && !c->draining_new_format)
|
||||
pad->output_eof = true;
|
||||
@@ -611,7 +619,7 @@ bool lavfi_process(struct lavfi *c)
|
||||
bool all_waiting = true;
|
||||
bool any_needs_input = false;
|
||||
bool any_needs_output = false;
|
||||
bool all_lavfi_eof = true;
|
||||
bool all_output_eof = true;
|
||||
bool all_input_eof = true;
|
||||
|
||||
// Determine the graph state
|
||||
@@ -623,12 +631,12 @@ bool lavfi_process(struct lavfi *c)
|
||||
any_needs_input |= pad->input_needed;
|
||||
all_input_eof &= pad->input_eof;
|
||||
} else if (pad->dir == LAVFI_OUT) {
|
||||
all_lavfi_eof &= pad->buffer_is_eof;
|
||||
all_output_eof &= pad->buffer_is_eof;
|
||||
any_needs_output |= pad->output_needed;
|
||||
}
|
||||
}
|
||||
|
||||
if (all_lavfi_eof && !all_input_eof) {
|
||||
if (all_output_eof && !all_input_eof) {
|
||||
free_graph(c);
|
||||
precreate_graph(c);
|
||||
all_waiting = false;
|
||||
|
||||
+245
-42
@@ -40,6 +40,7 @@
|
||||
#include "options/m_property.h"
|
||||
#include "common/common.h"
|
||||
#include "common/encode.h"
|
||||
#include "common/recorder.h"
|
||||
#include "input/input.h"
|
||||
|
||||
#include "audio/audio.h"
|
||||
@@ -57,6 +58,17 @@
|
||||
#include "command.h"
|
||||
#include "libmpv/client.h"
|
||||
|
||||
// Called by foreign threads when playback should be stopped and such.
|
||||
void mp_abort_playback_async(struct MPContext *mpctx)
|
||||
{
|
||||
mp_cancel_trigger(mpctx->playback_abort);
|
||||
|
||||
pthread_mutex_lock(&mpctx->lock);
|
||||
if (mpctx->demuxer_cancel)
|
||||
mp_cancel_trigger(mpctx->demuxer_cancel);
|
||||
pthread_mutex_unlock(&mpctx->lock);
|
||||
}
|
||||
|
||||
static void uninit_demuxer(struct MPContext *mpctx)
|
||||
{
|
||||
for (int r = 0; r < NUM_PTRACKS; r++) {
|
||||
@@ -80,6 +92,11 @@ static void uninit_demuxer(struct MPContext *mpctx)
|
||||
|
||||
free_demuxer_and_stream(mpctx->demuxer);
|
||||
mpctx->demuxer = NULL;
|
||||
|
||||
pthread_mutex_lock(&mpctx->lock);
|
||||
talloc_free(mpctx->demuxer_cancel);
|
||||
mpctx->demuxer_cancel = NULL;
|
||||
pthread_mutex_unlock(&mpctx->lock);
|
||||
}
|
||||
|
||||
#define APPEND(s, ...) mp_snprintf_cat(s, sizeof(s), __VA_ARGS__)
|
||||
@@ -453,6 +470,8 @@ void mp_switch_track_n(struct MPContext *mpctx, int order, enum stream_type type
|
||||
uninit_sub(mpctx, current);
|
||||
|
||||
if (current) {
|
||||
if (current->remux_sink)
|
||||
close_recorder_and_error(mpctx);
|
||||
current->selected = false;
|
||||
reselect_demux_stream(mpctx, current);
|
||||
}
|
||||
@@ -774,57 +793,144 @@ static void load_per_file_options(m_config_t *conf,
|
||||
}
|
||||
}
|
||||
|
||||
struct demux_open_args {
|
||||
int stream_flags;
|
||||
char *url;
|
||||
struct mpv_global *global;
|
||||
struct mp_cancel *cancel;
|
||||
struct mp_log *log;
|
||||
// results
|
||||
struct demuxer *demux;
|
||||
int err;
|
||||
};
|
||||
|
||||
static void open_demux_thread(void *pctx)
|
||||
static void *open_demux_thread(void *ctx)
|
||||
{
|
||||
struct demux_open_args *args = pctx;
|
||||
struct mpv_global *global = args->global;
|
||||
struct MPContext *mpctx = ctx;
|
||||
|
||||
struct demuxer_params p = {
|
||||
.force_format = global->opts->demuxer_name,
|
||||
.allow_capture = true,
|
||||
.stream_flags = args->stream_flags,
|
||||
.force_format = mpctx->open_format,
|
||||
.stream_flags = mpctx->open_url_flags,
|
||||
.initial_readahead = true,
|
||||
};
|
||||
args->demux = demux_open_url(args->url, &p, args->cancel, global);
|
||||
if (!args->demux) {
|
||||
mpctx->open_res_demuxer =
|
||||
demux_open_url(mpctx->open_url, &p, mpctx->open_cancel, mpctx->global);
|
||||
|
||||
if (mpctx->open_res_demuxer) {
|
||||
MP_VERBOSE(mpctx, "Opening done: %s\n", mpctx->open_url);
|
||||
} else {
|
||||
MP_VERBOSE(mpctx, "Opening failed or was aborted: %s\n", mpctx->open_url);
|
||||
|
||||
if (p.demuxer_failed) {
|
||||
args->err = MPV_ERROR_UNKNOWN_FORMAT;
|
||||
mpctx->open_res_error = MPV_ERROR_UNKNOWN_FORMAT;
|
||||
} else {
|
||||
args->err = MPV_ERROR_LOADING_FAILED;
|
||||
mpctx->open_res_error = MPV_ERROR_LOADING_FAILED;
|
||||
}
|
||||
}
|
||||
if (args->demux && global->opts->rebase_start_time)
|
||||
demux_set_ts_offset(args->demux, -args->demux->start_time);
|
||||
|
||||
atomic_store(&mpctx->open_done, true);
|
||||
mp_wakeup_core(mpctx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void cancel_open(struct MPContext *mpctx)
|
||||
{
|
||||
if (mpctx->open_cancel)
|
||||
mp_cancel_trigger(mpctx->open_cancel);
|
||||
|
||||
if (mpctx->open_active)
|
||||
pthread_join(mpctx->open_thread, NULL);
|
||||
mpctx->open_active = false;
|
||||
|
||||
TA_FREEP(&mpctx->open_cancel);
|
||||
TA_FREEP(&mpctx->open_url);
|
||||
TA_FREEP(&mpctx->open_format);
|
||||
|
||||
if (mpctx->open_res_demuxer)
|
||||
free_demuxer_and_stream(mpctx->open_res_demuxer);
|
||||
mpctx->open_res_demuxer = NULL;
|
||||
|
||||
atomic_store(&mpctx->open_done, false);
|
||||
}
|
||||
|
||||
// Setup all the field to open this url, and make sure a thread is running.
|
||||
static void start_open(struct MPContext *mpctx, char *url, int url_flags)
|
||||
{
|
||||
cancel_open(mpctx);
|
||||
|
||||
assert(!mpctx->open_active);
|
||||
assert(!mpctx->open_cancel);
|
||||
assert(!mpctx->open_res_demuxer);
|
||||
assert(!atomic_load(&mpctx->open_done));
|
||||
|
||||
mpctx->open_cancel = mp_cancel_new(NULL);
|
||||
mpctx->open_url = talloc_strdup(NULL, url);
|
||||
mpctx->open_format = talloc_strdup(NULL, mpctx->opts->demuxer_name);
|
||||
mpctx->open_url_flags = url_flags;
|
||||
if (mpctx->opts->load_unsafe_playlists)
|
||||
mpctx->open_url_flags = 0;
|
||||
|
||||
if (pthread_create(&mpctx->open_thread, NULL, open_demux_thread, mpctx)) {
|
||||
cancel_open(mpctx);
|
||||
return;
|
||||
}
|
||||
|
||||
mpctx->open_active = true;
|
||||
}
|
||||
|
||||
static void open_demux_reentrant(struct MPContext *mpctx)
|
||||
{
|
||||
struct demux_open_args args = {
|
||||
.global = mpctx->global,
|
||||
.cancel = mpctx->playback_abort,
|
||||
.log = mpctx->log,
|
||||
.stream_flags = mpctx->playing->stream_flags,
|
||||
.url = talloc_strdup(NULL, mpctx->stream_open_filename),
|
||||
};
|
||||
if (mpctx->opts->load_unsafe_playlists)
|
||||
args.stream_flags = 0;
|
||||
mpctx_run_reentrant(mpctx, open_demux_thread, &args);
|
||||
if (args.demux) {
|
||||
mpctx->demuxer = args.demux;
|
||||
enable_demux_thread(mpctx, mpctx->demuxer);
|
||||
char *url = mpctx->stream_open_filename;
|
||||
|
||||
if (mpctx->open_active) {
|
||||
bool done = atomic_load(&mpctx->open_done);
|
||||
bool failed = done && !mpctx->open_res_demuxer;
|
||||
bool correct_url = strcmp(mpctx->open_url, url) == 0;
|
||||
|
||||
if (correct_url && !failed) {
|
||||
MP_VERBOSE(mpctx, "Using prefetched/prefetching URL.\n");
|
||||
} else if (correct_url && failed) {
|
||||
MP_VERBOSE(mpctx, "Prefetched URL failed, retrying.\n");
|
||||
cancel_open(mpctx);
|
||||
} else {
|
||||
if (done) {
|
||||
MP_VERBOSE(mpctx, "Dropping finished prefetch of wrong URL.\n");
|
||||
} else {
|
||||
MP_VERBOSE(mpctx, "Aborting onging prefetch of wrong URL.\n");
|
||||
}
|
||||
cancel_open(mpctx);
|
||||
}
|
||||
}
|
||||
|
||||
if (!mpctx->open_active)
|
||||
start_open(mpctx, url, mpctx->playing->stream_flags);
|
||||
|
||||
// User abort should cancel the opener now.
|
||||
pthread_mutex_lock(&mpctx->lock);
|
||||
mpctx->demuxer_cancel = mpctx->open_cancel;
|
||||
pthread_mutex_unlock(&mpctx->lock);
|
||||
|
||||
while (!atomic_load(&mpctx->open_done)) {
|
||||
mp_idle(mpctx);
|
||||
|
||||
if (mpctx->stop_play)
|
||||
mp_abort_playback_async(mpctx);
|
||||
}
|
||||
|
||||
if (mpctx->open_res_demuxer) {
|
||||
assert(mpctx->demuxer_cancel == mpctx->open_cancel);
|
||||
mpctx->demuxer = mpctx->open_res_demuxer;
|
||||
mpctx->open_res_demuxer = NULL;
|
||||
mpctx->open_cancel = NULL;
|
||||
} else {
|
||||
mpctx->error_playing = args.err;
|
||||
mpctx->error_playing = mpctx->open_res_error;
|
||||
pthread_mutex_lock(&mpctx->lock);
|
||||
mpctx->demuxer_cancel = NULL;
|
||||
pthread_mutex_unlock(&mpctx->lock);
|
||||
}
|
||||
|
||||
cancel_open(mpctx); // cleanup
|
||||
}
|
||||
|
||||
void prefetch_next(struct MPContext *mpctx)
|
||||
{
|
||||
if (!mpctx->opts->prefetch_open)
|
||||
return;
|
||||
|
||||
struct playlist_entry *new_entry = mp_next_file(mpctx, +1, false, false);
|
||||
if (new_entry && !mpctx->open_active && new_entry->filename) {
|
||||
MP_VERBOSE(mpctx, "Prefetching: %s\n", new_entry->filename);
|
||||
start_open(mpctx, new_entry->filename, new_entry->stream_flags);
|
||||
}
|
||||
talloc_free(args.url);
|
||||
}
|
||||
|
||||
static bool init_complex_filters(struct MPContext *mpctx)
|
||||
@@ -977,7 +1083,7 @@ static void play_current_file(struct MPContext *mpctx)
|
||||
mpctx->filename = talloc_strdup(NULL, mpctx->playing->filename);
|
||||
mpctx->stream_open_filename = mpctx->filename;
|
||||
|
||||
mpctx->add_osd_seek_info &= OSD_SEEK_INFO_EDITION | OSD_SEEK_INFO_CURRENT_FILE;
|
||||
mpctx->add_osd_seek_info &= OSD_SEEK_INFO_CURRENT_FILE;
|
||||
|
||||
if (opts->reset_options) {
|
||||
for (int n = 0; opts->reset_options[n]; n++) {
|
||||
@@ -1038,6 +1144,10 @@ reopen_file:
|
||||
goto terminate_playback;
|
||||
}
|
||||
|
||||
if (mpctx->opts->rebase_start_time)
|
||||
demux_set_ts_offset(mpctx->demuxer, -mpctx->demuxer->start_time);
|
||||
enable_demux_thread(mpctx, mpctx->demuxer);
|
||||
|
||||
load_chapters(mpctx);
|
||||
add_demuxer_tracks(mpctx, mpctx->demuxer);
|
||||
|
||||
@@ -1149,6 +1259,8 @@ reopen_file:
|
||||
if (mpctx->opts->pause)
|
||||
pause_player(mpctx);
|
||||
|
||||
open_recorder(mpctx, true);
|
||||
|
||||
playback_start = mp_time_sec();
|
||||
mpctx->error_playing = 0;
|
||||
while (!mpctx->stop_play)
|
||||
@@ -1169,7 +1281,9 @@ terminate_playback:
|
||||
if (mpctx->step_frames)
|
||||
opts->pause = 1;
|
||||
|
||||
mp_cancel_trigger(mpctx->playback_abort);
|
||||
mp_abort_playback_async(mpctx);
|
||||
|
||||
close_recorder(mpctx);
|
||||
|
||||
// time to uninit all, except global stuff:
|
||||
uninit_complex_filters(mpctx);
|
||||
@@ -1255,8 +1369,9 @@ terminate_playback:
|
||||
// it can have side-effects and mutate mpctx.
|
||||
// direction: -1 (previous) or +1 (next)
|
||||
// force: if true, don't skip playlist entries marked as failed
|
||||
// mutate: if true, change loop counters
|
||||
struct playlist_entry *mp_next_file(struct MPContext *mpctx, int direction,
|
||||
bool force)
|
||||
bool force, bool mutate)
|
||||
{
|
||||
struct playlist_entry *next = playlist_get_next(mpctx->playlist, direction);
|
||||
if (next && direction < 0 && !force) {
|
||||
@@ -1316,7 +1431,7 @@ void mp_play_files(struct MPContext *mpctx)
|
||||
if (mpctx->stop_play == PT_NEXT_ENTRY || mpctx->stop_play == PT_ERROR ||
|
||||
mpctx->stop_play == AT_END_OF_FILE || !mpctx->stop_play)
|
||||
{
|
||||
new_entry = mp_next_file(mpctx, +1, false);
|
||||
new_entry = mp_next_file(mpctx, +1, false, true);
|
||||
}
|
||||
|
||||
mpctx->playlist->current = new_entry;
|
||||
@@ -1326,6 +1441,8 @@ void mp_play_files(struct MPContext *mpctx)
|
||||
if (!mpctx->playlist->current && mpctx->opts->player_idle_mode < 2)
|
||||
break;
|
||||
}
|
||||
|
||||
cancel_open(mpctx);
|
||||
}
|
||||
|
||||
// Abort current playback and set the given entry to play next.
|
||||
@@ -1339,3 +1456,89 @@ void mp_set_playlist_entry(struct MPContext *mpctx, struct playlist_entry *e)
|
||||
mpctx->stop_play = PT_CURRENT_ENTRY;
|
||||
mp_wakeup_core(mpctx);
|
||||
}
|
||||
|
||||
static void set_track_recorder_sink(struct track *track,
|
||||
struct mp_recorder_sink *sink)
|
||||
{
|
||||
if (track->d_sub)
|
||||
sub_set_recorder_sink(track->d_sub, sink);
|
||||
if (track->d_video)
|
||||
track->d_video->recorder_sink = sink;
|
||||
if (track->d_audio)
|
||||
track->d_audio->recorder_sink = sink;
|
||||
track->remux_sink = sink;
|
||||
}
|
||||
|
||||
void close_recorder(struct MPContext *mpctx)
|
||||
{
|
||||
if (!mpctx->recorder)
|
||||
return;
|
||||
|
||||
for (int n = 0; n < mpctx->num_tracks; n++)
|
||||
set_track_recorder_sink(mpctx->tracks[n], NULL);
|
||||
|
||||
mp_recorder_destroy(mpctx->recorder);
|
||||
mpctx->recorder = NULL;
|
||||
}
|
||||
|
||||
// Like close_recorder(), but also unset the option. Intended for use on errors.
|
||||
void close_recorder_and_error(struct MPContext *mpctx)
|
||||
{
|
||||
close_recorder(mpctx);
|
||||
talloc_free(mpctx->opts->record_file);
|
||||
mpctx->opts->record_file = NULL;
|
||||
mp_notify_property(mpctx, "record-file");
|
||||
MP_ERR(mpctx, "Disabling stream recording.\n");
|
||||
}
|
||||
|
||||
void open_recorder(struct MPContext *mpctx, bool on_init)
|
||||
{
|
||||
if (!mpctx->playback_initialized)
|
||||
return;
|
||||
|
||||
close_recorder(mpctx);
|
||||
|
||||
char *target = mpctx->opts->record_file;
|
||||
if (!target || !target[0])
|
||||
return;
|
||||
|
||||
struct sh_stream **streams = NULL;
|
||||
int num_streams = 0;
|
||||
|
||||
for (int n = 0; n < mpctx->num_tracks; n++) {
|
||||
struct track *track = mpctx->tracks[n];
|
||||
if (track->stream && track->selected &&
|
||||
(track->d_sub || track->d_video || track->d_audio))
|
||||
{
|
||||
MP_TARRAY_APPEND(NULL, streams, num_streams, track->stream);
|
||||
}
|
||||
}
|
||||
|
||||
mpctx->recorder = mp_recorder_create(mpctx->global, mpctx->opts->record_file,
|
||||
streams, num_streams);
|
||||
|
||||
if (!mpctx->recorder) {
|
||||
talloc_free(streams);
|
||||
close_recorder_and_error(mpctx);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!on_init)
|
||||
mp_recorder_mark_discontinuity(mpctx->recorder);
|
||||
|
||||
int n_stream = 0;
|
||||
for (int n = 0; n < mpctx->num_tracks; n++) {
|
||||
struct track *track = mpctx->tracks[n];
|
||||
if (n_stream >= num_streams)
|
||||
break;
|
||||
// (We expect track->stream not to be reused on other tracks.)
|
||||
if (track->stream == streams[n_stream]) {
|
||||
set_track_recorder_sink(track,
|
||||
mp_recorder_get_sink(mpctx->recorder, n_stream));
|
||||
n_stream++;
|
||||
}
|
||||
}
|
||||
|
||||
talloc_free(streams);
|
||||
}
|
||||
|
||||
@@ -1079,6 +1079,7 @@ static int script_readdir(lua_State *L)
|
||||
lua_pushstring(L, name); // list index name
|
||||
lua_settable(L, -3); // list
|
||||
}
|
||||
closedir(dir);
|
||||
talloc_free(fullpath);
|
||||
return 1;
|
||||
}
|
||||
@@ -1331,6 +1332,7 @@ static void add_functions(struct script_ctx *ctx)
|
||||
}
|
||||
|
||||
const struct mp_scripting mp_scripting_lua = {
|
||||
.name = "lua script",
|
||||
.file_ext = "lua",
|
||||
.load = load_lua,
|
||||
};
|
||||
@@ -419,6 +419,16 @@ function mp.register_idle(cb)
|
||||
idle_handlers[#idle_handlers + 1] = cb
|
||||
end
|
||||
|
||||
function mp.unregister_idle(cb)
|
||||
local new = {}
|
||||
for _, handler in ipairs(idle_handlers) do
|
||||
if handler ~= cb then
|
||||
new[#new + 1] = handler
|
||||
end
|
||||
end
|
||||
idle_handlers = new
|
||||
end
|
||||
|
||||
-- sent by "script-binding"
|
||||
mp.register_script_message("key-binding", dispatch_key_binding)
|
||||
|
||||
|
||||
+37
-25
@@ -459,20 +459,19 @@ function prepare_elements()
|
||||
elem_geo.h - slider_lo.border)
|
||||
end
|
||||
|
||||
else -- draw 1px nibbles
|
||||
else -- draw 2x1px nibbles
|
||||
|
||||
--top
|
||||
if (slider_lo.nibbles_top) then
|
||||
static_ass:rect_cw(s - 0.5, slider_lo.gap,
|
||||
s + 0.5, slider_lo.gap*2);
|
||||
static_ass:rect_cw(s - 1, slider_lo.border,
|
||||
s + 1, slider_lo.border + slider_lo.gap);
|
||||
end
|
||||
|
||||
--bottom
|
||||
if (slider_lo.nibbles_bottom) then
|
||||
static_ass:rect_cw(s - 0.5,
|
||||
elem_geo.h - slider_lo.gap*2,
|
||||
s + 0.5,
|
||||
elem_geo.h - slider_lo.gap);
|
||||
static_ass:rect_cw(s - 1,
|
||||
elem_geo.h -slider_lo.border -slider_lo.gap,
|
||||
s + 1, elem_geo.h - slider_lo.border);
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -548,14 +547,12 @@ function render_elements(master_ass)
|
||||
elem_ass:merge(element.static_ass)
|
||||
end
|
||||
|
||||
|
||||
|
||||
if (element.type == "slider") then
|
||||
|
||||
local slider_lo = element.layout.slider
|
||||
local elem_geo = element.layout.geometry
|
||||
local s_min, s_max = element.slider.min.value, element.slider.max.value
|
||||
|
||||
local s_min = element.slider.min.value
|
||||
local s_max = element.slider.max.value
|
||||
|
||||
-- draw pos marker
|
||||
local pos = element.slider.posF()
|
||||
@@ -568,7 +565,6 @@ function render_elements(master_ass)
|
||||
(slider_lo.stype == "knob") then
|
||||
foH = elem_geo.h / 2
|
||||
elseif (slider_lo.stype == "bar") then
|
||||
foV = foV + 1
|
||||
foH = slider_lo.border + slider_lo.gap
|
||||
end
|
||||
|
||||
@@ -585,10 +581,12 @@ function render_elements(master_ass)
|
||||
elem_ass:line_to(xp, (innerH)+foV)
|
||||
elem_ass:line_to(xp-(innerH/2), (innerH/2)+foV)
|
||||
elseif (slider_lo.stype == "knob") then
|
||||
elem_ass:rect_cw(xp, (9*innerH/20)+foV, elem_geo.w - foH, (11*innerH/20)+foV)
|
||||
elem_ass:rect_cw(foH, (3*innerH/8)+foV, xp, (5*innerH/8)+foV)
|
||||
elem_ass:round_rect_cw(xp - innerH/2, foV, xp + innerH/2,
|
||||
foV + innerH, innerH/2.0)
|
||||
elem_ass:rect_cw(xp, (9*innerH/20) + foV,
|
||||
elem_geo.w - foH, (11*innerH/20) + foV)
|
||||
elem_ass:rect_cw(foH, (3*innerH/8) + foV,
|
||||
xp, (5*innerH/8) + foV)
|
||||
elem_ass:round_rect_cw(xp - innerH/2, foV,
|
||||
xp + innerH/2, foV + innerH, innerH/2.0)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -660,8 +658,15 @@ function render_elements(master_ass)
|
||||
end
|
||||
|
||||
local maxchars = element.layout.button.maxchars
|
||||
|
||||
if not (maxchars == nil) and (#buttontext > maxchars) then
|
||||
if (#buttontext > maxchars+20) then
|
||||
while (#buttontext > maxchars+20) do
|
||||
buttontext = buttontext:gsub(".[\128-\191]*$", "")
|
||||
end
|
||||
buttontext = buttontext .. "..."
|
||||
end
|
||||
local _, nchars2 = buttontext:gsub(".[\128-\191]*", "")
|
||||
local stretch = (maxchars/#buttontext)*100
|
||||
buttontext = string.format("{\\fscx%f}",
|
||||
(maxchars/#buttontext)*100) .. buttontext
|
||||
end
|
||||
@@ -946,7 +951,7 @@ layouts["box"] = function ()
|
||||
lo = add_layout("title")
|
||||
lo.geometry = {x = posX, y = titlerowY, an = 8, w = 496, h = 12}
|
||||
lo.style = osc_styles.vidtitle
|
||||
lo.button.maxchars = 90
|
||||
lo.button.maxchars = 80
|
||||
|
||||
lo = add_layout("pl_prev")
|
||||
lo.geometry =
|
||||
@@ -1226,8 +1231,9 @@ layouts["bottombar"] = function()
|
||||
w = t_r - t_l, h = geo.h }
|
||||
lo = add_layout("title")
|
||||
lo.geometry = geo
|
||||
lo.style = osc_styles.vidtitleBar
|
||||
lo.button.maxchars = math.floor(geo.w/7)
|
||||
lo.style = string.format("%s{\\clip(%f,%f,%f,%f)}",
|
||||
osc_styles.vidtitleBar,
|
||||
geo.x, geo.y-geo.h/2, geo.w, geo.y+geo.h/2)
|
||||
|
||||
|
||||
-- Playback control buttons
|
||||
@@ -1296,6 +1302,7 @@ layouts["bottombar"] = function()
|
||||
lo.geometry = geo
|
||||
lo.style = osc_styles.timecodes
|
||||
lo.slider.border = 0
|
||||
lo.slider.gap = 2
|
||||
lo.slider.tooltip_style = osc_styles.timePosBar
|
||||
lo.slider.tooltip_an = 5
|
||||
lo.slider.stype = user_opts["seekbarstyle"]
|
||||
@@ -1403,7 +1410,8 @@ layouts["topbar"] = function()
|
||||
|
||||
|
||||
-- Seekbar
|
||||
geo = { x = sb_l, y = user_opts.barmargin, an = 7, w = math.max(0, sb_r - sb_l), h = geo.h }
|
||||
geo = { x = sb_l, y = user_opts.barmargin, an = 7,
|
||||
w = math.max(0, sb_r - sb_l), h = geo.h }
|
||||
new_element("bgbar1", "box")
|
||||
lo = add_layout("bgbar1")
|
||||
|
||||
@@ -1417,6 +1425,7 @@ layouts["topbar"] = function()
|
||||
lo.geometry = geo
|
||||
lo.style = osc_styles.timecodesBar
|
||||
lo.slider.border = 0
|
||||
lo.slider.gap = 2
|
||||
lo.slider.tooltip_style = osc_styles.timePosBar
|
||||
lo.slider.stype = user_opts["seekbarstyle"]
|
||||
lo.slider.tooltip_an = 5
|
||||
@@ -1449,8 +1458,9 @@ layouts["topbar"] = function()
|
||||
w = t_r - t_l, h = geo.h }
|
||||
lo = add_layout("title")
|
||||
lo.geometry = geo
|
||||
lo.style = osc_styles.vidtitleBar
|
||||
lo.button.maxchars = math.floor(geo.w/7)
|
||||
lo.style = string.format("%s{\\clip(%f,%f,%f,%f)}",
|
||||
osc_styles.vidtitleBar,
|
||||
geo.x, geo.y-geo.h/2, geo.w, geo.y+geo.h/2)
|
||||
end
|
||||
|
||||
-- Validate string type user options
|
||||
@@ -1506,6 +1516,7 @@ function osc_init()
|
||||
local have_pl = (pl_count > 1)
|
||||
local pl_pos = mp.get_property_number("playlist-pos", 0) + 1
|
||||
local have_ch = (mp.get_property_number("chapters", 0) > 0)
|
||||
local loop = mp.get_property("loop", "no")
|
||||
|
||||
local ne
|
||||
|
||||
@@ -1539,7 +1550,7 @@ function osc_init()
|
||||
ne = new_element("pl_prev", "button")
|
||||
|
||||
ne.content = "\238\132\144"
|
||||
ne.enabled = (pl_pos > 1)
|
||||
ne.enabled = (pl_pos > 1) or (loop ~= "no")
|
||||
ne.eventresponder["mouse_btn0_up"] =
|
||||
function ()
|
||||
mp.commandv("playlist-prev", "weak")
|
||||
@@ -1554,7 +1565,7 @@ function osc_init()
|
||||
ne = new_element("pl_next", "button")
|
||||
|
||||
ne.content = "\238\132\129"
|
||||
ne.enabled = (have_pl) and (pl_pos < pl_count)
|
||||
ne.enabled = (have_pl and (pl_pos < pl_count)) or (loop ~= "no")
|
||||
ne.eventresponder["mouse_btn0_up"] =
|
||||
function ()
|
||||
mp.commandv("playlist-next", "weak")
|
||||
@@ -2071,6 +2082,7 @@ function process_event(source, what)
|
||||
if n == 0 then
|
||||
--click on background (does not work)
|
||||
elseif n > 0 and not (n > #elements) and
|
||||
not (elements[n].eventresponder == nil) and
|
||||
not (elements[n].eventresponder[source .. "_" .. what] == nil) then
|
||||
|
||||
if mouse_hit(elements[n]) then
|
||||
|
||||
+64
-26
@@ -88,6 +88,42 @@ local function extract_chapters(data, video_length)
|
||||
return ret
|
||||
end
|
||||
|
||||
local function edl_track_joined(fragments, protocol, is_live)
|
||||
if not (type(fragments) == "table") or not fragments[1] then
|
||||
msg.debug("No fragments to join into EDL")
|
||||
return nil
|
||||
end
|
||||
|
||||
local edl = "edl://"
|
||||
local offset = 1
|
||||
|
||||
if (protocol == "http_dash_segments") and
|
||||
not fragments[1].duration and not is_live then
|
||||
-- assume MP4 DASH initialization segment
|
||||
edl = edl .. "!mp4_dash,init=" .. edl_escape(fragments[1].url) .. ";"
|
||||
offset = 2
|
||||
|
||||
-- Check remaining fragments for duration;
|
||||
-- if not available in all, give up.
|
||||
for i = offset, #fragments do
|
||||
if not fragments[i].duration then
|
||||
msg.error("EDL doesn't support fragments" ..
|
||||
"without duration with MP4 DASH")
|
||||
return nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for i = offset, #fragments do
|
||||
local fragment = fragments[i]
|
||||
edl = edl .. edl_escape(fragment.url)
|
||||
if fragment.duration then
|
||||
edl = edl..",length="..fragment.duration
|
||||
end
|
||||
edl = edl .. ";"
|
||||
end
|
||||
return edl
|
||||
end
|
||||
|
||||
mp.add_hook("on_load", 10, function ()
|
||||
local url = mp.get_property("stream-open-filename")
|
||||
@@ -124,15 +160,15 @@ mp.add_hook("on_load", 10, function ()
|
||||
if (mp.get_property("options/vid") == "no")
|
||||
and not option_was_set("ytdl-format") then
|
||||
|
||||
format = "bestaudio"
|
||||
format = "bestaudio/best"
|
||||
msg.verbose("Video disabled. Only using audio")
|
||||
end
|
||||
|
||||
if (format == "") then
|
||||
format = "bestvideo+bestaudio"
|
||||
format = "bestvideo+bestaudio/best"
|
||||
end
|
||||
table.insert(command, "--format")
|
||||
table.insert(command, string.format('(%s)[protocol!=http_dash_segments]/best', format))
|
||||
table.insert(command, format)
|
||||
|
||||
for param, arg in pairs(raw_options) do
|
||||
table.insert(command, "--" .. param)
|
||||
@@ -190,14 +226,7 @@ mp.add_hook("on_load", 10, function ()
|
||||
and (json.entries[1]["webpage_url"] == json["webpage_url"])) then
|
||||
msg.verbose("multi-arc video detected, building EDL")
|
||||
|
||||
local playlist = "edl://"
|
||||
for i, entry in pairs(json.entries) do
|
||||
playlist = playlist .. edl_escape(entry.url)
|
||||
if not (entry.duration == nil) then
|
||||
playlist = playlist..",start=0,length="..entry.duration
|
||||
end
|
||||
playlist = playlist .. ";"
|
||||
end
|
||||
local playlist = edl_track_joined(json.entries)
|
||||
|
||||
msg.debug("EDL: " .. playlist)
|
||||
|
||||
@@ -232,7 +261,7 @@ mp.add_hook("on_load", 10, function ()
|
||||
else
|
||||
subfile = subfile..edl_escape("memory://WEBVTT")
|
||||
end
|
||||
subfile = subfile..",start=0,length="..entry.duration..";"
|
||||
subfile = subfile..",length="..entry.duration..";"
|
||||
end
|
||||
msg.debug(j.." sub EDL: "..subfile)
|
||||
mp.commandv("sub-add", subfile, "auto", req.ext, j)
|
||||
@@ -270,23 +299,30 @@ mp.add_hook("on_load", 10, function ()
|
||||
else -- probably a video
|
||||
local streamurl = ""
|
||||
|
||||
-- DASH?
|
||||
-- DASH/split tracks
|
||||
if not (json["requested_formats"] == nil) then
|
||||
if (json["requested_formats"][1].protocol == "http_dash_segments") then
|
||||
msg.error("MPEG-Dash Segments unsupported, add [protocol!=http_dash_segments] to your ytdl-format.")
|
||||
return
|
||||
for _, track in pairs(json.requested_formats) do
|
||||
local edl_track = nil
|
||||
edl_track = edl_track_joined(track.fragments,
|
||||
track.protocol, json.is_live)
|
||||
if track.acodec and track.acodec ~= "none" then
|
||||
-- audio track
|
||||
mp.commandv("audio-add",
|
||||
edl_track or track.url, "auto",
|
||||
track.format_note or "")
|
||||
elseif track.vcodec and track.vcodec ~= "none" then
|
||||
-- video track
|
||||
streamurl = edl_track or track.url
|
||||
end
|
||||
end
|
||||
|
||||
-- video url
|
||||
streamurl = json["requested_formats"][1].url
|
||||
|
||||
-- audio url
|
||||
mp.commandv("audio-add", json["requested_formats"][2].url,
|
||||
"select", json["requested_formats"][2]["format_note"] or "")
|
||||
|
||||
elseif not (json.url == nil) then
|
||||
-- normal video
|
||||
streamurl = json.url
|
||||
local edl_track = nil
|
||||
edl_track = edl_track_joined(json.fragments, json.protocol,
|
||||
json.is_live)
|
||||
|
||||
-- normal video or single track
|
||||
streamurl = edl_track or json.url
|
||||
set_http_headers(json.http_headers)
|
||||
else
|
||||
msg.error("No URL found in JSON data.")
|
||||
@@ -295,7 +331,7 @@ mp.add_hook("on_load", 10, function ()
|
||||
|
||||
msg.debug("streamurl: " .. streamurl)
|
||||
|
||||
mp.set_property("stream-open-filename", streamurl)
|
||||
mp.set_property("stream-open-filename", streamurl:gsub("^data:", "data://", 1))
|
||||
|
||||
mp.set_property("file-local-options/force-media-title", json.title)
|
||||
|
||||
@@ -348,6 +384,8 @@ mp.add_hook("on_load", 10, function ()
|
||||
"rtmp_playpath", json.play_path)
|
||||
rtmp_prop = append_rtmp_prop(rtmp_prop,
|
||||
"rtmp_swfverify", json.player_url)
|
||||
rtmp_prop = append_rtmp_prop(rtmp_prop,
|
||||
"rtmp_swfurl", json.player_url)
|
||||
rtmp_prop = append_rtmp_prop(rtmp_prop,
|
||||
"rtmp_app", json.app)
|
||||
|
||||
|
||||
+11
-2
@@ -136,7 +136,7 @@ void mp_print_version(struct mp_log *log, int always)
|
||||
{
|
||||
int v = always ? MSGL_INFO : MSGL_V;
|
||||
mp_msg(log, v,
|
||||
"%s (C) 2000-2016 mpv/MPlayer/mplayer2 projects\n built on %s\n",
|
||||
"%s (C) 2000-2017 mpv/MPlayer/mplayer2 projects\n built on %s\n",
|
||||
mpv_version, mpv_builddate);
|
||||
print_libav_versions(log, v);
|
||||
mp_msg(log, v, "\n");
|
||||
@@ -199,6 +199,7 @@ void mp_destroy(struct MPContext *mpctx)
|
||||
pthread_detach(pthread_self());
|
||||
|
||||
mp_msg_uninit(mpctx->global);
|
||||
pthread_mutex_destroy(&mpctx->lock);
|
||||
talloc_free(mpctx);
|
||||
}
|
||||
|
||||
@@ -311,6 +312,12 @@ static int cfg_include(void *ctx, char *filename, int flags)
|
||||
return r;
|
||||
}
|
||||
|
||||
static void abort_playback_cb(void *ctx)
|
||||
{
|
||||
struct MPContext *mpctx = ctx;
|
||||
mp_abort_playback_async(mpctx);
|
||||
}
|
||||
|
||||
struct MPContext *mp_create(void)
|
||||
{
|
||||
char *enable_talloc = getenv("MPV_LEAK_REPORT");
|
||||
@@ -329,6 +336,8 @@ struct MPContext *mp_create(void)
|
||||
.playback_abort = mp_cancel_new(mpctx),
|
||||
};
|
||||
|
||||
pthread_mutex_init(&mpctx->lock, NULL);
|
||||
|
||||
mpctx->global = talloc_zero(mpctx, struct mpv_global);
|
||||
|
||||
// Nothing must call mp_msg*() and related before this
|
||||
@@ -361,7 +370,7 @@ struct MPContext *mp_create(void)
|
||||
cocoa_set_input_context(mpctx->input);
|
||||
#endif
|
||||
|
||||
mp_input_set_cancel(mpctx->input, mpctx->playback_abort);
|
||||
mp_input_set_cancel(mpctx->input, abort_playback_cb, mpctx);
|
||||
|
||||
char *verbose_env = getenv("MPV_VERBOSE");
|
||||
if (verbose_env)
|
||||
|
||||
+18
-53
@@ -17,7 +17,7 @@
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <pthread.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "config.h"
|
||||
@@ -213,21 +213,34 @@ int stream_dump(struct MPContext *mpctx, const char *source_filename)
|
||||
|
||||
int64_t size = stream_get_size(stream);
|
||||
|
||||
stream_set_capture_file(stream, opts->stream_dump);
|
||||
FILE *dest = fopen(opts->stream_dump, "wb");
|
||||
if (!dest) {
|
||||
MP_ERR(mpctx, "Error opening dump file: %s\n", mp_strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
while (mpctx->stop_play == KEEP_PLAYING && !stream->eof) {
|
||||
bool ok = true;
|
||||
|
||||
while (mpctx->stop_play == KEEP_PLAYING && ok) {
|
||||
if (!opts->quiet && ((stream->pos / (1024 * 1024)) % 2) == 1) {
|
||||
uint64_t pos = stream->pos;
|
||||
MP_MSG(mpctx, MSGL_STATUS, "Dumping %lld/%lld...",
|
||||
(long long int)pos, (long long int)size);
|
||||
}
|
||||
stream_fill_buffer(stream);
|
||||
bstr data = stream_peek(stream, STREAM_MAX_BUFFER_SIZE);
|
||||
if (data.len == 0) {
|
||||
ok &= stream->eof;
|
||||
break;
|
||||
}
|
||||
ok &= fwrite(data.start, data.len, 1, dest) == 1;
|
||||
stream_skip(stream, data.len);
|
||||
mp_wakeup_core(mpctx); // don't actually sleep
|
||||
mp_idle(mpctx); // but process input
|
||||
}
|
||||
|
||||
ok &= fclose(dest) == 0;
|
||||
free_stream(stream);
|
||||
return 0;
|
||||
return ok ? 0 : -1;
|
||||
}
|
||||
|
||||
void merge_playlist_files(struct playlist *pl)
|
||||
@@ -251,51 +264,3 @@ void merge_playlist_files(struct playlist *pl)
|
||||
playlist_add_file(pl, edl);
|
||||
talloc_free(edl);
|
||||
}
|
||||
|
||||
struct wrapper_args {
|
||||
struct MPContext *mpctx;
|
||||
void (*thread_fn)(void *);
|
||||
void *thread_arg;
|
||||
pthread_mutex_t mutex;
|
||||
bool done;
|
||||
};
|
||||
|
||||
static void *thread_wrapper(void *pctx)
|
||||
{
|
||||
struct wrapper_args *args = pctx;
|
||||
mpthread_set_name("opener");
|
||||
args->thread_fn(args->thread_arg);
|
||||
pthread_mutex_lock(&args->mutex);
|
||||
args->done = true;
|
||||
pthread_mutex_unlock(&args->mutex);
|
||||
mp_wakeup_core(args->mpctx); // this interrupts mp_idle()
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Run the thread_fn in a new thread. Wait until the thread returns, but while
|
||||
// waiting, process input and input commands.
|
||||
int mpctx_run_reentrant(struct MPContext *mpctx, void (*thread_fn)(void *arg),
|
||||
void *thread_arg)
|
||||
{
|
||||
struct wrapper_args args = {mpctx, thread_fn, thread_arg};
|
||||
pthread_mutex_init(&args.mutex, NULL);
|
||||
bool success = false;
|
||||
pthread_t thread;
|
||||
if (pthread_create(&thread, NULL, thread_wrapper, &args))
|
||||
goto done;
|
||||
while (!success) {
|
||||
mp_idle(mpctx);
|
||||
|
||||
if (mpctx->stop_play)
|
||||
mp_cancel_trigger(mpctx->playback_abort);
|
||||
|
||||
pthread_mutex_lock(&args.mutex);
|
||||
success |= args.done;
|
||||
pthread_mutex_unlock(&args.mutex);
|
||||
}
|
||||
pthread_join(thread, NULL);
|
||||
done:
|
||||
pthread_mutex_destroy(&args.mutex);
|
||||
mp_wakeup_core(mpctx); // avoid lost wakeups during waiting
|
||||
return success ? 0 : -1;
|
||||
}
|
||||
+7
-11
@@ -262,7 +262,7 @@ static void term_osd_print_status_lazy(struct MPContext *mpctx)
|
||||
if (mpctx->demuxer) {
|
||||
struct stream_cache_info info = {0};
|
||||
demux_stream_control(mpctx->demuxer, STREAM_CTRL_GET_CACHE_INFO, &info);
|
||||
if (info.size > 0) {
|
||||
if (info.size > 0 || mpctx->demuxer->is_network) {
|
||||
saddf(&line, " Cache: ");
|
||||
|
||||
struct demux_ctrl_reader_state s = {.ts_duration = -1};
|
||||
@@ -273,10 +273,12 @@ static void term_osd_print_status_lazy(struct MPContext *mpctx)
|
||||
} else {
|
||||
saddf(&line, "%2ds", (int)s.ts_duration);
|
||||
}
|
||||
if (info.fill >= 1024 * 1024) {
|
||||
saddf(&line, "+%lldMB", (long long)(info.fill / 1024 / 1024));
|
||||
} else {
|
||||
saddf(&line, "+%lldKB", (long long)(info.fill / 1024));
|
||||
if (info.size > 0) {
|
||||
if (info.fill >= 1024 * 1024) {
|
||||
saddf(&line, "+%lldMB", (long long)(info.fill / 1024 / 1024));
|
||||
} else {
|
||||
saddf(&line, "+%lldKB", (long long)(info.fill / 1024));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -474,12 +476,6 @@ static void add_seek_osd_messages(struct MPContext *mpctx)
|
||||
"Chapter: %s", chapter);
|
||||
talloc_free(chapter);
|
||||
}
|
||||
if ((mpctx->add_osd_seek_info & OSD_SEEK_INFO_EDITION) && mpctx->demuxer) {
|
||||
set_osd_msg(mpctx, 1, mpctx->opts->osd_duration,
|
||||
"Playing edition %d of %d.",
|
||||
mpctx->demuxer->edition + 1,
|
||||
mpctx->demuxer->num_editions);
|
||||
}
|
||||
if (mpctx->add_osd_seek_info & OSD_SEEK_INFO_CURRENT_FILE) {
|
||||
if (mpctx->filename) {
|
||||
set_osd_msg(mpctx, 1, mpctx->opts->osd_duration, "%s",
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "options/options.h"
|
||||
#include "common/common.h"
|
||||
#include "common/encode.h"
|
||||
#include "common/recorder.h"
|
||||
#include "options/m_config.h"
|
||||
#include "options/m_property.h"
|
||||
#include "common/playlist.h"
|
||||
@@ -330,6 +331,8 @@ static void mp_seek(MPContext *mpctx, struct seek_params seek)
|
||||
clear_audio_output_buffers(mpctx);
|
||||
|
||||
reset_playback_state(mpctx);
|
||||
if (mpctx->recorder)
|
||||
mp_recorder_mark_discontinuity(mpctx->recorder);
|
||||
|
||||
/* Use the target time as "current position" for further relative
|
||||
* seeks etc until a new video frame has been decoded */
|
||||
@@ -663,6 +666,9 @@ static void handle_pause_on_low_cache(struct MPContext *mpctx)
|
||||
force_update = true;
|
||||
}
|
||||
|
||||
if (s.eof && !busy)
|
||||
prefetch_next(mpctx);
|
||||
|
||||
if (force_update)
|
||||
mp_notify(mpctx, MP_EVENT_CACHE_UPDATE, NULL);
|
||||
}
|
||||
|
||||
+10
-6
@@ -31,6 +31,7 @@
|
||||
#include "common/msg.h"
|
||||
#include "options/path.h"
|
||||
#include "video/mp_image.h"
|
||||
#include "video/mp_image_pool.h"
|
||||
#include "video/decode/dec_video.h"
|
||||
#include "video/out/vo.h"
|
||||
#include "video/image_writer.h"
|
||||
@@ -346,12 +347,15 @@ static struct mp_image *screenshot_get(struct MPContext *mpctx, int mode)
|
||||
}
|
||||
}
|
||||
|
||||
if (image && mpctx->vo_chain && mpctx->vo_chain->hwdec_devs) {
|
||||
struct mp_hwdec_ctx *ctx =
|
||||
hwdec_devices_get_first(mpctx->vo_chain->hwdec_devs);
|
||||
struct mp_image *nimage = NULL;
|
||||
if (ctx && ctx->download_image && (image->fmt.flags & MP_IMGFLAG_HWACCEL))
|
||||
nimage = ctx->download_image(ctx, image, NULL);
|
||||
bool hwimage = image && (image->fmt.flags & MP_IMGFLAG_HWACCEL);
|
||||
if (hwimage) {
|
||||
struct mp_image *nimage = mp_image_hw_download(image, NULL);
|
||||
if (!nimage && mpctx->vo_chain && mpctx->vo_chain->hwdec_devs) {
|
||||
struct mp_hwdec_ctx *ctx =
|
||||
hwdec_devices_get_first(mpctx->vo_chain->hwdec_devs);
|
||||
if (ctx && ctx->download_image && hwimage)
|
||||
nimage = ctx->download_image(ctx, image, NULL);
|
||||
}
|
||||
if (nimage) {
|
||||
talloc_free(image);
|
||||
image = nimage;
|
||||
|
||||
+39
-5
@@ -37,10 +37,14 @@
|
||||
#include "libmpv/client.h"
|
||||
|
||||
extern const struct mp_scripting mp_scripting_lua;
|
||||
extern const struct mp_scripting mp_scripting_cplugin;
|
||||
|
||||
static const struct mp_scripting *const scripting_backends[] = {
|
||||
#if HAVE_LUA
|
||||
&mp_scripting_lua,
|
||||
#endif
|
||||
#if HAVE_CPLUGINS
|
||||
&mp_scripting_cplugin,
|
||||
#endif
|
||||
NULL
|
||||
};
|
||||
@@ -80,13 +84,12 @@ static void *script_thread(void *p)
|
||||
struct thread_arg *arg = p;
|
||||
|
||||
char name[90];
|
||||
snprintf(name, sizeof(name), "lua (%s)", mpv_client_name(arg->client));
|
||||
snprintf(name, sizeof(name), "%s (%s)", arg->backend->name,
|
||||
mpv_client_name(arg->client));
|
||||
mpthread_set_name(name);
|
||||
|
||||
if (arg->backend->load(arg->client, arg->fname) < 0)
|
||||
MP_ERR(arg, "Could not load script %s\n", arg->fname);
|
||||
|
||||
MP_VERBOSE(arg, "Exiting...\n");
|
||||
MP_ERR(arg, "Could not load %s %s\n", arg->backend->name, arg->fname);
|
||||
|
||||
mpv_detach_destroy(arg->client);
|
||||
talloc_free(arg);
|
||||
@@ -133,7 +136,7 @@ int mp_load_script(struct MPContext *mpctx, const char *fname)
|
||||
}
|
||||
arg->log = mp_client_get_log(arg->client);
|
||||
|
||||
MP_VERBOSE(arg, "Loading script %s...\n", fname);
|
||||
MP_VERBOSE(arg, "Loading %s %s...\n", backend->name, fname);
|
||||
|
||||
pthread_t thread;
|
||||
if (pthread_create(&thread, NULL, script_thread, arg)) {
|
||||
@@ -228,3 +231,34 @@ void mp_load_scripts(struct MPContext *mpctx)
|
||||
}
|
||||
talloc_free(tmp);
|
||||
}
|
||||
|
||||
#if HAVE_CPLUGINS
|
||||
|
||||
#include <dlfcn.h>
|
||||
|
||||
#define MPV_DLOPEN_FN "mpv_open_cplugin"
|
||||
typedef int (*mpv_open_cplugin)(mpv_handle *handle);
|
||||
|
||||
static int load_cplugin(struct mpv_handle *client, const char *fname)
|
||||
{
|
||||
int r = -1;
|
||||
void *lib = dlopen(fname, RTLD_NOW | RTLD_LOCAL);
|
||||
if (!lib)
|
||||
goto error;
|
||||
// Note: once loaded, we never unload, as unloading the libraries linked to
|
||||
// the plugin can cause random serious problems.
|
||||
mpv_open_cplugin sym = (mpv_open_cplugin)dlsym(lib, MPV_DLOPEN_FN);
|
||||
if (!sym)
|
||||
goto error;
|
||||
r = sym(client) ? -1 : 0;
|
||||
error:
|
||||
return r;
|
||||
}
|
||||
|
||||
const struct mp_scripting mp_scripting_cplugin = {
|
||||
.name = "SO plugin",
|
||||
.file_ext = "so",
|
||||
.load = load_cplugin,
|
||||
};
|
||||
|
||||
#endif
|
||||
+27
-7
@@ -328,6 +328,10 @@ static void vo_chain_reset_state(struct vo_chain *vo_c)
|
||||
|
||||
if (vo_c->video_src)
|
||||
video_reset(vo_c->video_src);
|
||||
|
||||
// Prepare for continued playback after a seek.
|
||||
if (!vo_c->input_mpi && vo_c->cached_coverart)
|
||||
vo_c->input_mpi = mp_image_new_ref(vo_c->cached_coverart);
|
||||
}
|
||||
|
||||
void reset_video_state(struct MPContext *mpctx)
|
||||
@@ -381,6 +385,7 @@ static void vo_chain_uninit(struct vo_chain *vo_c)
|
||||
lavfi_set_connected(vo_c->filter_src, false);
|
||||
|
||||
mp_image_unrefp(&vo_c->input_mpi);
|
||||
mp_image_unrefp(&vo_c->cached_coverart);
|
||||
vf_destroy(vo_c->vf);
|
||||
talloc_free(vo_c);
|
||||
// this does not free the VO
|
||||
@@ -682,13 +687,25 @@ static int video_decode_and_filter(struct MPContext *mpctx)
|
||||
return r;
|
||||
|
||||
if (!vo_c->input_mpi) {
|
||||
// Decode a new image, or at least feed the decoder a packet.
|
||||
r = decode_image(mpctx);
|
||||
if (r == VD_WAIT)
|
||||
return r;
|
||||
if (vo_c->cached_coverart) {
|
||||
// Don't ever decode it twice, not even after seek resets.
|
||||
// (On seek resets, input_mpi is set to the cached image.)
|
||||
r = VD_EOF;
|
||||
} else {
|
||||
// Decode a new image, or at least feed the decoder a packet.
|
||||
r = decode_image(mpctx);
|
||||
if (r == VD_WAIT)
|
||||
return r;
|
||||
}
|
||||
}
|
||||
if (vo_c->input_mpi)
|
||||
|
||||
if (vo_c->input_mpi) {
|
||||
vo_c->input_format = vo_c->input_mpi->params;
|
||||
vf_set_proto_frame(vo_c->vf, vo_c->input_mpi);
|
||||
|
||||
if (vo_c->is_coverart && !vo_c->cached_coverart)
|
||||
vo_c->cached_coverart = mp_image_new_ref(vo_c->input_mpi);
|
||||
}
|
||||
|
||||
bool eof = !vo_c->input_mpi && (r == VD_EOF || r < 0);
|
||||
r = video_filter(mpctx, eof);
|
||||
@@ -1413,8 +1430,11 @@ void write_video(struct MPContext *mpctx)
|
||||
mp_image_params_get_dsize(&p, &d_w, &d_h);
|
||||
snprintf(extra, sizeof(extra), " => %dx%d", d_w, d_h);
|
||||
}
|
||||
MP_INFO(mpctx, "VO: [%s] %dx%d%s %s\n",
|
||||
info->name, p.w, p.h, extra, vo_format_name(p.imgfmt));
|
||||
char sfmt[20] = {0};
|
||||
if (p.hw_subfmt)
|
||||
snprintf(sfmt, sizeof(sfmt), "[%s]", mp_imgfmt_to_name(p.hw_subfmt));
|
||||
MP_INFO(mpctx, "VO: [%s] %dx%d%s %s%s\n",
|
||||
info->name, p.w, p.h, extra, mp_imgfmt_to_name(p.imgfmt), sfmt);
|
||||
MP_VERBOSE(mpctx, "VO: Description: %s\n", info->description);
|
||||
|
||||
int vo_r = vo_reconfig(vo, &p);
|
||||
|
||||
+1
-3
@@ -338,10 +338,8 @@ static int resize_cache(struct priv *s, int64_t size)
|
||||
buffer_size += s->back_size;
|
||||
|
||||
unsigned char *buffer = malloc(buffer_size);
|
||||
if (!buffer) {
|
||||
free(buffer);
|
||||
if (!buffer)
|
||||
return STREAM_ERROR;
|
||||
}
|
||||
|
||||
if (s->buffer) {
|
||||
// Copy & free the old ringbuffer data.
|
||||
|
||||
+16
-51
@@ -258,8 +258,6 @@ static int open_internal(const stream_info_t *sinfo, const char *url, int flags,
|
||||
|
||||
assert(s->seekable == !!s->seek);
|
||||
|
||||
s->uncached_type = s->type;
|
||||
|
||||
if (s->mime_type)
|
||||
MP_VERBOSE(s, "Mime-type: '%s'\n", s->mime_type);
|
||||
|
||||
@@ -328,7 +326,7 @@ stream_t *open_output_stream(const char *filename, struct mpv_global *global)
|
||||
|
||||
static bool stream_reconnect(stream_t *s)
|
||||
{
|
||||
if (!s->streaming || s->uncached_stream || !s->seekable || !s->cancel)
|
||||
if (!s->streaming || s->caching || !s->seekable || !s->cancel)
|
||||
return false;
|
||||
|
||||
int64_t pos = s->pos;
|
||||
@@ -353,57 +351,25 @@ static bool stream_reconnect(stream_t *s)
|
||||
return false;
|
||||
}
|
||||
|
||||
static void stream_capture_write(stream_t *s, void *buf, size_t len)
|
||||
{
|
||||
if (s->capture_file && len > 0) {
|
||||
if (fwrite(buf, len, 1, s->capture_file) < 1) {
|
||||
MP_ERR(s, "Error writing capture file: %s\n", mp_strerror(errno));
|
||||
stream_set_capture_file(s, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void stream_set_capture_file(stream_t *s, const char *filename)
|
||||
{
|
||||
if (!bstr_equals(bstr0(s->capture_filename), bstr0(filename))) {
|
||||
if (s->capture_file)
|
||||
fclose(s->capture_file);
|
||||
talloc_free(s->capture_filename);
|
||||
s->capture_file = NULL;
|
||||
s->capture_filename = NULL;
|
||||
if (filename) {
|
||||
s->capture_file = fopen(filename, "ab");
|
||||
if (s->capture_file) {
|
||||
s->capture_filename = talloc_strdup(NULL, filename);
|
||||
if (s->buf_pos < s->buf_len)
|
||||
stream_capture_write(s, s->buffer, s->buf_len);
|
||||
} else {
|
||||
MP_ERR(s, "Error opening capture file: %s\n", mp_strerror(errno));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Read function bypassing the local stream buffer. This will not write into
|
||||
// s->buffer, but into buf[0..len] instead.
|
||||
// Returns 0 on error or EOF, and length of bytes read on success.
|
||||
// Partial reads are possible, even if EOF is not reached.
|
||||
static int stream_read_unbuffered(stream_t *s, void *buf, int len)
|
||||
{
|
||||
int orig_len = len;
|
||||
int res = 0;
|
||||
s->buf_pos = s->buf_len = 0;
|
||||
// we will retry even if we already reached EOF previously.
|
||||
len = s->fill_buffer ? s->fill_buffer(s, buf, len) : -1;
|
||||
if (len < 0)
|
||||
len = 0;
|
||||
if (len == 0) {
|
||||
if (s->fill_buffer && !mp_cancel_test(s->cancel))
|
||||
res = s->fill_buffer(s, buf, len);
|
||||
if (res <= 0) {
|
||||
// just in case this is an error e.g. due to network
|
||||
// timeout reset and retry
|
||||
// do not retry if this looks like proper eof
|
||||
int64_t size = stream_get_size(s);
|
||||
if (!s->eof && s->pos != size && stream_reconnect(s)) {
|
||||
s->eof = 1; // make sure EOF is set to ensure no endless recursion
|
||||
return stream_read_unbuffered(s, buf, orig_len);
|
||||
return stream_read_unbuffered(s, buf, len);
|
||||
}
|
||||
|
||||
s->eof = 1;
|
||||
@@ -411,9 +377,8 @@ static int stream_read_unbuffered(stream_t *s, void *buf, int len)
|
||||
}
|
||||
// When reading succeeded we are obviously not at eof.
|
||||
s->eof = 0;
|
||||
s->pos += len;
|
||||
stream_capture_write(s, buf, len);
|
||||
return len;
|
||||
s->pos += res;
|
||||
return res;
|
||||
}
|
||||
|
||||
static int stream_fill_buffer_by(stream_t *s, int64_t len)
|
||||
@@ -647,11 +612,9 @@ void free_stream(stream_t *s)
|
||||
if (!s)
|
||||
return;
|
||||
|
||||
stream_set_capture_file(s, NULL);
|
||||
|
||||
if (s->close)
|
||||
s->close(s);
|
||||
free_stream(s->uncached_stream);
|
||||
free_stream(s->underlying);
|
||||
talloc_free(s);
|
||||
}
|
||||
|
||||
@@ -670,8 +633,8 @@ stream_t *open_memory_stream(void *data, int len)
|
||||
static stream_t *open_cache(stream_t *orig, const char *name)
|
||||
{
|
||||
stream_t *cache = new_stream();
|
||||
cache->uncached_type = orig->uncached_type;
|
||||
cache->uncached_stream = orig;
|
||||
cache->underlying = orig;
|
||||
cache->caching = true;
|
||||
cache->seekable = true;
|
||||
cache->mode = STREAM_READ;
|
||||
cache->read_chunk = 4 * STREAM_BUFFER_SIZE;
|
||||
@@ -682,6 +645,8 @@ static stream_t *open_cache(stream_t *orig, const char *name)
|
||||
cache->lavf_type = talloc_strdup(cache, orig->lavf_type);
|
||||
cache->streaming = orig->streaming,
|
||||
cache->is_network = orig->is_network;
|
||||
cache->is_local_file = orig->is_local_file;
|
||||
cache->is_directory = orig->is_directory;
|
||||
cache->cancel = orig->cancel;
|
||||
cache->global = orig->global;
|
||||
|
||||
@@ -722,7 +687,7 @@ static int stream_enable_cache(stream_t **stream, struct mp_cache_opts *opts)
|
||||
|
||||
stream_t *fcache = open_cache(orig, "file-cache");
|
||||
if (stream_file_cache_init(fcache, orig, &use_opts) <= 0) {
|
||||
fcache->uncached_stream = NULL; // don't free original stream
|
||||
fcache->underlying = NULL; // don't free original stream
|
||||
free_stream(fcache);
|
||||
fcache = orig;
|
||||
}
|
||||
@@ -731,10 +696,10 @@ static int stream_enable_cache(stream_t **stream, struct mp_cache_opts *opts)
|
||||
|
||||
int res = stream_cache_init(cache, fcache, &use_opts);
|
||||
if (res <= 0) {
|
||||
cache->uncached_stream = NULL; // don't free original stream
|
||||
cache->underlying = NULL; // don't free original stream
|
||||
free_stream(cache);
|
||||
if (fcache != orig) {
|
||||
fcache->uncached_stream = NULL;
|
||||
fcache->underlying = NULL;
|
||||
free_stream(fcache);
|
||||
}
|
||||
} else {
|
||||
|
||||
+4
-22
@@ -28,20 +28,6 @@
|
||||
|
||||
#include "misc/bstr.h"
|
||||
|
||||
enum streamtype {
|
||||
STREAMTYPE_GENERIC = 0,
|
||||
STREAMTYPE_FILE,
|
||||
STREAMTYPE_DIR,
|
||||
STREAMTYPE_DVB,
|
||||
STREAMTYPE_DVD,
|
||||
STREAMTYPE_BLURAY,
|
||||
STREAMTYPE_TV,
|
||||
STREAMTYPE_MF,
|
||||
STREAMTYPE_EDL,
|
||||
STREAMTYPE_AVDEVICE,
|
||||
STREAMTYPE_CDDA,
|
||||
};
|
||||
|
||||
#define STREAM_BUFFER_SIZE 2048
|
||||
#define STREAM_MAX_SECTOR_SIZE (8 * 1024)
|
||||
|
||||
@@ -178,8 +164,6 @@ typedef struct stream {
|
||||
// Close
|
||||
void (*close)(struct stream *s);
|
||||
|
||||
enum streamtype type; // see STREAMTYPE_*
|
||||
enum streamtype uncached_type; // if stream is cache, type of wrapped str.
|
||||
int sector_size; // sector size (seek will be aligned on this size if non 0)
|
||||
int read_chunk; // maximum amount of data to read at once to limit latency
|
||||
unsigned int buf_pos, buf_len;
|
||||
@@ -197,16 +181,16 @@ typedef struct stream {
|
||||
bool fast_skip : 1; // consider stream fast enough to fw-seek by skipping
|
||||
bool is_network : 1; // original stream_info_t.is_network flag
|
||||
bool allow_caching : 1; // stream cache makes sense
|
||||
bool caching : 1; // is a cache, or accesses a cache
|
||||
bool is_local_file : 1; // from the filesystem
|
||||
bool is_directory : 1; // directory on the filesystem
|
||||
bool access_references : 1; // open other streams
|
||||
struct mp_log *log;
|
||||
struct mpv_global *global;
|
||||
|
||||
struct mp_cancel *cancel; // cancellation notification
|
||||
|
||||
FILE *capture_file;
|
||||
char *capture_filename;
|
||||
|
||||
struct stream *uncached_stream; // underlying stream for cache wrapper
|
||||
struct stream *underlying; // e.g. cache wrapper
|
||||
|
||||
// Includes additional padding in case sizes get rounded up by sector size.
|
||||
unsigned char buffer[];
|
||||
@@ -214,8 +198,6 @@ typedef struct stream {
|
||||
|
||||
int stream_fill_buffer(stream_t *s);
|
||||
|
||||
void stream_set_capture_file(stream_t *s, const char *filename);
|
||||
|
||||
struct mp_cache_opts;
|
||||
bool stream_wants_cache(stream_t *stream, struct mp_cache_opts *opts);
|
||||
int stream_enable_cache_defaults(stream_t **stream);
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
|
||||
static int open_f(stream_t *stream)
|
||||
{
|
||||
stream->type = STREAMTYPE_AVDEVICE;
|
||||
stream->demuxer = "lavf";
|
||||
stream->allow_caching = false;
|
||||
|
||||
return STREAM_OK;
|
||||
}
|
||||
|
||||
@@ -425,7 +425,7 @@ static int bluray_stream_open_internal(stream_t *s)
|
||||
|
||||
char *time = mp_format_time(ti->duration / 90000, false);
|
||||
MP_VERBOSE(s, "idx: %3d duration: %s (playlist: %05d.mpls)\n",
|
||||
i + 1, time, ti->playlist);
|
||||
i, time, ti->playlist);
|
||||
talloc_free(time);
|
||||
|
||||
/* try to guess which title may contain the main movie */
|
||||
@@ -450,7 +450,6 @@ static int bluray_stream_open_internal(stream_t *s)
|
||||
s->fill_buffer = bluray_stream_fill_buffer;
|
||||
s->close = bluray_stream_close;
|
||||
s->control = bluray_stream_control;
|
||||
s->type = STREAMTYPE_BLURAY;
|
||||
s->sector_size = BLURAY_SECTOR_SIZE;
|
||||
s->priv = b;
|
||||
s->demuxer = "+disc";
|
||||
|
||||
@@ -383,7 +383,6 @@ static int open_cdda(stream_t *st)
|
||||
|
||||
st->streaming = true;
|
||||
|
||||
st->type = STREAMTYPE_CDDA;
|
||||
st->demuxer = "+disc";
|
||||
|
||||
print_cdtext(st, 0);
|
||||
|
||||
+1
-2
@@ -931,6 +931,7 @@ static int dvb_open(stream_t *stream)
|
||||
return STREAM_ERROR;
|
||||
}
|
||||
|
||||
stream->priv = mp_get_config_group(stream, stream->global, &stream_dvb_conf);
|
||||
dvb_state_t* state = dvb_get_state(stream);
|
||||
|
||||
dvb_priv_t *p = stream->priv;
|
||||
@@ -990,7 +991,6 @@ static int dvb_open(stream_t *stream)
|
||||
return STREAM_ERROR;
|
||||
}
|
||||
|
||||
stream->type = STREAMTYPE_DVB;
|
||||
stream->fill_buffer = dvb_streaming_read;
|
||||
stream->close = dvbin_close;
|
||||
stream->control = dvbin_stream_control;
|
||||
@@ -1010,7 +1010,6 @@ dvb_state_t *dvb_get_state(stream_t *stream)
|
||||
}
|
||||
struct mp_log *log = stream->log;
|
||||
struct mpv_global *global = stream->global;
|
||||
stream->priv = mp_get_config_group(stream, stream->global, &stream_dvb_conf);
|
||||
dvb_priv_t *priv = stream->priv;
|
||||
int type, size;
|
||||
char filename[30], *name;
|
||||
|
||||
@@ -904,7 +904,6 @@ static int open_s_internal(stream_t *stream)
|
||||
|
||||
// ... (unimplemented)
|
||||
// return NULL;
|
||||
stream->type = STREAMTYPE_DVD;
|
||||
stream->demuxer = "+disc";
|
||||
stream->lavf_type = "mpeg";
|
||||
stream->sector_size = 2048;
|
||||
|
||||
@@ -507,7 +507,6 @@ static int open_s_internal(stream_t *stream)
|
||||
stream->fill_buffer = fill_buffer;
|
||||
stream->control = control;
|
||||
stream->close = stream_dvdnav_close;
|
||||
stream->type = STREAMTYPE_DVD;
|
||||
stream->demuxer = "+disc";
|
||||
stream->lavf_type = "mpeg";
|
||||
stream->allow_caching = false;
|
||||
@@ -596,7 +595,7 @@ unsupported:
|
||||
}
|
||||
|
||||
const stream_info_t stream_info_ifo_dvdnav = {
|
||||
.name = "ifo/dvdnav",
|
||||
.name = "ifo_dvdnav",
|
||||
.open = ifo_dvdnav_stream_open,
|
||||
.protocols = (const char*const[]){ "file", "", NULL },
|
||||
};
|
||||
+1
-1
@@ -5,8 +5,8 @@
|
||||
|
||||
static int s_open (struct stream *stream)
|
||||
{
|
||||
stream->type = STREAMTYPE_EDL;
|
||||
stream->demuxer = "edl";
|
||||
stream->allow_caching = false;
|
||||
|
||||
return STREAM_OK;
|
||||
}
|
||||
|
||||
@@ -234,7 +234,7 @@ static int open_f(stream_t *stream)
|
||||
.fd = -1
|
||||
};
|
||||
stream->priv = p;
|
||||
stream->type = STREAMTYPE_FILE;
|
||||
stream->is_local_file = true;
|
||||
|
||||
bool write = stream->mode == STREAM_WRITE;
|
||||
int m = O_CLOEXEC | (write ? O_RDWR | O_CREAT | O_TRUNC : O_RDONLY);
|
||||
@@ -281,7 +281,7 @@ static int open_f(stream_t *stream)
|
||||
if (fstat(p->fd, &st) == 0) {
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
p->use_poll = false;
|
||||
stream->type = STREAMTYPE_DIR;
|
||||
stream->is_directory = true;
|
||||
stream->allow_caching = false;
|
||||
MP_INFO(stream, "This is a directory - adding to playlist.\n");
|
||||
}
|
||||
|
||||
@@ -416,6 +416,7 @@ const stream_info_t stream_info_ffmpeg = {
|
||||
.protocols = (const char *const[]){
|
||||
"rtmp", "rtsp", "http", "https", "mms", "mmst", "mmsh", "mmshttp", "rtp",
|
||||
"httpproxy", "hls", "rtmpe", "rtmps", "rtmpt", "rtmpte", "rtmpts", "srtp",
|
||||
"data",
|
||||
NULL },
|
||||
.can_write = true,
|
||||
.is_safe = true,
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
static int
|
||||
mf_stream_open (stream_t *stream)
|
||||
{
|
||||
stream->type = STREAMTYPE_MF;
|
||||
stream->demuxer = "mf";
|
||||
stream->allow_caching = false;
|
||||
|
||||
|
||||
@@ -39,7 +39,6 @@ static int
|
||||
tv_stream_open (stream_t *stream)
|
||||
{
|
||||
|
||||
stream->type = STREAMTYPE_TV;
|
||||
stream->close=tv_stream_close;
|
||||
stream->demuxer = "tv";
|
||||
stream->allow_caching = false;
|
||||
|
||||
+1
-4
@@ -145,7 +145,7 @@ const struct m_sub_options tv_params_conf = {
|
||||
|
||||
tvi_handle_t *tv_new_handle(int size, struct mp_log *log, const tvi_functions_t *functions)
|
||||
{
|
||||
tvi_handle_t *h = malloc(sizeof(*h));
|
||||
tvi_handle_t *h = calloc(1, sizeof(*h));
|
||||
|
||||
if (!h)
|
||||
return NULL;
|
||||
@@ -159,12 +159,9 @@ tvi_handle_t *tv_new_handle(int size, struct mp_log *log, const tvi_functions_t
|
||||
|
||||
h->log = log;
|
||||
h->functions = functions;
|
||||
h->seq = 0;
|
||||
h->chanlist = -1;
|
||||
h->chanlist_s = NULL;
|
||||
h->norm = -1;
|
||||
h->channel = -1;
|
||||
h->scan = NULL;
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
+3
-2
@@ -20,6 +20,7 @@
|
||||
#include "config.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include "common/common.h"
|
||||
#include "video/img_fourcc.h"
|
||||
#include "tv.h"
|
||||
|
||||
@@ -105,7 +106,7 @@ static int do_control(priv_t *priv, int cmd, void *arg)
|
||||
static double grab_video_frame(priv_t *priv, char *buffer, int len)
|
||||
{
|
||||
memset(buffer, 0x42, len);
|
||||
return 1;
|
||||
return MP_NOPTS_VALUE;
|
||||
}
|
||||
|
||||
static int get_video_framesize(priv_t *priv)
|
||||
@@ -117,7 +118,7 @@ static int get_video_framesize(priv_t *priv)
|
||||
static double grab_audio_frame(priv_t *priv, char *buffer, int len)
|
||||
{
|
||||
memset(buffer, 0x42, len);
|
||||
return 1;
|
||||
return MP_NOPTS_VALUE;
|
||||
}
|
||||
|
||||
static int get_audio_framesize(priv_t *priv)
|
||||
|
||||
Loaded 100 of 174 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user