Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef2d6ed125 | ||
|
|
fd1bc95b48 | ||
|
|
d7a7e9a8c8 | ||
|
|
4a8647b6b8 | ||
|
|
4b5de33e89 | ||
|
|
9d2bcd2ef9 | ||
|
|
e392d6610d | ||
|
|
78d808c5bd | ||
|
|
b9ba9a898a | ||
|
|
e2a54bb1ca | ||
|
|
ec4a8bdb3d | ||
|
|
dc6f8d4a0a | ||
|
|
9024cb1685 | ||
|
|
5d74ba98ce | ||
|
|
87190969a7 | ||
|
|
367e9fb7f1 | ||
|
|
2ded41d2be | ||
|
|
062349ff5b | ||
|
|
eab92cec60 | ||
|
|
3759a3f40b | ||
|
|
d81b5690df | ||
|
|
8c2e299b84 | ||
|
|
469666b75b | ||
|
|
896a97c2e2 | ||
|
|
56c52bd2e1 | ||
|
|
355a145a48 | ||
|
|
3a21d71ad0 | ||
|
|
7ca4a453e0 | ||
|
|
52a0cbe456 | ||
|
|
5640ceb838 | ||
|
|
cfed84af2b | ||
|
|
259f219438 | ||
|
|
c7b73edc65 | ||
|
|
32cc190a55 | ||
|
|
39ae261cc5 | ||
|
|
78bcbe289e | ||
|
|
91a3bda6f5 | ||
|
|
7fd3dbcd52 | ||
|
|
d41f0a54b0 | ||
|
|
d4ee5e5a8a | ||
|
|
b2e5eb13bc | ||
|
|
088a0075c5 | ||
|
|
9f70117233 | ||
|
|
eccabeb81c | ||
|
|
0c4a73fa21 | ||
|
|
fa7beaad1f | ||
|
|
00091411ec | ||
|
|
0b144eac39 | ||
|
|
c30aa23401 | ||
|
|
efa9fe8edf | ||
|
|
3c3361217c | ||
|
|
356e703510 | ||
|
|
f3c35d8108 | ||
|
|
dea738a245 | ||
|
|
251299da4f | ||
|
|
2e3db648b5 | ||
|
|
08a4af5e6e | ||
|
|
0432ab8f09 | ||
|
|
0a1c87464b | ||
|
|
48f60e182a | ||
|
|
98af572484 | ||
|
|
5bcb22beea | ||
|
|
546f7472fb | ||
|
|
45687c17f7 | ||
|
|
2f986a5980 | ||
|
|
27b59e1940 | ||
|
|
aaf6e3b58d | ||
|
|
f95cde60ff | ||
|
|
3fe5ff99c6 | ||
|
|
f918ec4b2b | ||
|
|
c3e11f7b7c | ||
|
|
f3f4e048d8 | ||
|
|
46b60a3e72 | ||
|
|
f127869037 | ||
|
|
cb4342dda4 | ||
|
|
ebe798a333 | ||
|
|
dcfde2934d | ||
|
|
6bb48a10db | ||
|
|
3623cec7d2 | ||
|
|
d60db967bd | ||
|
|
99d9921f39 | ||
|
|
16d276308a | ||
|
|
b4acfcc8aa | ||
|
|
c6b83fec7d | ||
|
|
c0ef3cf9c2 | ||
|
|
8f51418ca4 | ||
|
|
7d01a16f64 | ||
|
|
f53c7f1a78 | ||
|
|
7531275f62 | ||
|
|
fde784129f | ||
|
|
bd9c0a10e5 | ||
|
|
e11a20a812 | ||
|
|
64cd0f4292 | ||
|
|
510c6a1be4 | ||
|
|
fb8deb69a6 | ||
|
|
77e1e8e38e | ||
|
|
79974e7ad9 | ||
|
|
ea18cb99ba | ||
|
|
c5361d12d8 | ||
|
|
358932a109 | ||
|
|
831fc4f012 | ||
|
|
f29bba1123 | ||
|
|
48f9ea7104 | ||
|
|
ae94f329a9 | ||
|
|
85488f6892 | ||
|
|
4a4a9f3302 | ||
|
|
588b2f48e5 | ||
|
|
e656889624 | ||
|
|
91a1b17104 | ||
|
|
6e45e1de77 | ||
|
|
d191d76e52 | ||
|
|
f53d73b9dc | ||
|
|
79f48500e2 | ||
|
|
b5bc93d009 | ||
|
|
a14f9249c4 | ||
|
|
8b47e5daa4 | ||
|
|
bf2e2bf3e8 | ||
|
|
43e811cb4b | ||
|
|
2d44dfaba9 | ||
|
|
f4d62dc4a0 | ||
|
|
374600cec0 | ||
|
|
5a6ba2a4fd | ||
|
|
e246c3f060 | ||
|
|
61afe3820a | ||
|
|
60048b7eb9 | ||
|
|
7be98ef1b2 | ||
|
|
2eac58eaa9 | ||
|
|
065bb635d9 | ||
|
|
c5827387fd | ||
|
|
1e00bcc14c | ||
|
|
7635438592 | ||
|
|
e518bf2c72 | ||
|
|
d47b708f00 | ||
|
|
2fb2f9b93a | ||
|
|
995c47da9a | ||
|
|
da9590d368 | ||
|
|
34a33ebd4d | ||
|
|
e58ecc3630 | ||
|
|
6d83455a95 | ||
|
|
35be389031 | ||
|
|
885e991312 | ||
|
|
26b6873ffd | ||
|
|
71a8aa2e9a | ||
|
|
16b48a429d | ||
|
|
f2110f582d | ||
|
|
8660b4c9f0 | ||
|
|
ab6fac43b4 | ||
|
|
5d2f1da7c5 | ||
|
|
771a8bf5c6 | ||
|
|
08cd50b84b | ||
|
|
0b1ef81498 | ||
|
|
fc76966d9e | ||
|
|
0fe62bc1d2 | ||
|
|
d72bcc8041 | ||
|
|
329a7147d0 | ||
|
|
4f4eea6b44 | ||
|
|
33e72ed451 | ||
|
|
6a3da439cd | ||
|
|
f98e1b0b96 | ||
|
|
7cd1a3c766 | ||
|
|
46f1ce1e2f | ||
|
|
be230d16e5 | ||
|
|
5b6cce2b73 | ||
|
|
923e3c7b20 | ||
|
|
d81fb97f45 | ||
|
|
3abf9c9204 | ||
|
|
ea23a86f49 | ||
|
|
8ed32e90c9 | ||
|
|
37cf92c07a | ||
|
|
59972fbfe1 | ||
|
|
f110552898 | ||
|
|
315e2e3da8 | ||
|
|
b57debe5b3 | ||
|
|
8c7f9dc1a8 | ||
|
|
823c353faa | ||
|
|
06219c7f88 | ||
|
|
5d47f9a565 | ||
|
|
1298c29dc7 | ||
|
|
76a73a0a5d | ||
|
|
b2ce00ed56 | ||
|
|
d057e7a142 | ||
|
|
3738bfb451 | ||
|
|
8523e1fd95 | ||
|
|
fd5098a409 | ||
|
|
c0f272fcd1 | ||
|
|
1b71cfccba | ||
|
|
549a9ea6fa | ||
|
|
614efea3e6 | ||
|
|
c57304a591 | ||
|
|
2d6da37b6b | ||
|
|
e40851ea22 | ||
|
|
9ed5dae480 | ||
|
|
c6953bfa8c | ||
|
|
deb1c3c7a8 | ||
|
|
9ca1592f3f | ||
|
|
17c5738cb4 | ||
|
|
d5615102d5 | ||
|
|
40c9b38023 | ||
|
|
dc9a5cbfd7 | ||
|
|
65499d863a | ||
|
|
6e6c32ed6c | ||
|
|
f9fe5d06ad | ||
|
|
f3b6966d14 | ||
|
|
740fdc139f | ||
|
|
247ec0cb84 | ||
|
|
13a63331b6 | ||
|
|
9278ce98f7 | ||
|
|
4ce53025cb | ||
|
|
3e58ce96ac | ||
|
|
acb74236ac | ||
|
|
c5094206ce | ||
|
|
24478a8a72 | ||
|
|
e081e46950 | ||
|
|
22c76e85db | ||
|
|
d1d864546c |
No files matched your search
@@ -10,5 +10,12 @@ If you're not using git master or the latest release, update.
|
||||
|
||||
### Log files
|
||||
|
||||
Make a log file made with -v or --log-file=output.txt, paste it to sprunge.us,
|
||||
and replace this text with a link to it.
|
||||
Make a log file made with -v or --log-file=output.txt, paste it to
|
||||
http://sprunge.us or a similar site, and replace this text with a link to it.
|
||||
|
||||
Do not bother reporting a bug if you do not provide the required information.
|
||||
|
||||
### Sample files
|
||||
|
||||
Sample files needed to reproduce this issue can be uploaded to https://0x0.st/
|
||||
or similar sites. (Only needed if the issue cannot be reproduced without it.)
|
||||
@@ -32,7 +32,13 @@ API changes
|
||||
|
||||
::
|
||||
|
||||
--- mpv 0.17.1 ---
|
||||
--- mpv 0.19.0
|
||||
1.22 - add stream_cb API for custom protocols
|
||||
--- mpv 0.18.1 ---
|
||||
---- - remove "status" log level from mpv_request_log_messages() docs. This
|
||||
is 100% equivalent to "v". The behavior is still the same, thus no
|
||||
actual API change.
|
||||
--- mpv 0.18.0 ---
|
||||
1.21 - mpv_set_property() changes behavior with MPV_FORMAT_NODE. Before this
|
||||
change it rejected mpv_nodes with format==MPV_FORMAT_STRING if the
|
||||
property was not a string or did not have special mechanisms in place
|
||||
|
||||
@@ -173,6 +173,24 @@ sed -i 's_/mingw64/bin_/mingw64/lib_' /mingw64/lib/pkgconfig/mpv.pc
|
||||
rmdir /mingw64/bin/pkgconfig
|
||||
```
|
||||
|
||||
Linking libmpv with MSVC programs
|
||||
---------------------------------
|
||||
|
||||
You can build C++ programs in Visual Studio and link them with libmpv. To do
|
||||
this, you need a Visual Studio which supports ``stdint.h`` (recent ones do),
|
||||
and you need to create a import library for the mpv DLL:
|
||||
|
||||
```bash
|
||||
lib /def:mpv.def /name:mpv-1.dll /out:mpv.lib /MACHINE:X64
|
||||
```
|
||||
|
||||
The string in the ``/name:`` parameter must match the filename of the DLL (this
|
||||
is simply the filename the MSVC linker will use). The ``mpv.def`` can be
|
||||
retrieved from the mpv build directory, or can be produced by MingGW's
|
||||
gendef.exe helper from the mpv DLL.
|
||||
|
||||
Static linking is not possible.
|
||||
|
||||
Running mpv
|
||||
-----------
|
||||
|
||||
|
||||
@@ -19,7 +19,28 @@ Interface changes
|
||||
|
||||
::
|
||||
|
||||
--- mpv 0.17.1 ---
|
||||
--- mpv 0.19.0 ---
|
||||
- deprecate "balance" option/property (no replacement)
|
||||
--- mpv 0.18.1 ---
|
||||
- deprecate --heartbeat-cmd
|
||||
- remove --softvol=no capability:
|
||||
- deprecate --softvol, it now does nothing
|
||||
- --volume, --mute, and the corresponding properties now always control
|
||||
softvol, and behave as expected without surprises (e.g. you can set
|
||||
them normally while no audio is initialized)
|
||||
- rename --softvol-max to --volume-max (deprecated alias is added)
|
||||
- the --volume-restore-data option and property are removed without
|
||||
replacement. They were _always_ internal, and used for watch-later
|
||||
resume/restore. Now --volume/--mute are saved directly instead.
|
||||
- the previous point means resuming files with older watch-later configs
|
||||
will print an error about missing --volume-restore-data (which you can
|
||||
ignore), and will not restore the previous value
|
||||
- as a consequence, volume controls will no longer control PulseAudio
|
||||
per-application value, or use the system mixer's per-application
|
||||
volume processing
|
||||
- system or per-application volume can still be controlled with the
|
||||
ao-volume and ao-mute properties (there are no command line options)
|
||||
--- mpv 0.18.0 ---
|
||||
- now ab-loops are active even if one of the "ab-loop-a"/"-b" properties is
|
||||
unset ("no"), in which case the start of the file is used if the A loop
|
||||
point is unset, and the end of the file for an unset B loop point
|
||||
@@ -27,7 +48,7 @@ Interface changes
|
||||
(also needs --embeddedfonts=no)
|
||||
- add "hwdec-interop" and "hwdec-current" properties
|
||||
- deprecated "hwdec-active" and "hwdec-detected" properties (to be removed
|
||||
in mpv 0.19.0)
|
||||
in mpv 0.20.0)
|
||||
- choice option/property values that are "yes" or "no" will now be returned
|
||||
as booleans when using the mpv_node functions in the client API, the
|
||||
"native" property accessors in Lua, and the JSON API. They can be set as
|
||||
|
||||
@@ -312,34 +312,6 @@ Available filters are:
|
||||
``mpv '--af=format=channels=5.1' '--audio-channels=5.1'`` would always force
|
||||
remixing audio to 5.1 and output it like this.
|
||||
|
||||
``delay[=[ch1,ch2,...]]``
|
||||
Delays the sound to the loudspeakers such that the sound from the
|
||||
different channels arrives at the listening position simultaneously. It is
|
||||
only useful if you have more than 2 loudspeakers.
|
||||
|
||||
``[ch1,ch2,...]``
|
||||
The delay in ms that should be imposed on each channel (floating point
|
||||
number between 0 and 1000).
|
||||
|
||||
To calculate the required delay for the different channels, do as follows:
|
||||
|
||||
1. Measure the distance to the loudspeakers in meters in relation to your
|
||||
listening position, giving you the distances s1 to s5 (for a 5.1
|
||||
system). There is no point in compensating for the subwoofer (you will
|
||||
not hear the difference anyway).
|
||||
|
||||
2. Subtract the distances s1 to s5 from the maximum distance, i.e.
|
||||
``s[i] = max(s) - s[i]; i = 1...5``.
|
||||
|
||||
3. Calculate the required delays in ms as ``d[i] = 1000*s[i]/342; i =
|
||||
1...5``.
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
``mpv --af=delay=[10.5,10.5,0,0,7,0] media.avi``
|
||||
Would delay front left and right by 10.5 ms, the two rear channels
|
||||
and the subwoofer by 0 ms and the center channel by 7 ms.
|
||||
|
||||
``drc[=method:target]``
|
||||
Applies dynamic range compression. This maximizes the volume by compressing
|
||||
the audio signal's dynamic range. (Formerly called ``volnorm``.)
|
||||
|
||||
+70
-45
@@ -459,6 +459,8 @@ Input Commands that are Possibly Subject to Change
|
||||
Remove all filters. Note that like the other sub-commands, this does
|
||||
not control automatically inserted filters.
|
||||
|
||||
The argument is always needed. E.g. in case of ``clr`` use ``vf clr ""``.
|
||||
|
||||
You can assign labels to filter by prefixing them with ``@name:`` (where
|
||||
``name`` is a user-chosen arbitrary identifier). Labels can be used to
|
||||
refer to filters by name in all of the filter chain modification commands.
|
||||
@@ -561,29 +563,20 @@ Input Commands that are Possibly Subject to Change
|
||||
the resolution is reduced to that of the video's. You can read the
|
||||
``osd-width`` and ``osd-height`` properties. At least with ``--vo-xv`` and
|
||||
anamorphic video (such as DVD), ``osd-par`` should be read as well, and the
|
||||
overlay should be aspect-compensated. (Future directions: maybe mpv should
|
||||
take care of some of these things automatically, but it's hard to tell
|
||||
where to draw the line.)
|
||||
overlay should be aspect-compensated.
|
||||
|
||||
``id`` is an integer between 0 and 63 identifying the overlay element. The
|
||||
ID can be used to add multiple overlay parts, update a part by using this
|
||||
command with an already existing ID, or to remove a part with
|
||||
``overlay-remove``. Using a previously unused ID will add a new overlay,
|
||||
while reusing an ID will update it. (Future directions: there should be
|
||||
something to ensure different programs wanting to create overlays don't
|
||||
conflict with each others, should that ever be needed.)
|
||||
while reusing an ID will update it.
|
||||
|
||||
``x`` and ``y`` specify the position where the OSD should be displayed.
|
||||
|
||||
``file`` specifies the file the raw image data is read from. It can be
|
||||
either a numeric UNIX file descriptor prefixed with ``@`` (e.g. ``@4``),
|
||||
or a filename. The file will be mapped into memory with ``mmap()``. Some VOs
|
||||
will pass the mapped pointer directly to display APIs (e.g. opengl or
|
||||
vdpau), so no actual copying is involved. Truncating the source file while
|
||||
the overlay is active will crash the player. You shouldn't change the data
|
||||
while the overlay is active, because the data is essentially accessed at
|
||||
random points. Instead, call ``overlay-add`` again (preferably with a
|
||||
different memory region to prevent tearing).
|
||||
or a filename. The file will be mapped into memory with ``mmap()``,
|
||||
copied, and unmapped before the command returns (changed in mpv 0.18.1).
|
||||
|
||||
It is also possible to pass a raw memory address for use as bitmap memory
|
||||
by passing a memory address as integer prefixed with an ``&`` character.
|
||||
@@ -616,15 +609,14 @@ Input Commands that are Possibly Subject to Change
|
||||
(Technically, the minimum size would be ``stride * (h - 1) + w * 4``, but
|
||||
for simplicity, the player will access all ``stride * h`` bytes.)
|
||||
|
||||
.. admonition:: Warning
|
||||
.. note::
|
||||
|
||||
When updating the overlay, you should prepare a second shared memory
|
||||
region (e.g. make use of the offset parameter) and add this as overlay,
|
||||
instead of reusing the same memory every time. Otherwise, you might
|
||||
get the equivalent of tearing, when your application and mpv write/read
|
||||
the buffer at the same time. Also, keep in mind that mpv might access
|
||||
an overlay's memory at random times whenever it feels the need to do
|
||||
so, for example when redrawing the screen.
|
||||
Before mpv 0.18.1, you had to do manual "double buffering" when updating
|
||||
an overlay by replacing it with a different memory buffer. Since mpv
|
||||
0.18.1, the memory is simply copied and doesn't reference any of the
|
||||
memory indicated by the command's arguments after the commend returns.
|
||||
If you want to use this command before mpv 0.18.1, reads the old docs
|
||||
to see how to handle this correctly.
|
||||
|
||||
``overlay-remove <id>``
|
||||
Remove an overlay added with ``overlay-add`` and the same ID. Does nothing
|
||||
@@ -648,7 +640,7 @@ Input Commands that are Possibly Subject to Change
|
||||
The argument is the name of the binding.
|
||||
|
||||
It can optionally be prefixed with the name of the script, using ``/`` as
|
||||
separator, e.g. ``script_binding scriptname/bindingname``.
|
||||
separator, e.g. ``script-binding scriptname/bindingname``.
|
||||
|
||||
For completeness, here is how this command works internally. The details
|
||||
could change any time. On any matching key event, ``script-message-to``
|
||||
@@ -817,7 +809,7 @@ Input sections group a set of bindings, and enable or disable them at once.
|
||||
In ``input.conf``, each key binding is assigned to an input section, rather
|
||||
than actually having explicit text sections.
|
||||
|
||||
See also: ``enable_section`` and ``disable_section`` commands.
|
||||
See also: ``enable-section`` and ``disable-section`` commands.
|
||||
|
||||
Predefined bindings:
|
||||
|
||||
@@ -877,6 +869,12 @@ Property list
|
||||
looks better for display purposes. Use the ``path`` property to get an
|
||||
unmodified filename.)
|
||||
|
||||
This has a sub-property:
|
||||
|
||||
``filename/no-ext``
|
||||
Like the ``filename`` property, but if the text contains a ``.``, strip
|
||||
all text after the last ``.``. Usually this removes the file extension.
|
||||
|
||||
``file-size``
|
||||
Length in bytes of the source file/stream. (This is the same as
|
||||
``${stream-end}``. For ordered chapters and such, the
|
||||
@@ -1242,29 +1240,31 @@ Property list
|
||||
See ``--hr-seek``.
|
||||
|
||||
``mixer-active``
|
||||
Return ``yes`` if the audio mixer is active, ``no`` otherwise. This has
|
||||
implications for ``--softvol=no`` mode: if the mixer is inactive, changing
|
||||
``volume`` doesn't actually change anything on the system mixer. If the
|
||||
``--volume`` or ``--mute`` option are used, these might not be applied
|
||||
properly until the mixer becomes active either. (The options, if set, will
|
||||
just overwrite the mixer state at audio initialization.)
|
||||
Return ``yes`` if the audio mixer is active, ``no`` otherwise.
|
||||
|
||||
While the behavior with ``mixer-active==yes`` is relatively well-defined,
|
||||
the ``no`` case will provide possibly wrong or insignificant values.
|
||||
|
||||
Note that an active mixer does not necessarily imply active audio output,
|
||||
although this is implied in the current implementation.
|
||||
This option is relatively useless. Before mpv 0.18.1, it could be used to
|
||||
infer behavior of the ``volume`` property.
|
||||
|
||||
``volume`` (RW)
|
||||
Current volume (see ``--volume`` for details). Also see ``mixer-active``
|
||||
property.
|
||||
Current volume (see ``--volume`` for details).
|
||||
|
||||
``volume-max``
|
||||
Current maximum value the volume property can be set to. (This may depend
|
||||
on the ``--softvol-max`` option.)
|
||||
``volume-max`` (RW)
|
||||
Current maximum value the volume property can be set to. (Equivalent to the
|
||||
``--volume-max`` option.)
|
||||
|
||||
``mute`` (RW)
|
||||
Current mute status (``yes``/``no``). Also see ``mixer-active`` property.
|
||||
Current mute status (``yes``/``no``).
|
||||
|
||||
``ao-volume`` (RW)
|
||||
System volume. This property is available only if mpv audio output is
|
||||
currently active, and only if the underlying implementation supports volume
|
||||
control. What this option does depends on the API. For example, on ALSA
|
||||
this usually changes system-wide audio, while with PulseAudio this controls
|
||||
per-application volume.
|
||||
|
||||
``ao-mute`` (RW)
|
||||
Similar to ``ao-volume``, but controls the mute state. May be unimplemented
|
||||
even if ``ao-volume`` works.
|
||||
|
||||
``audio-delay`` (RW)
|
||||
See ``--audio-delay``.
|
||||
@@ -1327,6 +1327,8 @@ Property list
|
||||
It doesn't change the volumes of each channel, but instead sets up a pan
|
||||
matrix to mix the left and right channels.)
|
||||
|
||||
Deprecated.
|
||||
|
||||
``fullscreen`` (RW)
|
||||
See ``--fullscreen``.
|
||||
|
||||
@@ -1388,7 +1390,7 @@ Property list
|
||||
properties to see whether this was successful.
|
||||
|
||||
Unlike in mpv 0.9.x and before, this does not return the currently active
|
||||
hardware decoder. Since mpv 0.17.1, ``hwdec-current`` is available for
|
||||
hardware decoder. Since mpv 0.18.0, ``hwdec-current`` is available for
|
||||
this purpose.
|
||||
|
||||
``hwdec-current``
|
||||
@@ -1412,13 +1414,13 @@ Property list
|
||||
platform and VO.
|
||||
|
||||
``hwdec-active``
|
||||
Deprecated. To be removed in mpv 0.19.0. Use ``hwdec-current`` instead.
|
||||
Deprecated. To be removed in mpv 0.20.0. Use ``hwdec-current`` instead.
|
||||
|
||||
Return ``yes`` or ``no``, depending on whether any type of hardware decoding
|
||||
is actually in use.
|
||||
|
||||
``hwdec-detected``
|
||||
Deprecated. To be removed in mpv 0.19.0.
|
||||
Deprecated. To be removed in mpv 0.20.0.
|
||||
|
||||
If hardware decoding is active, this returns the hardware decoder in use.
|
||||
Otherwise, it returns either ``no``, or if applicable, the currently loaded
|
||||
@@ -1485,6 +1487,12 @@ Property list
|
||||
``video-params/gamma``
|
||||
The gamma function in use as string. (Exact values subject to change.)
|
||||
|
||||
``video-params/nom-peak``
|
||||
The video encoding's nominal peak brightness as float.
|
||||
|
||||
``video-params/sig-peak``
|
||||
The video file's tagged signal peak as float.
|
||||
|
||||
``video-params/chroma-location``
|
||||
Chroma location as string. (Exact values subject to change.)
|
||||
|
||||
@@ -1511,6 +1519,9 @@ Property list
|
||||
"colormatrix" MPV_FORMAT_STRING
|
||||
"colorlevels" MPV_FORMAT_STRING
|
||||
"primaries" MPV_FORMAT_STRING
|
||||
"gamma" MPV_FORMAT_STRING
|
||||
"nom-peak" MPV_FORMAT_DOUBLE
|
||||
"sig-peak" MPV_FORMAT_DOUBLE
|
||||
"chroma-location" MPV_FORMAT_STRING
|
||||
"rotate" MPV_FORMAT_INT64
|
||||
"stereo-in" MPV_FORMAT_STRING
|
||||
@@ -1591,7 +1602,7 @@ Property list
|
||||
|
||||
``osd-width``, ``osd-height``
|
||||
Last known OSD width (can be 0). This is needed if you want to use the
|
||||
``overlay_add`` command. It gives you the actual OSD size, which can be
|
||||
``overlay-add`` command. It gives you the actual OSD size, which can be
|
||||
different from the window size in some cases.
|
||||
|
||||
``osd-par``
|
||||
@@ -1802,6 +1813,16 @@ Property list
|
||||
``track-list/N/audio-channels`` (deprecated)
|
||||
Deprecated alias for ``track-list/N/demux-channel-count``.
|
||||
|
||||
``track-list/N/replaygain-track-peak``, ``track-list/N/replaygain-track-gain``
|
||||
Per-track replaygain values. Only available for audio tracks with
|
||||
corresponding information stored in the source file.
|
||||
|
||||
``track-list/N/replaygain-album-peak``, ``track-list/N/replaygain-album-gain``
|
||||
Per-album replaygain values. If the file has per-track but no per-album
|
||||
information, the per-album values will be copied from the per-track
|
||||
values currently. It's possible that future mpv versions will make
|
||||
these properties unavailable instead in this case.
|
||||
|
||||
When querying the property with the client API using ``MPV_FORMAT_NODE``,
|
||||
or with Lua ``mp.get_property_native``, this will return a mpv_node with
|
||||
the following contents:
|
||||
@@ -1831,6 +1852,10 @@ Property list
|
||||
"demux-samplerate" MPV_FORMAT_INT64
|
||||
"demux-fps" MPV_FORMAT_DOUBLE
|
||||
"audio-channels" MPV_FORMAT_INT64
|
||||
"replaygain-track-peak" MPV_FORMAT_DOUBLE
|
||||
"replaygain-track-gain" MPV_FORMAT_DOUBLE
|
||||
"replaygain-album-peak" MPV_FORMAT_DOUBLE
|
||||
"replaygain-album-gain" MPV_FORMAT_DOUBLE
|
||||
|
||||
``chapter-list``
|
||||
List of chapters, current entry marked. Currently, the raw property value
|
||||
@@ -2134,7 +2159,7 @@ Property list
|
||||
|
||||
(Note that if an option is marked as file-local, even ``options/`` will
|
||||
access the local value, and the ``old`` value, which will be restored on
|
||||
end of playback, can not be read or written until end of playback.)
|
||||
end of playback, cannot be read or written until end of playback.)
|
||||
|
||||
``option-info/<name>``
|
||||
Additional per-option information.
|
||||
|
||||
+2
-2
@@ -41,7 +41,7 @@ It's also possible to send input.conf style text-only commands:
|
||||
|
||||
::
|
||||
|
||||
> echo 'show_text ${playback-time}' | socat - /tmp/mpvsocket
|
||||
> echo 'show-text ${playback-time}' | socat - /tmp/mpvsocket
|
||||
|
||||
But you won't get a reply over the socket. (This particular command shows the
|
||||
playback time on the player's OSD.)
|
||||
@@ -65,7 +65,7 @@ You can send commands from a command prompt:
|
||||
|
||||
::
|
||||
|
||||
echo show_text ${playback-time} >\\.\pipe\mpvsocket
|
||||
echo show-text ${playback-time} >\\.\pipe\mpvsocket
|
||||
|
||||
To be able to simultaneously read and write from the IPC pipe, like on Linux,
|
||||
it's necessary to write an external program that uses overlapped file I/O (or
|
||||
|
||||
+3
-3
@@ -175,7 +175,7 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
Similar to ``mp.set_property``, but set the given property using its native
|
||||
type.
|
||||
|
||||
Since there are several data types which can not represented natively in
|
||||
Since there are several data types which cannot represented natively in
|
||||
Lua, this might not always work as expected. For example, while the Lua
|
||||
wrapper can do some guesswork to decide whether a Lua table is an array
|
||||
or a map, this would fail with empty tables. Also, there are not many
|
||||
@@ -541,8 +541,8 @@ Example command-line::
|
||||
--script-opts=myscript-optionA=TEST,myscript-optionB=0,myscript-optionC=yes
|
||||
|
||||
|
||||
mp.utils options
|
||||
----------------
|
||||
mp.utils functions
|
||||
------------------
|
||||
|
||||
This built-in module provides generic helper functions for Lua, and have
|
||||
strictly speaking nothing to do with mpv or video/audio playback. They are
|
||||
|
||||
+1
-1
@@ -946,7 +946,7 @@ For Windows-specifics, see `FILES ON WINDOWS`_ section.
|
||||
``~/.config/mpv/watch_later/``
|
||||
Contains temporary config files needed for resuming playback of files with
|
||||
the watch later feature. See for example the ``Q`` key binding, or the
|
||||
``quit_watch_later`` input command.
|
||||
``quit-watch-later`` input command.
|
||||
|
||||
Each file is a small config file which is loaded if the corresponding media
|
||||
file is loaded. It contains the playback position and some (not necessarily
|
||||
|
||||
+127
-63
@@ -196,7 +196,7 @@ Playback Control
|
||||
position and so can take some time depending on decoding performance. For
|
||||
some video formats, precise seeks are disabled. This option selects the
|
||||
default choice to use for seeks; it is possible to explicitly override that
|
||||
default in the definition of key bindings and in slave mode commands.
|
||||
default in the definition of key bindings and in input commands.
|
||||
|
||||
:no: Never use precise seeks.
|
||||
:absolute: Use precise seeks if the seek is to an absolute position in the
|
||||
@@ -374,7 +374,7 @@ Program Behavior
|
||||
|
||||
``--idle=<no|yes|once>``
|
||||
Makes mpv wait idly instead of quitting when there is no file to play.
|
||||
Mostly useful in slave mode, where mpv can be controlled through input
|
||||
Mostly useful in input mode, where mpv can be controlled through input
|
||||
commands.
|
||||
|
||||
``once`` will only idle at start and let the player close once the
|
||||
@@ -404,7 +404,7 @@ Program Behavior
|
||||
``--no-resume-playback``
|
||||
Do not restore playback position from the ``watch_later`` configuration
|
||||
subdirectory (usually ``~/.config/mpv/watch_later/``).
|
||||
See ``quit_watch_later`` input command.
|
||||
See ``quit-watch-later`` input command.
|
||||
|
||||
``--profile=<profile1,profile2,...>``
|
||||
Use the given profile(s), ``--profile=help`` displays a list of the
|
||||
@@ -586,7 +586,9 @@ Video
|
||||
:vaapi: requires ``--vo=opengl`` or ``--vo=vaapi`` (Linux only)
|
||||
:vaapi-copy: copies video back into system RAM (Linux with Intel GPUs only)
|
||||
:videotoolbox: requires ``--vo=opengl`` (OS X 10.8 and up only)
|
||||
:videotoolbox-copy: copies video back into system RAM (OS X 10.8 and up only)
|
||||
:dxva2: requires ``--vo=opengl:backend=angle`` or
|
||||
|
||||
``--vo=opengl:backend=dxinterop`` (Windows only)
|
||||
:dxva2-copy: copies video back to system RAM (Windows only)
|
||||
:d3d11va: requires ``--vo=opengl:backend=angle`` (Windows only)
|
||||
@@ -638,15 +640,15 @@ Video
|
||||
In some cases, RGB conversion is forced, which means the RGB conversion
|
||||
is performed by the hardware decoding API, instead of the OpenGL code
|
||||
used by ``--vo=opengl``. This means certain obscure colorspaces may
|
||||
not display correctly, and not certain filtering (such as debanding)
|
||||
can not be applied in an ideal way.
|
||||
not display correctly, not certain filtering (such as debanding)
|
||||
cannot be applied in an ideal way.
|
||||
|
||||
``vdpau`` is usually safe. If deinterlacing enabled (or the ``vdpaupp``
|
||||
video filter is active in general), it forces RGB conversion. The latter
|
||||
currently does not treat certain colorspaces like BT.2020 correctly
|
||||
(which is mostly a mpv-specific restriction). If the ``vdpauprb``
|
||||
retrieves image data without RGB conversion, but does not work with
|
||||
postprocessing.
|
||||
(which is mostly a mpv-specific restriction). The ``vdpauprb`` video
|
||||
filter retrieves image data without RGB conversion and is safe (but
|
||||
precludes use of vdpau postprocessing).
|
||||
|
||||
``vaapi`` is safe if the ``vaapi-egl`` backend is indicated in the logs.
|
||||
If ``vaapi-glx`` is indicated, and the video colorspace is either BT.601
|
||||
@@ -690,8 +692,8 @@ Video
|
||||
choice of the format can influence performance considerably. On the other
|
||||
hand, there doesn't appear to be a good way to detect the best format for
|
||||
the given hardware. ``nv12``, the default, works better on modern hardware,
|
||||
while ``uyvy422`` appears to be better for old hardware. ``rgb0`` also
|
||||
works.
|
||||
while ``uyvy422`` appears to be better for old hardware. ``rgb0`` and
|
||||
``yuv420p`` also work.
|
||||
|
||||
``--panscan=<0.0-1.0>``
|
||||
Enables pan-and-scan functionality (cropping the sides of e.g. a 16:9
|
||||
@@ -883,9 +885,9 @@ Video
|
||||
You can get the list of allowed codecs with ``mpv --vd=help``. Remove the
|
||||
prefix, e.g. instead of ``lavc:h264`` use ``h264``.
|
||||
|
||||
By default, this is set to ``h264,vc1,wmv3,hevc,mpeg2video``. Note that the
|
||||
hardware acceleration special codecs like ``h264_vdpau`` are not relevant
|
||||
anymore, and in fact have been removed from Libav in this form.
|
||||
By default, this is set to ``h264,vc1,wmv3,hevc,mpeg2video,vp9``. Note that
|
||||
the hardware acceleration special codecs like ``h264_vdpau`` are not
|
||||
relevant anymore, and in fact have been removed from Libav in this form.
|
||||
|
||||
This is usually only needed with broken GPUs, where a codec is reported
|
||||
as supported, but decoding causes more problems than it solves.
|
||||
@@ -1072,14 +1074,15 @@ Audio
|
||||
|
||||
``--volume=<value>``
|
||||
Set the startup volume. 0 means silence, 100 means no volume reduction or
|
||||
amplification. A value of -1 (the default) will not change the volume. See
|
||||
also ``--softvol``.
|
||||
amplification. Negative values can be passed for compatibility, but are
|
||||
treated as 0.
|
||||
|
||||
.. note::
|
||||
Since mpv 0.18.1, this always controls the internal mixer (aka "softvol").
|
||||
|
||||
This was changed after the mpv 0.9 release. Before that, 100 actually
|
||||
meant maximum volume. At the same time, the volume scale was made cubic,
|
||||
so the old values won't match up with the new ones anyway.
|
||||
``--balance=<value>``
|
||||
How much left/right channels contribute to the audio.
|
||||
|
||||
Deprecated.
|
||||
|
||||
``--audio-delay=<sec>``
|
||||
Audio delay in seconds (positive or negative float value). Positive values
|
||||
@@ -1094,20 +1097,17 @@ Audio
|
||||
|
||||
See also: ``--volume``.
|
||||
|
||||
``--softvol=<mode>``
|
||||
Control whether to use the volume controls of the audio output driver or
|
||||
the internal mpv volume filter.
|
||||
``--softvol=<no|yes|auto>``
|
||||
Deprecated/unfunctional. Before mpv 0.18.1, this used to control whether
|
||||
to use the volume controls of the audio output driver or the internal mpv
|
||||
volume filter.
|
||||
|
||||
:no: prefer audio driver controls, use the volume filter only if
|
||||
absolutely needed
|
||||
:yes: always use the volume filter
|
||||
:auto: prefer the volume filter if the audio driver uses the system mixer
|
||||
(default)
|
||||
The current behavior is as if this option was set to ``yes``. The other
|
||||
behaviors are not available anymore, although ``auto`` almost matches
|
||||
current behavior in most cases.
|
||||
|
||||
The intention of ``auto`` is to avoid changing system mixer settings from
|
||||
within mpv with default settings. mpv is a video player, not a mixer panel.
|
||||
On the other hand, mixer controls are enabled for sound servers like
|
||||
PulseAudio, which provide per-application volume.
|
||||
The ``no`` behavior is still partially available through the ``ao-volume``
|
||||
and ``ao-mute`` properties. But there are no options to reset these.
|
||||
|
||||
``--audio-demuxer=<[+]name>``
|
||||
Use this audio demuxer type when using ``--audio-file``. Use a '+' before
|
||||
@@ -1150,30 +1150,51 @@ Audio
|
||||
This and enabling passthrough via ``--ad`` are deprecated in favor of
|
||||
using ``--audio-spdif=dts-hd``.
|
||||
|
||||
``--audio-channels=<auto|number|layout>``
|
||||
Request a channel layout for audio output (default: stereo). This will ask
|
||||
the AO to open a device with the given channel layout. It's up to the AO
|
||||
to accept this layout, or to pick a fallback or to error out if the
|
||||
requested layout is not supported.
|
||||
``--audio-channels=<auto-safe|auto|layouts>``
|
||||
Control which audio channels are output (e.g. surround vs. stereo). There
|
||||
are the following possibilities:
|
||||
|
||||
The ``--audio-channels`` option either takes a channel number or an explicit
|
||||
channel layout. Channel numbers refer to default layouts, e.g. 2 channels
|
||||
refer to stereo, 6 refers to 5.1.
|
||||
- ``--audio-channels=auto-safe``
|
||||
Use the system's preferred channel layout. If there is none (such
|
||||
as when accessing a hardware device instead of the system mixer),
|
||||
force stereo. Some audio outputs might simply accept any layout and
|
||||
do downmixing on their own.
|
||||
|
||||
This is the default.
|
||||
- ``--audio-channels=auto``
|
||||
Send the audio device whatever it accepts, preferring the audio's
|
||||
original channel layout. Can cause issues with HDMI (see the warning
|
||||
below).
|
||||
- ``--audio-channels=layout1,layout2,...``
|
||||
List of ``,``-separated channel layouts which should be allowed.
|
||||
Technically, this only adjusts the filter chain output to the best
|
||||
matching layout in the list, and passes the result to the audio API.
|
||||
It's possible that the audio API will select a different channel
|
||||
layout.
|
||||
|
||||
Using this mode is recommended for direct hardware output, especially
|
||||
over HDMI (see HDMI warning below).
|
||||
- ``--audio-channels=stereo``
|
||||
Force a plain stereo downmix. This is a special-case of the previous
|
||||
item. (See paragraphs below for implications.)
|
||||
|
||||
If a list of layouts is given, each item can be either an explicit channel
|
||||
layout name (like ``5.1``), or a channel number. Channel numbers refer to
|
||||
default layouts, e.g. 2 channels refer to stereo, 6 refers to 5.1.
|
||||
|
||||
See ``--audio-channels=help`` output for defined default layouts. This also
|
||||
lists speaker names, which can be used to express arbitrary channel
|
||||
layouts (e.g. ``fl-fr-lfe`` is 2.1).
|
||||
|
||||
``--audio-channels=auto`` tries to play audio using the input file's
|
||||
channel layout. There is no guarantee that the audio API handles this
|
||||
correctly. See the HDMI warning below.
|
||||
(``empty`` is an accepted obsolete alias for ``auto``.)
|
||||
|
||||
This will also request the channel layout from the decoder. If the decoder
|
||||
does not support the layout, it will fall back to its native channel layout.
|
||||
(You can use ``--ad-lavc-downmix=no`` to make the decoder always output
|
||||
its native layout.) Note that only some decoders support remixing audio.
|
||||
Some that do include AC-3, AAC or DTS audio.
|
||||
If the list of channel layouts has only 1 item, the decoder is asked to
|
||||
produce according output. This sometimes triggers decoder-downmix, which
|
||||
might be different from the normal mpv downmix. (Only some decoders support
|
||||
remixing audio, like AC-3, AAC or DTS. You can use ``--ad-lavc-downmix=no``
|
||||
to make the decoder always output its native layout.) One consequence is
|
||||
that ``--audio-channels=stereo`` triggers decoder downmix, while ``auto``
|
||||
or ``auto-safe`` never will, even if they end up selecting stereo. This
|
||||
happens because the decision whether to use decoder downmix happens long
|
||||
before the audio device is opened.
|
||||
|
||||
If the channel layout of the media file (i.e. the decoder) and the AO's
|
||||
channel layout don't match, mpv will attempt to insert a conversion filter.
|
||||
@@ -1186,6 +1207,10 @@ Audio
|
||||
channel layout, random things can happen, such as dropping the
|
||||
additional channels, or adding noise.
|
||||
|
||||
You are recommended to set an explicit whitelist of the layouts you
|
||||
want. For example, most A/V receivers connected via HDMI and that can
|
||||
do 7.1 would be served by: ``--audio-channels=7.1,5.1,stereo``
|
||||
|
||||
``--audio-normalize-downmix=<yes|no>``
|
||||
Enable/disable normalization if surround audio is downmixed to stereo
|
||||
(default: no). If this is disabled, downmix can cause clipping. If it's
|
||||
@@ -1264,10 +1289,12 @@ Audio
|
||||
their start timestamps differ, and then video timing is gradually adjusted
|
||||
if necessary to reach correct synchronization later.
|
||||
|
||||
``--softvol-max=<100.0-1000.0>``
|
||||
``--volume-max=<100.0-1000.0>``, ``--softvol-max=<...>``
|
||||
Set the maximum amplification level in percent (default: 130). A value of
|
||||
130 will allow you to adjust the volume up to about double the normal level.
|
||||
|
||||
``--softvol-max`` is a deprecated alias and should not be used.
|
||||
|
||||
``--audio-file-auto=<no|exact|fuzzy|all>``, ``--no-audio-file-auto``
|
||||
Load additional audio files matching the video filename. The parameter
|
||||
specifies how external audio files are matched. ``exact`` is enabled by
|
||||
@@ -1276,7 +1303,7 @@ Audio
|
||||
:no: Don't automatically load external audio files.
|
||||
:exact: Load the media filename with audio file extension (default).
|
||||
:fuzzy: Load all audio files containing media filename.
|
||||
:all: Load all aufio files in the current and ``--audio-file-paths``
|
||||
:all: Load all audio files in the current and ``--audio-file-paths``
|
||||
directories.
|
||||
|
||||
``--audio-file-paths=<path1:path2:...>``
|
||||
@@ -1288,7 +1315,7 @@ Audio
|
||||
or to set your own application name when using libmpv.
|
||||
|
||||
``--volume-restore-data=<string>``
|
||||
Used internally for use by playback resume (e.g. with ``quit_watch_later``).
|
||||
Used internally for use by playback resume (e.g. with ``quit-watch-later``).
|
||||
Restoring value has to be done carefully, because different AOs as well as
|
||||
softvol can have different value ranges, and we don't want to restore
|
||||
volume if setting the volume changes it system wide. The normal options
|
||||
@@ -1314,13 +1341,32 @@ Audio
|
||||
|
||||
Default: 0.2 (200 ms).
|
||||
|
||||
``--audio-stream-silence=<yes|no>``
|
||||
Cash-grab consumer audio hardware (such as A/V receivers) often ignore
|
||||
initial audio sent over HDMI. This can happen every time audio over HDMI
|
||||
is stopped and resumed. In order to compensate for this, you can enable
|
||||
this option to not to stop and restart audio on seeks, and fill the gaps
|
||||
with silence. Likewise, when pausing playback, audio is not stopped, and
|
||||
silence is played while paused. Note that if no audio track is selected,
|
||||
the audio device will still be closed immediately.
|
||||
|
||||
Not all AOs support this.
|
||||
|
||||
``--audio-wait-open=<secs>``
|
||||
This makes sense for use with ``--audio-stream-silence=yes``. If this option
|
||||
is given, the player will wait for the given amount of seconds after opening
|
||||
the audio device before sending actual audio data to it. Useful if your
|
||||
expensive hardware discards the first 1 or 2 seconds of audio data sent to
|
||||
it. If ``--audio-stream-silence=yes`` is not set, this option will likely
|
||||
just waste time.
|
||||
|
||||
Subtitles
|
||||
---------
|
||||
|
||||
.. note::
|
||||
|
||||
Changing styling and position does not work with all subtitles. Image-based
|
||||
subtitles (DVD, Bluray/PGS, DVB) can not changed for fundamental reasons.
|
||||
subtitles (DVD, Bluray/PGS, DVB) cannot changed for fundamental reasons.
|
||||
Subtitles in ASS format are normally not changed intentionally, but
|
||||
overriding them can be controlled with ``--ass-style-override``.
|
||||
|
||||
@@ -2019,6 +2065,14 @@ Window
|
||||
be the default behavior. Currently only affects X11 VOs.
|
||||
|
||||
``--heartbeat-cmd=<command>``
|
||||
|
||||
.. warning::
|
||||
|
||||
This option is redundant with Lua scripting. Further, it shouldn't be
|
||||
needed for disabling screensaver anyway, since mpv will call
|
||||
``xdg-screensaver`` when using X11 backend. As a consequence this
|
||||
option has been deprecated with no direct replacement.
|
||||
|
||||
Command that is executed every 30 seconds during playback via *system()* -
|
||||
i.e. using the shell. The time between the commands can be customized with
|
||||
the ``--heartbeat-interval`` option. The command is not run while playback
|
||||
@@ -2438,7 +2492,7 @@ Demuxer
|
||||
stop reading additional packets as soon as one of the limits is reached.
|
||||
(The limits still can be slightly overstepped due to technical reasons.)
|
||||
|
||||
Set these limits highher if you get a packet queue overflow warning, and
|
||||
Set these limits higher if you get a packet queue overflow warning, and
|
||||
you think normal playback would be possible with a larger packet queue.
|
||||
|
||||
See ``--list-options`` for defaults and value range.
|
||||
@@ -2463,7 +2517,7 @@ Demuxer
|
||||
|
||||
``--force-seekable=<yes|no>``
|
||||
If the player thinks that the media is not seekable (e.g. playing from a
|
||||
pipe, or it's a http stream with a server that doesn't support range
|
||||
pipe, or it's an http stream with a server that doesn't support range
|
||||
requests), seeking will be disabled. This option can forcibly enable it.
|
||||
For seeks within the cache, there's a good chance of success.
|
||||
|
||||
@@ -2533,7 +2587,7 @@ Input
|
||||
automatically enabled when ``-`` is found on the command line. There are
|
||||
situations where you have to set it manually, e.g. if you open
|
||||
``/dev/stdin`` (or the equivalent on your system), use stdin in a playlist
|
||||
or intend to read from stdin later on via the loadfile or loadlist slave
|
||||
or intend to read from stdin later on via the loadfile or loadlist input
|
||||
commands.
|
||||
|
||||
``--input-ipc-server=<filename>``
|
||||
@@ -2655,7 +2709,7 @@ OSD
|
||||
(default), then the playback time, duration, and some more information is
|
||||
shown.
|
||||
|
||||
This is also used for the ``show_progress`` command (by default mapped to
|
||||
This is also used for the ``show-progress`` command (by default mapped to
|
||||
``P``), or in some non-default cases when seeking.
|
||||
|
||||
``--osd-status-msg`` is a legacy equivalent (but with a minor difference).
|
||||
@@ -2663,7 +2717,7 @@ OSD
|
||||
``--osd-status-msg=<string>``
|
||||
Show a custom string during playback instead of the standard status text.
|
||||
This overrides the status text used for ``--osd-level=3``, when using the
|
||||
``show_progress`` command (by default mapped to ``P``), or in some
|
||||
``show-progress`` command (by default mapped to ``P``), or in some
|
||||
non-default cases when seeking. Expands properties. See
|
||||
`Property Expansion`_.
|
||||
|
||||
@@ -2924,7 +2978,7 @@ Screenshot
|
||||
insert the number of the current month as number. You have to use
|
||||
multiple ``%tX`` specifiers to build a full date/time string.
|
||||
``%{prop[:fallback text]}``
|
||||
Insert the value of the slave property 'prop'. E.g. ``%{filename}`` is
|
||||
Insert the value of the input property 'prop'. E.g. ``%{filename}`` is
|
||||
the same as ``%f``. If the property does not exist or is not available,
|
||||
an error text is inserted, unless a fallback is specified.
|
||||
``%%``
|
||||
@@ -3173,7 +3227,7 @@ TV
|
||||
If <chan> is an integer greater than 1000, it will be treated as
|
||||
frequency (in kHz) rather than channel name from frequency table.
|
||||
Use _ for spaces in names (or play with quoting ;-) ). The channel
|
||||
names will then be written using OSD, and the slave commands
|
||||
names will then be written using OSD, and the input commands
|
||||
``tv_step_channel``, ``tv_set_channel`` and ``tv_last_channel``
|
||||
will be usable for a remote control. Not compatible with
|
||||
the ``frequency`` parameter.
|
||||
@@ -3290,7 +3344,7 @@ Cache
|
||||
With ``auto``, the cache will usually be enabled for network streams,
|
||||
using the size set by ``--cache-default``. With ``yes``, the cache will
|
||||
always be enabled with the size set by ``--cache-default`` (unless the
|
||||
stream can not be cached, or ``--cache-default`` disables caching).
|
||||
stream cannot be cached, or ``--cache-default`` disables caching).
|
||||
|
||||
May be useful when playing files from slow media, but can also have
|
||||
negative effects, especially with file formats that require a lot of
|
||||
@@ -3622,7 +3676,7 @@ Miscellaneous
|
||||
``--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`` slave property.
|
||||
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
|
||||
@@ -3666,6 +3720,16 @@ Miscellaneous
|
||||
``--audio-file``, this includes all tracks, and does not cause default
|
||||
stream selection over the "proper" file.
|
||||
|
||||
``--autoload-files=<yes|no>``
|
||||
Automatically load/select external files (default: yes).
|
||||
|
||||
If set to ``no``, then do not automatically load external files as specified
|
||||
by ``--sub-auto`` and ``--audio-file-auto``. If external files are forcibly
|
||||
added (like with ``--sub-file``), they will not be auto-selected.
|
||||
|
||||
This does not affect playlist expansion, redirection, or other loading of
|
||||
referenced files like with ordered chapters.
|
||||
|
||||
``--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
|
||||
@@ -3685,7 +3749,7 @@ Miscellaneous
|
||||
video or audio outputs are not possible, but you can use filters to merge
|
||||
them into one.
|
||||
|
||||
The complex filter can not be changed yet during playback. It's also not
|
||||
The complex filter cannot be changed yet during playback. It's also not
|
||||
possible to change the tracks connected to the filter at runtime. Other
|
||||
tracks, as long as they're not connected to the filter, and the
|
||||
corresponding output is not connected to the filter, can still be freely
|
||||
|
||||
+11
-1
@@ -214,7 +214,7 @@ Available filters are:
|
||||
Format name, e.g. rgb15, bgr24, 420p, etc. (default: don't change).
|
||||
``<outfmt>``
|
||||
Format name that should be substituted for the output. If they do not
|
||||
have the same bytes per pixel and chroma subsamplimg, it will fail.
|
||||
have the same bytes per pixel and chroma subsampling, it will fail.
|
||||
``<colormatrix>``
|
||||
Controls the YUV to RGB color space conversion when playing video. There
|
||||
are various standards. Normally, BT.601 should be used for SD video, and
|
||||
@@ -288,6 +288,8 @@ Available filters are:
|
||||
:adobe: Adobe RGB (1998)
|
||||
:prophoto: ProPhoto RGB (ROMM)
|
||||
:cie1931: CIE 1931 RGB
|
||||
:dci-p3: DCI-P3 (Digital Cinema)
|
||||
:v-gamut: Panasonic V-Gamut primaries
|
||||
|
||||
``<gamma>``
|
||||
Gamma function the source file was encoded with. Normally this should be set
|
||||
@@ -311,6 +313,8 @@ Available filters are:
|
||||
:gamma2.8: Pure power curve (gamma 2.8)
|
||||
:prophoto: ProPhoto RGB (ROMM) curve
|
||||
:st2084: SMPTE ST2084 (HDR) curve
|
||||
:std-b67: ARIB STD-B67 (Hybrid Log-gamma) curve
|
||||
:v-log: Panasonic V-Log transfer curve
|
||||
|
||||
``<peak>``
|
||||
Reference peak illumination for the video file. This is mostly
|
||||
@@ -817,6 +821,12 @@ Available filters are:
|
||||
Whether deinterlacing is enabled (default: no).
|
||||
``interlaced-only=<yes|no>``
|
||||
If ``yes`` (default), only deinterlace frames marked as interlaced.
|
||||
``mode=<blend|bob|adaptive|mocomp|ivctc|none>``
|
||||
Tries to select a video processor with the given processing capability.
|
||||
If a video processor supports multiple capabilities, it is not clear
|
||||
which algorithm is actually selected. ``none`` always falls back. On
|
||||
most if not all hardware, this option will probably do nothing, because
|
||||
a video processor usually supports all modes or none.
|
||||
|
||||
``buffer=<num>``
|
||||
Buffer ``<num>`` frames in the filter chain. This filter is probably pretty
|
||||
|
||||
+21
-4
@@ -362,6 +362,9 @@ Available video output drivers are:
|
||||
exchange for adding some blur. This filter is good at temporal
|
||||
interpolation, and also known as "smoothmotion" (see ``tscale``).
|
||||
|
||||
``linear``
|
||||
A ``tscale`` filter.
|
||||
|
||||
``custom``
|
||||
A user-defined custom shader (see ``scale-shader``).
|
||||
|
||||
@@ -465,7 +468,7 @@ Available video output drivers are:
|
||||
8
|
||||
Dither to 8 bit output.
|
||||
|
||||
Note that the depth of the connected video display device can not be
|
||||
Note that the depth of the connected video display device cannot be
|
||||
detected. Often, LCD panels will do dithering on their own, which
|
||||
conflicts with ``opengl``'s dithering and leads to ugly output.
|
||||
|
||||
@@ -629,6 +632,9 @@ Available video output drivers are:
|
||||
never resets (regardless of seeks).
|
||||
vec2 image_size
|
||||
The size in pixels of the input image.
|
||||
vec2 target_size
|
||||
The size in pixels of the visible part of the scaled (and possibly
|
||||
cropped) image.
|
||||
|
||||
For example, a shader that inverts the colors could look like this::
|
||||
|
||||
@@ -979,6 +985,8 @@ Available video output drivers are:
|
||||
CIE 1931 RGB (not to be confused with CIE XYZ)
|
||||
dci-p3
|
||||
DCI-P3 (Digital Cinema Colorspace), SMPTE RP431-2
|
||||
v-gamut
|
||||
Panasonic V-Gamut (VARICAM) primaries
|
||||
|
||||
``target-trc=<value>``
|
||||
Specifies the transfer characteristics (gamma) of the display. Video
|
||||
@@ -1003,6 +1011,16 @@ Available video output drivers are:
|
||||
ProPhoto RGB (ROMM)
|
||||
st2084
|
||||
SMPTE ST2084 (HDR) curve, PQ OETF
|
||||
std-b67
|
||||
ARIB STD-B67 (Hybrid Log-gamma) curve, also known as BBC/NHK HDR
|
||||
v-log
|
||||
Panasonic V-Log (VARICAM) curve
|
||||
|
||||
NOTE: When using HDR output formats, mpv will encode to the specified
|
||||
curve but it will not set any HDMI flags or other signalling that
|
||||
might be required for the target device to correctly display the
|
||||
HDR signal. The user should independently guarantee this before
|
||||
using these signal formats for display.
|
||||
|
||||
``target-brightness=<1..100000>``
|
||||
Specifies the display's approximate brightness in cd/m^2. When playing
|
||||
@@ -1080,8 +1098,7 @@ Available video output drivers are:
|
||||
|
||||
``3dlut-size=<r>x<g>x<b>``
|
||||
Size of the 3D LUT generated from the ICC profile in each dimension.
|
||||
Default is 128x256x64.
|
||||
Sizes must be a power of two, and 512 at most.
|
||||
Default is 64x64x64. Sizes may range from 2 to 512.
|
||||
|
||||
``icc-contrast=<0-100000>``
|
||||
Specifies an upper limit on the target device's contrast ratio.
|
||||
@@ -1285,7 +1302,7 @@ Available video output drivers are:
|
||||
This also supports many of the suboptions the ``opengl`` VO has. Run
|
||||
``mpv --vo=opengl-cb:help`` for a list.
|
||||
|
||||
This also supports the ``vo_cmdline`` command.
|
||||
This also supports the ``vo-cmdline`` command.
|
||||
|
||||
``rpi`` (Raspberry Pi)
|
||||
Native video output on the Raspberry Pi using the MMAL API.
|
||||
|
||||
@@ -30,7 +30,7 @@ Player
|
||||
* Slave mode compatibility broken (see below).
|
||||
* Re-enable screensaver while the player is paused.
|
||||
* Allow resuming playback at a later point with ``Shift+q``, also see the
|
||||
``quit_watch_later`` input command.
|
||||
``quit-watch-later`` input command.
|
||||
* ``--keep-open`` option to stop the player from closing the window and
|
||||
exiting after playback ends.
|
||||
* A client API, that allows embedding **mpv** into applications
|
||||
@@ -312,11 +312,11 @@ input.conf and Slave Commands
|
||||
+--------------------------------+----------------------------------------+
|
||||
| Old | New |
|
||||
+================================+========================================+
|
||||
| ``pt_step 1 [0|1]`` | ``playlist_next [weak|force]`` |
|
||||
| ``pt_step 1 [0|1]`` | ``playlist-next [weak|force]`` |
|
||||
| | (translation layer cannot deal with |
|
||||
| | whitespace) |
|
||||
+--------------------------------+----------------------------------------+
|
||||
| ``pt_step -1 [0|1]`` | ``playlist_prev [weak|force] (same)`` |
|
||||
| ``pt_step -1 [0|1]`` | ``playlist-prev [weak|force] (same)`` |
|
||||
+--------------------------------+----------------------------------------+
|
||||
| ``switch_ratio [<ratio>]`` | ``set video-aspect <ratio>`` |
|
||||
| | |
|
||||
@@ -331,7 +331,7 @@ input.conf and Slave Commands
|
||||
| ``<step> <dir>`` | ``no-osd``: ``no-osd cycle <prop>`` |
|
||||
| | ``<step>`` |
|
||||
+--------------------------------+----------------------------------------+
|
||||
| ``osd_show_property_text`` | ``show_text <text>`` |
|
||||
| ``osd_show_property_text`` | ``show-text <text>`` |
|
||||
| ``<text>`` | The property expansion format string |
|
||||
| | syntax slightly changed. |
|
||||
+--------------------------------+----------------------------------------+
|
||||
@@ -340,9 +340,9 @@ input.conf and Slave Commands
|
||||
| | ``raw`` prefix to disable property |
|
||||
| | expansion. |
|
||||
+--------------------------------+----------------------------------------+
|
||||
| ``show_tracks`` | ``show_text ${track-list}`` |
|
||||
| ``show_tracks`` | ``show-text ${track-list}`` |
|
||||
+--------------------------------+----------------------------------------+
|
||||
| ``show_chapters`` | ``show_text ${chapter-list}`` |
|
||||
| ``show_chapters`` | ``show-text ${chapter-list}`` |
|
||||
+--------------------------------+----------------------------------------+
|
||||
| ``af_switch``, ``af_add``, ... | ``af set|add|...`` |
|
||||
+--------------------------------+----------------------------------------+
|
||||
|
||||
@@ -25,7 +25,8 @@ Release procedure
|
||||
|
||||
- Create and/or update the `VERSION` file.
|
||||
|
||||
- Update `DOCS/client-api-changes.rst` (on major releases).
|
||||
- Update `DOCS/client-api-changes.rst` and `DOCS/interface-changes.rst`
|
||||
(in particular, update the last version numbers if necessary)
|
||||
|
||||
- Create tag v0.X.Y.
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ options/options.h, options/options.c
|
||||
parser-mpcmd.c, and uses the option table in options.c.
|
||||
|
||||
input/input.c:
|
||||
This translates keyboard input comming from VOs and other sources (such
|
||||
This translates keyboard input coming from VOs and other sources (such
|
||||
as remote control devices like Apple IR or client API commands) to the
|
||||
key bindings listed in the user's (or the builtin) input.conf and turns
|
||||
them into items of type struct mp_cmd. These commands are queued, and read
|
||||
|
||||
@@ -137,6 +137,20 @@ most likely the best maintenance out of all stable releases. Older releases
|
||||
are for distros, and at best receive basic changes like fixing critical security
|
||||
issues or build fixes, and at worst are completely abandoned.
|
||||
|
||||
## FFmpeg ABI compatibility
|
||||
|
||||
mpv does not support linking against FFmpeg versions it was not built with, even
|
||||
if the linked version is supposedly ABI-compatible with the version it was
|
||||
compiled against. Expect malfunctions, crashes, and security issues if you
|
||||
do it anyway.
|
||||
|
||||
The reason for not supporting this is because it creates far too much complexity
|
||||
with little to no benefit, coupled with absurd and unusable FFmpeg API
|
||||
artifacts.
|
||||
|
||||
Newer mpv versions will refuse to start if runtime and compile time FFmpeg
|
||||
library versions mismatch.
|
||||
|
||||
## Release cycle
|
||||
|
||||
Every other month, an arbitrary git snapshot is made, and is assigned
|
||||
|
||||
+190
-1
@@ -1,3 +1,192 @@
|
||||
Release 0.19.0
|
||||
==============
|
||||
|
||||
Build System Changes
|
||||
--------------------
|
||||
|
||||
- build: add --htmldir option
|
||||
- build: always require atomics
|
||||
- wscript: add proper unversioned SONAME for Android
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
New
|
||||
~~~
|
||||
|
||||
- client API: add stream_cb API for user-defined stream implementations (bumps client API version to 1.22)
|
||||
- vf_d3d11vpp: add video processor selection
|
||||
- videotoolbox: add --hwdec=videotoolbox-copy for h/w accelerated decoding with video filters
|
||||
- vo_opengl: add a tscale=linear direct implementation
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- audio/filter: remove delay audio filter
|
||||
|
||||
|
||||
Options and Commands
|
||||
--------------------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- command: add filename/no-ext sub-property that returns filename without extension (#3404)
|
||||
- command: add properties for HDR metadata
|
||||
- command: add replaygain information properties to track-list
|
||||
- options: add vp9 to --hwdec-codecs
|
||||
- player: add --audio-stream-silence
|
||||
- player: add --audio-wait-open
|
||||
- player: add --no-autoload-files
|
||||
- videotoolbox: add yuv420p to --videotoolbox-format
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- options: un-restrict --audio-delay
|
||||
- use - as command-name separator everywhere
|
||||
- vo_opengl: reduce default 3dlut-size to 64x64x64 (since accuracy is improved)
|
||||
|
||||
|
||||
Deprecated
|
||||
~~~~~~~~~~
|
||||
|
||||
- deprecate "balance" option/property (no replacement)
|
||||
|
||||
|
||||
|
||||
Fixes and Minor Enhancements
|
||||
----------------------------
|
||||
|
||||
- Windows: don't wait for GUI thread when polling for events (#3393)
|
||||
- af_lavcac3enc: error out properly if encoding fails
|
||||
- af_volume: don't let softvol overwrite af_volume volumedb sub-option
|
||||
- ao_pulse: fix some volume control rounding issues
|
||||
- ao_wasapi: in exclusive mode, do not output multichannel by default
|
||||
- audio: add heuristic to move auto-downmixing before other filters
|
||||
- audio: show an osd bar when changing ao-volume
|
||||
- demux: make ALBUM replaygain tags optional (#3405)
|
||||
- demux_raw: fix small typo to add s16be support
|
||||
- demux_timeline: restore mkv edition switching
|
||||
- libarchive: sanitize non-UTF8 archive entries
|
||||
- macOS/vo_opengl: fix crash when glctx is NULL during init (#3360)
|
||||
- player: disable display-sync with spdif transcoding
|
||||
- player: do not cut off terminal status line if it contains newlines (#3340)
|
||||
- player: fix display-sync timing if audio resumes slowly
|
||||
- player: improve instant track switching (#3392)
|
||||
- player: improve non-hr seeking with external audio tracks
|
||||
- player: offset demuxer on start/seek properly with audio/sub delay
|
||||
- player: sync audio as well when enabling it mid-stream
|
||||
- stream/stream_bluray: display list of available titles in verbose mode
|
||||
- sub: don't potentially discard too many subtitles on seek
|
||||
- video: respect --deinterlace=auto
|
||||
- vo_direct3d: add missing header (fixes Cygwin build)
|
||||
- vo_opengl: angle: try D3D9 when D3D11 fails eglInitialize
|
||||
- vo_opengl: angle: use WARP if there are no hw adapters (makes it work on Windows 7 without hardware-accelerated graphics)
|
||||
- vo_opengl: increase 3DLUT accuracy at smaller LUT sizes
|
||||
- vo_opengl: remove the 3dlut-size npot2 restriction
|
||||
- vo_wayland: fix high CPU usage due to busy polling
|
||||
- wayland_common: clip window size to the display output size
|
||||
- wayland_common: fix crashes when switching to fullscreen before the video output is fully initialized
|
||||
- wayland_common: fix fullscreen image switching bug
|
||||
- wayland_common: prevent black bars on most non-native aspect ratios
|
||||
- wayland_common: remove untested/unusable wayland dnd code
|
||||
- win32: mpv.rc: re-add version info
|
||||
- x11: skip ICC update on every window move
|
||||
- ytdl: Error out with http_dash_segments (unsupported for now)
|
||||
|
||||
|
||||
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.18.1..v0.19.0`
|
||||
in the git repository or by visiting either
|
||||
https://github.com/mpv-player/mpv/compare/v0.18.1...v0.19.0 or
|
||||
http://git.srsfckn.biz/mpv/log/?qt=range&q=v0.18.1..v0.19.0
|
||||
|
||||
|
||||
Release 0.18.1
|
||||
==============
|
||||
|
||||
Note: Running mpv with different versions of the FFmpeg/Libav libraries than
|
||||
it was compiled with is no longer supported. Even supposedly ABI-compatible
|
||||
versions have been a source of trouble, and it creates far too much
|
||||
complexity with little to no benefit, coupled with absurd and unusable FFmpeg
|
||||
API artifacts.
|
||||
|
||||
Instead, mpv will exit with an error when such a situation is detected.
|
||||
This simply means that mpv needs to be rebuilt whenever FFmpeg libraries change.
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
New
|
||||
~~~
|
||||
|
||||
- d3d: implement screenshots for --hwdec=d3d11va
|
||||
- vo_opengl: add output_size uniform to custom shader
|
||||
- vo_opengl: implement the Panasonic V-Log function (#3157)
|
||||
- vo_opengl: implement ARIB STD-B68 (HLG) HDR TRC
|
||||
|
||||
|
||||
Options and Commands
|
||||
--------------------
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
- command: pack sub image data in overlay-add command
|
||||
|
||||
|
||||
Deprecated
|
||||
~~~~~~~~~~
|
||||
|
||||
- options: deprecate --heartbeat-cmd
|
||||
- audio: deprecate --softvol
|
||||
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- audio: drop --softvol=no and --softvol=auto (#3322)
|
||||
|
||||
|
||||
Fixes and Minor Enhancements
|
||||
----------------------------
|
||||
|
||||
- video: fix deinterlace filter handling on pixel format changes
|
||||
- x11: silence xdg-screensaver
|
||||
- vo_opengl: angle: update the swapchain on resize (#3301)
|
||||
- vo_opengl: error out gracefully when trying to use FBOs without FBO API
|
||||
- vd_lavc: expose mastering display side data reference peak (improves results with HDR content)
|
||||
- vo_opengl: generalize HDR tone mapping mechanism (#3293)
|
||||
- vo_opengl: don't constantly resize the output FBO
|
||||
- vo_opengl: use ringbuffer of PBOs
|
||||
- Windows: make WM_NCHITTEST simpler and more accurate
|
||||
- ao_oss: do not add an entry to audio-device-list if device file missing
|
||||
- dec_audio: fix segment boudnary switching
|
||||
- ao_lavc, vo_lavc: Migrate to new FFmpeg encoding API
|
||||
- vo_opengl: explicitly use main framebuffer when reading window content (#3284)
|
||||
- vo_xv: fix behavior with odd sizes
|
||||
- audio: insert auto-inserted filters at end of chain
|
||||
- x11: add missing FocusChangeMask (disables key repeat when losing focus while a key is down)
|
||||
- ao_coreaudio: error out when selecting invalid device
|
||||
- ad_lavc: work around misbehavior of some FFmpeg decoders like wmapro (#3297)
|
||||
- player: cut off status line on terminal width
|
||||
|
||||
|
||||
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.18.0..v0.18.1`
|
||||
in the git repository or by visiting either
|
||||
https://github.com/mpv-player/mpv/compare/v0.18.0...v0.18.1 or
|
||||
http://git.srsfckn.biz/mpv/log/?qt=range&q=v0.18.0..v0.18.1
|
||||
|
||||
|
||||
Release 0.18.0
|
||||
==============
|
||||
|
||||
@@ -39,7 +228,7 @@ New
|
||||
|
||||
|
||||
Removed
|
||||
-------
|
||||
~~~~~~~
|
||||
|
||||
- vo_opengl: remove nnedi3 prescaler (replaced by user shaders)
|
||||
- vo_opengl: remove prescaling framework with superxbr prescaler (replaced by user shaders)
|
||||
|
||||
@@ -25,7 +25,7 @@ function add_files_at(index, files)
|
||||
local oldcount = mp.get_property_number("playlist-count", 1)
|
||||
for i = 1, #files do
|
||||
mp.commandv("loadfile", files[i], "append")
|
||||
mp.commandv("playlist_move", oldcount + i - 1, index + i - 1)
|
||||
mp.commandv("playlist-move", oldcount + i - 1, index + i - 1)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
+44
-13
@@ -138,6 +138,9 @@ static void mp_audio_destructor(void *ptr)
|
||||
* available on every plane. The previous data is kept (for the smallest
|
||||
* common number of samples before/after resize).
|
||||
*
|
||||
* This also makes sure the resulting buffer is writable (even in the case
|
||||
* the buffer has the correct size).
|
||||
*
|
||||
* mpa->samples is not set or used.
|
||||
*
|
||||
* This function is flexible enough to handle format and channel layout
|
||||
@@ -153,6 +156,12 @@ void mp_audio_realloc(struct mp_audio *mpa, int samples)
|
||||
int size = get_plane_size(mpa, samples);
|
||||
if (size < 0)
|
||||
abort(); // oom or invalid parameters
|
||||
if (!mp_audio_is_writeable(mpa)) {
|
||||
for (int n = 0; n < MP_NUM_CHANNELS; n++) {
|
||||
av_buffer_unref(&mpa->allocated[n]);
|
||||
mpa->planes[n] = NULL;
|
||||
}
|
||||
}
|
||||
for (int n = 0; n < mpa->num_planes; n++) {
|
||||
if (!mpa->allocated[n] || size != mpa->allocated[n]->size) {
|
||||
if (av_buffer_realloc(&mpa->allocated[n], size) < 0)
|
||||
@@ -171,7 +180,7 @@ void mp_audio_realloc(struct mp_audio *mpa, int samples)
|
||||
// If the buffer is reallocated, also preallocate.
|
||||
void mp_audio_realloc_min(struct mp_audio *mpa, int samples)
|
||||
{
|
||||
if (samples > mp_audio_get_allocated_size(mpa)) {
|
||||
if (samples > mp_audio_get_allocated_size(mpa) || !mp_audio_is_writeable(mpa)) {
|
||||
size_t alloc = ta_calc_prealloc_elems(samples);
|
||||
if (alloc > INT_MAX)
|
||||
abort(); // oom
|
||||
@@ -257,13 +266,21 @@ void mp_audio_skip_samples(struct mp_audio *data, int samples)
|
||||
data->pts += samples / (double)data->rate;
|
||||
}
|
||||
|
||||
// Return the timestamp of the sample just after the end of this frame.
|
||||
double mp_audio_end_pts(struct mp_audio *f)
|
||||
{
|
||||
if (f->pts == MP_NOPTS_VALUE || f->rate < 1)
|
||||
return MP_NOPTS_VALUE;
|
||||
return f->pts + f->samples / (double)f->rate;
|
||||
}
|
||||
|
||||
// Clip the given frame to the given timestamp range. Adjusts the frame size
|
||||
// and timestamp.
|
||||
void mp_audio_clip_timestamps(struct mp_audio *f, double start, double end)
|
||||
{
|
||||
if (f->pts == MP_NOPTS_VALUE || f->rate < 1)
|
||||
double f_end = mp_audio_end_pts(f);
|
||||
if (f_end == MP_NOPTS_VALUE)
|
||||
return;
|
||||
double f_end = f->pts + f->samples / (double)f->rate;
|
||||
if (end != MP_NOPTS_VALUE) {
|
||||
if (f_end >= end) {
|
||||
if (f->pts >= end) {
|
||||
@@ -339,9 +356,9 @@ struct mp_audio *mp_audio_from_avframe(struct AVFrame *avframe)
|
||||
mp_chmap_from_lavc(&lavc_chmap, avframe->channel_layout);
|
||||
|
||||
#if LIBAVUTIL_VERSION_MICRO >= 100
|
||||
// FFmpeg being special again
|
||||
if (lavc_chmap.num != avframe->channels)
|
||||
mp_chmap_from_channels(&lavc_chmap, avframe->channels);
|
||||
// FFmpeg being stupid POS again
|
||||
if (lavc_chmap.num != av_frame_get_channels(avframe))
|
||||
mp_chmap_from_channels(&lavc_chmap, av_frame_get_channels(avframe));
|
||||
#endif
|
||||
|
||||
new->rate = avframe->sample_rate;
|
||||
@@ -386,12 +403,9 @@ fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Returns NULL on failure. The input is always unreffed.
|
||||
struct AVFrame *mp_audio_to_avframe_and_unref(struct mp_audio *frame)
|
||||
int mp_audio_to_avframe(struct mp_audio *frame, struct AVFrame *avframe)
|
||||
{
|
||||
struct AVFrame *avframe = av_frame_alloc();
|
||||
if (!avframe)
|
||||
goto fail;
|
||||
av_frame_unref(avframe);
|
||||
|
||||
avframe->nb_samples = frame->samples;
|
||||
avframe->format = af_to_avformat(frame->format);
|
||||
@@ -402,8 +416,8 @@ struct AVFrame *mp_audio_to_avframe_and_unref(struct mp_audio *frame)
|
||||
if (!avframe->channel_layout)
|
||||
goto fail;
|
||||
#if LIBAVUTIL_VERSION_MICRO >= 100
|
||||
// FFmpeg being a stupid POS (but I respect it)
|
||||
avframe->channels = frame->channels.num;
|
||||
// FFmpeg being a stupid POS again
|
||||
av_frame_set_channels(avframe, frame->channels.num);
|
||||
#endif
|
||||
avframe->sample_rate = frame->rate;
|
||||
|
||||
@@ -449,6 +463,23 @@ struct AVFrame *mp_audio_to_avframe_and_unref(struct mp_audio *frame)
|
||||
avframe = tmp;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
av_frame_unref(avframe);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Returns NULL on failure. The input is always unreffed.
|
||||
struct AVFrame *mp_audio_to_avframe_and_unref(struct mp_audio *frame)
|
||||
{
|
||||
struct AVFrame *avframe = av_frame_alloc();
|
||||
if (!avframe)
|
||||
goto fail;
|
||||
|
||||
if (mp_audio_to_avframe(frame, avframe) < 0)
|
||||
goto fail;
|
||||
|
||||
talloc_free(frame);
|
||||
return avframe;
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@ void mp_audio_copy(struct mp_audio *dst, int dst_offset,
|
||||
void mp_audio_copy_attributes(struct mp_audio *dst, struct mp_audio *src);
|
||||
void mp_audio_skip_samples(struct mp_audio *data, int samples);
|
||||
void mp_audio_clip_timestamps(struct mp_audio *f, double start, double end);
|
||||
double mp_audio_end_pts(struct mp_audio *data);
|
||||
|
||||
bool mp_audio_is_writeable(struct mp_audio *data);
|
||||
int mp_audio_make_writeable(struct mp_audio *data);
|
||||
@@ -80,6 +81,7 @@ int mp_audio_make_writeable(struct mp_audio *data);
|
||||
struct AVFrame;
|
||||
struct mp_audio *mp_audio_from_avframe(struct AVFrame *avframe);
|
||||
struct AVFrame *mp_audio_to_avframe_and_unref(struct mp_audio *frame);
|
||||
int mp_audio_to_avframe(struct mp_audio *frame, struct AVFrame *avframe);
|
||||
|
||||
struct mp_audio_pool;
|
||||
struct mp_audio_pool *mp_audio_pool_create(void *ta_parent);
|
||||
|
||||
@@ -230,44 +230,6 @@ void mp_chmap_set_unknown(struct mp_chmap *dst, int num_channels)
|
||||
}
|
||||
}
|
||||
|
||||
// Return channel index of the given speaker, or -1.
|
||||
static int mp_chmap_find_speaker(const struct mp_chmap *map, int speaker)
|
||||
{
|
||||
for (int n = 0; n < map->num; n++) {
|
||||
if (map->speaker[n] == speaker)
|
||||
return n;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void mp_chmap_remove_speaker(struct mp_chmap *map, int speaker)
|
||||
{
|
||||
int index = mp_chmap_find_speaker(map, speaker);
|
||||
if (index >= 0) {
|
||||
for (int n = index; n < map->num - 1; n++)
|
||||
map->speaker[n] = map->speaker[n + 1];
|
||||
map->num--;
|
||||
}
|
||||
}
|
||||
|
||||
// Some decoders output additional, redundant channels, which are usually
|
||||
// useless and will mess up proper audio output channel handling.
|
||||
// map: channel map from which the channels should be removed
|
||||
// requested: if not NULL, and if it contains any of the "useless" channels,
|
||||
// don't remove them (this is for convenience)
|
||||
void mp_chmap_remove_useless_channels(struct mp_chmap *map,
|
||||
const struct mp_chmap *requested)
|
||||
{
|
||||
if (requested &&
|
||||
mp_chmap_find_speaker(requested, MP_SPEAKER_ID_DL) >= 0)
|
||||
return;
|
||||
|
||||
if (map->num > 2) {
|
||||
mp_chmap_remove_speaker(map, MP_SPEAKER_ID_DL);
|
||||
mp_chmap_remove_speaker(map, MP_SPEAKER_ID_DR);
|
||||
}
|
||||
}
|
||||
|
||||
// Return the ffmpeg/libav channel layout as in <libavutil/channel_layout.h>.
|
||||
// Speakers not representable by ffmpeg/libav are dropped.
|
||||
// Warning: this ignores the order of the channels, and will return a channel
|
||||
|
||||
@@ -109,9 +109,6 @@ void mp_chmap_fill_na(struct mp_chmap *map, int num);
|
||||
void mp_chmap_from_channels(struct mp_chmap *dst, int num_channels);
|
||||
void mp_chmap_set_unknown(struct mp_chmap *dst, int num_channels);
|
||||
|
||||
void mp_chmap_remove_useless_channels(struct mp_chmap *map,
|
||||
const struct mp_chmap *requested);
|
||||
|
||||
uint64_t mp_chmap_to_lavc(const struct mp_chmap *src);
|
||||
uint64_t mp_chmap_to_lavc_unchecked(const struct mp_chmap *src);
|
||||
void mp_chmap_from_lavc(struct mp_chmap *dst, uint64_t src);
|
||||
|
||||
@@ -374,3 +374,16 @@ void mp_chmal_sel_log(const struct mp_chmap_sel *s, struct mp_log *log, int lev)
|
||||
if (s->allow_any)
|
||||
mp_msg(log, lev, " - anything\n");
|
||||
}
|
||||
|
||||
// Select a channel map from the given list that fits best to c. Don't change
|
||||
// *c if there's no match, or the list is empty.
|
||||
void mp_chmap_sel_list(struct mp_chmap *c, struct mp_chmap *maps, int num_maps)
|
||||
{
|
||||
// This is a separate function to keep messing with mp_chmap_sel internals
|
||||
// within this source file.
|
||||
struct mp_chmap_sel sel = {
|
||||
.chmaps = maps,
|
||||
.num_chmaps = num_maps,
|
||||
};
|
||||
mp_chmap_sel_fallback(&sel, c);
|
||||
}
|
||||
@@ -47,4 +47,6 @@ bool mp_chmap_sel_get_def(const struct mp_chmap_sel *s, struct mp_chmap *map,
|
||||
struct mp_log;
|
||||
void mp_chmal_sel_log(const struct mp_chmap_sel *s, struct mp_log *log, int lev);
|
||||
|
||||
void mp_chmap_sel_list(struct mp_chmap *c, struct mp_chmap *maps, int num_maps);
|
||||
|
||||
#endif
|
||||
@@ -104,9 +104,9 @@ static int init(struct dec_audio *da, const char *decoder)
|
||||
lavc_context->codec_type = AVMEDIA_TYPE_AUDIO;
|
||||
lavc_context->codec_id = lavc_codec->id;
|
||||
|
||||
if (opts->downmix) {
|
||||
if (opts->downmix && mpopts->audio_output_channels.num_chmaps == 1) {
|
||||
lavc_context->request_channel_layout =
|
||||
mp_chmap_to_lavc(&mpopts->audio_output_channels);
|
||||
mp_chmap_to_lavc(&mpopts->audio_output_channels.chmaps[0]);
|
||||
}
|
||||
|
||||
// Always try to set - option only exists for AC3 at the moment
|
||||
@@ -186,6 +186,12 @@ static int decode_packet(struct dec_audio *da, struct demux_packet *mpkt,
|
||||
struct priv *priv = da->priv;
|
||||
AVCodecContext *avctx = priv->avctx;
|
||||
|
||||
// If the decoder discards the timestamp for some reason, we use the
|
||||
// interpolated PTS. Initialize it so that it works for the initial
|
||||
// packet as well.
|
||||
if (mpkt && priv->next_pts == MP_NOPTS_VALUE)
|
||||
priv->next_pts = mpkt->pts;
|
||||
|
||||
int in_len = mpkt ? mpkt->len : 0;
|
||||
|
||||
AVPacket pkt;
|
||||
|
||||
@@ -200,7 +200,9 @@ void audio_work(struct dec_audio *da)
|
||||
if (da->current_frame)
|
||||
return;
|
||||
|
||||
if (!da->packet && demux_read_packet_async(da->header, &da->packet) == 0) {
|
||||
if (!da->packet && !da->new_segment &&
|
||||
demux_read_packet_async(da->header, &da->packet) == 0)
|
||||
{
|
||||
da->current_state = DATA_WAIT;
|
||||
return;
|
||||
}
|
||||
@@ -211,6 +213,7 @@ void audio_work(struct dec_audio *da)
|
||||
da->packet = NULL;
|
||||
}
|
||||
|
||||
bool had_input_packet = !!da->packet;
|
||||
bool had_packet = da->packet || da->new_segment;
|
||||
|
||||
int ret = da->ad_driver->decode_packet(da, da->packet, &da->current_frame);
|
||||
@@ -233,12 +236,12 @@ void audio_work(struct dec_audio *da)
|
||||
|
||||
fix_audio_pts(da);
|
||||
|
||||
bool segment_end = true;
|
||||
bool segment_end = !da->current_frame && !had_input_packet;
|
||||
|
||||
if (da->current_frame) {
|
||||
mp_audio_clip_timestamps(da->current_frame, da->start, da->end);
|
||||
if (da->current_frame->pts != MP_NOPTS_VALUE && da->start != MP_NOPTS_VALUE)
|
||||
segment_end = da->current_frame->pts >= da->start;
|
||||
segment_end = da->current_frame->pts >= da->end;
|
||||
if (da->current_frame->samples == 0) {
|
||||
talloc_free(da->current_frame);
|
||||
da->current_frame = NULL;
|
||||
|
||||
+145
-174
@@ -31,7 +31,6 @@
|
||||
#include "af.h"
|
||||
|
||||
// Static list of filters
|
||||
extern const struct af_info af_info_delay;
|
||||
extern const struct af_info af_info_channels;
|
||||
extern const struct af_info af_info_format;
|
||||
extern const struct af_info af_info_volume;
|
||||
@@ -46,7 +45,6 @@ extern const struct af_info af_info_lavfi;
|
||||
extern const struct af_info af_info_rubberband;
|
||||
|
||||
static const struct af_info *const filter_list[] = {
|
||||
&af_info_delay,
|
||||
&af_info_channels,
|
||||
&af_info_format,
|
||||
&af_info_volume,
|
||||
@@ -210,29 +208,6 @@ static struct af_instance *af_prepend(struct af_stream *s,
|
||||
return new;
|
||||
}
|
||||
|
||||
/* Create and insert a new filter of type name after the filter in the
|
||||
argument. This function can be called during runtime, the return
|
||||
value is the new filter */
|
||||
static struct af_instance *af_append(struct af_stream *s,
|
||||
struct af_instance *af,
|
||||
char *name, char **args)
|
||||
{
|
||||
if (!af)
|
||||
af = s->first;
|
||||
if (af == s->last)
|
||||
af = s->last->prev;
|
||||
// Create the new filter and make sure it is OK
|
||||
struct af_instance *new = af_create(s, name, args);
|
||||
if (!new)
|
||||
return NULL;
|
||||
// Update pointers
|
||||
new->prev = af;
|
||||
new->next = af->next;
|
||||
af->next = new;
|
||||
new->next->prev = new;
|
||||
return new;
|
||||
}
|
||||
|
||||
// Uninit and remove the filter "af"
|
||||
static void af_remove(struct af_stream *s, struct af_instance *af)
|
||||
{
|
||||
@@ -275,8 +250,12 @@ static void af_print_filter_chain(struct af_stream *s, struct af_instance *at,
|
||||
while (af) {
|
||||
char b[128] = {0};
|
||||
mp_snprintf_cat(b, sizeof(b), " [%s] ", af->info->name);
|
||||
if (af->label)
|
||||
mp_snprintf_cat(b, sizeof(b), "\"%s\" ", af->label);
|
||||
if (af->data)
|
||||
mp_snprintf_cat(b, sizeof(b), "%s", mp_audio_config_to_str(af->data));
|
||||
if (af->auto_inserted)
|
||||
mp_snprintf_cat(b, sizeof(b), " [a]");
|
||||
if (af == at)
|
||||
mp_snprintf_cat(b, sizeof(b), " <-");
|
||||
MP_MSG(s, msg_level, "%s\n", b);
|
||||
@@ -287,169 +266,156 @@ static void af_print_filter_chain(struct af_stream *s, struct af_instance *at,
|
||||
MP_MSG(s, msg_level, " [ao] %s\n", mp_audio_config_to_str(&s->output));
|
||||
}
|
||||
|
||||
static bool af_is_conversion_filter(struct af_instance *af)
|
||||
{
|
||||
return af && strcmp(af->info->name, "lavrresample") == 0;
|
||||
}
|
||||
|
||||
// in is what af can take as input - insert a conversion filter if the actual
|
||||
// input format doesn't match what af expects.
|
||||
// Returns:
|
||||
// AF_OK: must call af_reinit() or equivalent, format matches (or is closer)
|
||||
// AF_FALSE: nothing was changed, format matches
|
||||
// else: error
|
||||
static int af_fix_format_conversion(struct af_stream *s,
|
||||
struct af_instance **p_af,
|
||||
struct mp_audio in)
|
||||
{
|
||||
int rv;
|
||||
struct af_instance *af = *p_af;
|
||||
struct af_instance *prev = af->prev;
|
||||
struct mp_audio actual = *prev->data;
|
||||
if (actual.format == in.format)
|
||||
return AF_FALSE;
|
||||
int dstfmt = in.format;
|
||||
char *filter = "lavrresample";
|
||||
if (!af_lavrresample_test_conversion(actual.format, dstfmt))
|
||||
return AF_ERROR;
|
||||
if (strcmp(filter, prev->info->name) == 0) {
|
||||
if (prev->control(prev, AF_CONTROL_SET_FORMAT, &dstfmt) == AF_OK) {
|
||||
*p_af = prev;
|
||||
return AF_OK;
|
||||
}
|
||||
return AF_ERROR;
|
||||
}
|
||||
struct af_instance *new = af_prepend(s, af, filter, NULL);
|
||||
if (new == NULL)
|
||||
return AF_ERROR;
|
||||
new->auto_inserted = true;
|
||||
if (AF_OK != (rv = new->control(new, AF_CONTROL_SET_FORMAT, &dstfmt))) {
|
||||
af_remove(s, new);
|
||||
return rv;
|
||||
}
|
||||
*p_af = new;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
// same as af_fix_format_conversion - only wrt. channels
|
||||
static int af_fix_channels(struct af_stream *s, struct af_instance **p_af,
|
||||
struct mp_audio in)
|
||||
{
|
||||
int rv;
|
||||
struct af_instance *af = *p_af;
|
||||
struct af_instance *prev = af->prev;
|
||||
struct mp_audio actual = *prev->data;
|
||||
if (mp_chmap_equals(&actual.channels, &in.channels))
|
||||
return AF_FALSE;
|
||||
if (prev->control(prev, AF_CONTROL_SET_CHANNELS, &in.channels) == AF_OK) {
|
||||
*p_af = prev;
|
||||
return AF_OK;
|
||||
}
|
||||
char *filter = "lavrresample";
|
||||
struct af_instance *new = af_prepend(s, af, filter, NULL);
|
||||
if (new == NULL)
|
||||
return AF_ERROR;
|
||||
new->auto_inserted = true;
|
||||
if (AF_OK != (rv = new->control(new, AF_CONTROL_SET_CHANNELS, &in.channels)))
|
||||
return rv;
|
||||
*p_af = new;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
static int af_fix_rate(struct af_stream *s, struct af_instance **p_af,
|
||||
struct mp_audio in)
|
||||
{
|
||||
int rv;
|
||||
struct af_instance *af = *p_af;
|
||||
struct af_instance *prev = af->prev;
|
||||
struct mp_audio actual = *prev->data;
|
||||
if (actual.rate == in.rate)
|
||||
return AF_FALSE;
|
||||
if (prev->control(prev, AF_CONTROL_SET_RESAMPLE_RATE, &in.rate) == AF_OK) {
|
||||
*p_af = prev;
|
||||
return AF_OK;
|
||||
}
|
||||
char *filter = "lavrresample";
|
||||
struct af_instance *new = af_prepend(s, af, filter, NULL);
|
||||
if (new == NULL)
|
||||
return AF_ERROR;
|
||||
new->auto_inserted = true;
|
||||
if (AF_OK != (rv = new->control(new, AF_CONTROL_SET_RESAMPLE_RATE, &in.rate)))
|
||||
return rv;
|
||||
*p_af = new;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
static void reset_formats(struct af_stream *s)
|
||||
{
|
||||
struct mp_audio none = {0};
|
||||
for (struct af_instance *af = s->first; af; af = af->next) {
|
||||
af->control(af, AF_CONTROL_SET_RESAMPLE_RATE, &(int){0});
|
||||
af->control(af, AF_CONTROL_SET_CHANNELS, &(struct mp_chmap){0});
|
||||
af->control(af, AF_CONTROL_SET_FORMAT, &(int){0});
|
||||
if (af != s->first && af != s->last)
|
||||
mp_audio_copy_config(af->data, &none);
|
||||
}
|
||||
}
|
||||
|
||||
static int filter_reinit(struct af_instance *af)
|
||||
{
|
||||
struct af_instance *prev = af->prev;
|
||||
assert(prev);
|
||||
|
||||
// Check if this is the first filter
|
||||
struct mp_audio in = *prev->data;
|
||||
// Reset just in case...
|
||||
mp_audio_set_null_data(&in);
|
||||
|
||||
if (!mp_audio_config_valid(&in))
|
||||
return AF_ERROR;
|
||||
|
||||
af->fmt_in = in;
|
||||
int rv = af->control(af, AF_CONTROL_REINIT, &in);
|
||||
if (rv == AF_OK && !mp_audio_config_equals(&in, prev->data))
|
||||
rv = AF_FALSE; // conversion filter needed
|
||||
if (rv == AF_FALSE)
|
||||
af->fmt_in = in;
|
||||
|
||||
if (rv == AF_OK) {
|
||||
if (!mp_audio_config_valid(af->data))
|
||||
return AF_ERROR;
|
||||
af->fmt_out = *af->data;
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static int filter_reinit_with_conversion(struct af_stream *s, struct af_instance *af)
|
||||
{
|
||||
int rv = filter_reinit(af);
|
||||
|
||||
// Conversion filter is needed
|
||||
if (rv == AF_FALSE) {
|
||||
// First try if we can change the output format of the previous
|
||||
// filter to the input format the current filter is expecting.
|
||||
struct mp_audio in = af->fmt_in;
|
||||
if (af->prev != s->first && !mp_audio_config_equals(af->prev->data, &in)) {
|
||||
// This should have been successful (because it succeeded
|
||||
// before), even if just reverting to the old output format.
|
||||
mp_audio_copy_config(af->prev->data, &in);
|
||||
rv = filter_reinit(af->prev);
|
||||
if (rv != AF_OK)
|
||||
return rv;
|
||||
}
|
||||
if (!mp_audio_config_equals(af->prev->data, &in)) {
|
||||
// Retry with conversion filter added.
|
||||
struct af_instance *new =
|
||||
af_prepend(s, af, "lavrresample", NULL);
|
||||
if (!new)
|
||||
return AF_ERROR;
|
||||
new->auto_inserted = true;
|
||||
mp_audio_copy_config(new->data, &in);
|
||||
rv = filter_reinit(new);
|
||||
if (rv != AF_OK)
|
||||
af_remove(s, new);
|
||||
}
|
||||
if (rv == AF_OK)
|
||||
rv = filter_reinit(af);
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static int af_find_output_conversion(struct af_stream *s, struct mp_audio *cfg)
|
||||
{
|
||||
assert(mp_audio_config_valid(&s->output));
|
||||
assert(s->initialized > 0);
|
||||
|
||||
if (mp_chmap_equals_reordered(&s->input.channels, &s->output.channels))
|
||||
return AF_ERROR;
|
||||
|
||||
// Heuristic to detect point of conversion. If it looks like something
|
||||
// more complicated is going on, better bail out.
|
||||
// We expect that the last filter converts channels.
|
||||
struct af_instance *conv = s->last->prev;
|
||||
if (!conv->auto_inserted)
|
||||
return AF_ERROR;
|
||||
if (!(mp_chmap_equals_reordered(&conv->fmt_in.channels, &s->input.channels) &&
|
||||
mp_chmap_equals_reordered(&conv->fmt_out.channels, &s->output.channels)))
|
||||
return AF_ERROR;
|
||||
// Also, should be the only one which does auto conversion.
|
||||
for (struct af_instance *af = s->first->next; af != s->last; af = af->next)
|
||||
{
|
||||
if (af != conv && af->auto_inserted &&
|
||||
!mp_chmap_equals_reordered(&af->fmt_in.channels, &af->fmt_out.channels))
|
||||
return AF_ERROR;
|
||||
}
|
||||
// And not if it's the only filter.
|
||||
if (conv->prev == s->first && conv->next == s->last)
|
||||
return AF_ERROR;
|
||||
|
||||
*cfg = s->output;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
// Return AF_OK on success or AF_ERROR on failure.
|
||||
// Warning:
|
||||
// A failed af_reinit() leaves the audio chain behind in a useless, broken
|
||||
// state (for example, format filters that were tentatively inserted stay
|
||||
// inserted).
|
||||
// In that case, you should always rebuild the filter chain, or abort.
|
||||
static int af_reinit(struct af_stream *s)
|
||||
static int af_do_reinit(struct af_stream *s, bool second_pass)
|
||||
{
|
||||
struct mp_audio convert_early = {0};
|
||||
if (second_pass) {
|
||||
// If a channel conversion happens, and it is done by an auto-inserted
|
||||
// filter, then insert a filter to convert it early. Otherwise, do
|
||||
// nothing and return immediately.
|
||||
if (af_find_output_conversion(s, &convert_early) != AF_OK)
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
remove_auto_inserted_filters(s);
|
||||
af_chain_forget_frames(s);
|
||||
reset_formats(s);
|
||||
s->first->fmt_in = s->first->fmt_out = s->input;
|
||||
|
||||
if (mp_audio_config_valid(&convert_early)) {
|
||||
struct af_instance *new = af_prepend(s, s->first, "lavrresample", NULL);
|
||||
if (!new)
|
||||
return AF_ERROR;
|
||||
new->auto_inserted = true;
|
||||
mp_audio_copy_config(new->data, &convert_early);
|
||||
int rv = filter_reinit(new);
|
||||
if (rv != AF_DETACH && rv != AF_OK)
|
||||
return AF_ERROR;
|
||||
MP_VERBOSE(s, "Moving up output conversion.\n");
|
||||
}
|
||||
|
||||
// Start with the second filter, as the first filter is the special input
|
||||
// filter which needs no initialization.
|
||||
struct af_instance *af = s->first->next;
|
||||
// Up to 4 retries per filter (channel, rate, format conversions)
|
||||
int max_retry = 4;
|
||||
int retry = 0;
|
||||
while (af) {
|
||||
if (retry >= max_retry)
|
||||
goto negotiate_error;
|
||||
int rv = filter_reinit_with_conversion(s, af);
|
||||
|
||||
// Check if this is the first filter
|
||||
struct mp_audio in = *af->prev->data;
|
||||
// Reset just in case...
|
||||
mp_audio_set_null_data(&in);
|
||||
|
||||
if (!mp_audio_config_valid(&in))
|
||||
goto error;
|
||||
|
||||
af->fmt_in = in;
|
||||
int rv = af->control(af, AF_CONTROL_REINIT, &in);
|
||||
if (rv == AF_OK && !mp_audio_config_equals(&in, af->prev->data))
|
||||
rv = AF_FALSE; // conversion filter needed
|
||||
switch (rv) {
|
||||
case AF_OK:
|
||||
if (!mp_audio_config_valid(af->data))
|
||||
goto error;
|
||||
af->fmt_out = *af->data;
|
||||
af = af->next;
|
||||
break;
|
||||
case AF_FALSE: { // Configuration filter is needed
|
||||
if (af_fix_channels(s, &af, in) == AF_OK) {
|
||||
retry++;
|
||||
continue;
|
||||
}
|
||||
if (af_fix_rate(s, &af, in) == AF_OK) {
|
||||
retry++;
|
||||
continue;
|
||||
}
|
||||
// Do this last, to prevent "format->lavrresample" being added to
|
||||
// the filter chain when output formats not supported by
|
||||
// af_lavrresample are in use.
|
||||
if (af_fix_format_conversion(s, &af, in) == AF_OK) {
|
||||
retry++;
|
||||
continue;
|
||||
}
|
||||
case AF_FALSE: {
|
||||
// If the format conversion is (probably) caused by spdif, then
|
||||
// (as a feature) drop the filter, instead of failing hard.
|
||||
int fmt_in1 = af->prev->data->format;
|
||||
int fmt_in2 = in.format;
|
||||
int fmt_in2 = af->fmt_in.format;
|
||||
if (af_fmt_is_valid(fmt_in1) && af_fmt_is_valid(fmt_in2)) {
|
||||
bool spd1 = af_fmt_is_spdif(fmt_in1);
|
||||
bool spd2 = af_fmt_is_spdif(fmt_in2);
|
||||
@@ -460,7 +426,6 @@ static int af_reinit(struct af_stream *s)
|
||||
struct af_instance *aft = af->prev;
|
||||
af_remove(s, af);
|
||||
af = aft->next;
|
||||
retry++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -478,8 +443,6 @@ static int af_reinit(struct af_stream *s)
|
||||
af->info->name, rv);
|
||||
goto error;
|
||||
}
|
||||
if (af && !af->auto_inserted)
|
||||
retry = 0;
|
||||
}
|
||||
|
||||
/* Set previously unset fields in s->output to those of the filter chain
|
||||
@@ -503,6 +466,19 @@ error:
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
static int af_reinit(struct af_stream *s)
|
||||
{
|
||||
int r = af_do_reinit(s, false);
|
||||
if (r == AF_OK && mp_audio_config_valid(&s->output)) {
|
||||
r = af_do_reinit(s, true);
|
||||
if (r != AF_OK) {
|
||||
MP_ERR(s, "Failed second pass filter negotiation.\n");
|
||||
r = af_do_reinit(s, false);
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
// Uninit and remove all filters
|
||||
void af_uninit(struct af_stream *s)
|
||||
{
|
||||
@@ -557,7 +533,7 @@ void af_destroy(struct af_stream *s)
|
||||
format of the preferred output respectively. The function is
|
||||
reentrant i.e. if called with an already initialized stream the
|
||||
stream will be reinitialized.
|
||||
If one of the prefered output parameters is 0 the one that needs
|
||||
If one of the preferred output parameters is 0 the one that needs
|
||||
no conversion is used (i.e. the output format in the last filter).
|
||||
The return value is 0 if success and -1 if failure */
|
||||
int af_init(struct af_stream *s)
|
||||
@@ -602,12 +578,7 @@ struct af_instance *af_add(struct af_stream *s, char *name, char *label,
|
||||
if (af_find_by_label(s, label))
|
||||
return NULL;
|
||||
|
||||
struct af_instance *new;
|
||||
// Insert the filter somewhere nice
|
||||
if (af_is_conversion_filter(s->first->next))
|
||||
new = af_append(s, s->first->next, name, args);
|
||||
else
|
||||
new = af_prepend(s, s->first->next, name, args);
|
||||
struct af_instance *new = af_prepend(s, s->last, name, args);
|
||||
if (!new)
|
||||
return NULL;
|
||||
new->label = talloc_strdup(new, label);
|
||||
|
||||
@@ -112,9 +112,6 @@ struct af_stream {
|
||||
enum af_control {
|
||||
AF_CONTROL_REINIT = 1,
|
||||
AF_CONTROL_RESET,
|
||||
AF_CONTROL_SET_RESAMPLE_RATE,
|
||||
AF_CONTROL_SET_FORMAT,
|
||||
AF_CONTROL_SET_CHANNELS,
|
||||
AF_CONTROL_SET_VOLUME,
|
||||
AF_CONTROL_GET_VOLUME,
|
||||
AF_CONTROL_SET_PAN_LEVEL,
|
||||
@@ -160,6 +157,4 @@ int af_test_output(struct af_instance *af, struct mp_audio *out);
|
||||
int af_from_ms(int n, float *in, int *out, int rate, float mi, float ma);
|
||||
float af_softclip(float a);
|
||||
|
||||
bool af_lavrresample_test_conversion(int src_format, int dst_format);
|
||||
|
||||
#endif /* MPLAYER_AF_H */
|
||||
@@ -1,194 +0,0 @@
|
||||
/*
|
||||
* This audio filter delays the output signal for the different
|
||||
* channels and can be used for simple position panning.
|
||||
* An extension for this filter would be a reverb.
|
||||
*
|
||||
* Original author: Anders
|
||||
*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "af.h"
|
||||
|
||||
#define L 65536
|
||||
|
||||
#define UPDATEQI(qi) qi=(qi+1)&(L-1)
|
||||
|
||||
// Data for specific instances of this filter
|
||||
typedef struct af_delay_s
|
||||
{
|
||||
void* q[AF_NCH]; // Circular queues used for delaying audio signal
|
||||
int wi[AF_NCH]; // Write index
|
||||
int ri; // Read index
|
||||
float d[AF_NCH]; // Delay [ms]
|
||||
char *delaystr;
|
||||
}af_delay_t;
|
||||
|
||||
// Initialization and runtime control
|
||||
static int control(struct af_instance* af, int cmd, void* arg)
|
||||
{
|
||||
af_delay_t* s = af->priv;
|
||||
switch(cmd){
|
||||
case AF_CONTROL_REINIT:{
|
||||
int i;
|
||||
struct mp_audio *in = arg;
|
||||
|
||||
if (in->bps != 1 && in->bps != 2 && in->bps != 4) {
|
||||
MP_FATAL(af, "Sample format not supported\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
// Free prevous delay queues
|
||||
for(i=0;i<af->data->nch;i++)
|
||||
free(s->q[i]);
|
||||
|
||||
mp_audio_force_interleaved_format(in);
|
||||
mp_audio_copy_config(af->data, in);
|
||||
|
||||
// Allocate new delay queues
|
||||
for(i=0;i<af->data->nch;i++){
|
||||
s->q[i] = calloc(L,af->data->bps);
|
||||
if(NULL == s->q[i])
|
||||
MP_FATAL(af, "Out of memory\n");
|
||||
}
|
||||
|
||||
if(AF_OK != af_from_ms(AF_NCH, s->d, s->wi, af->data->rate, 0.0, 1000.0))
|
||||
return AF_ERROR;
|
||||
s->ri = 0;
|
||||
for(i=0;i<AF_NCH;i++){
|
||||
MP_DBG(af, "Channel %i delayed by %0.3fms\n",
|
||||
i,MPCLAMP(s->d[i],0.0,1000.0));
|
||||
MP_TRACE(af, "Channel %i delayed by %i samples\n",
|
||||
i,s->wi[i]);
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
// Deallocate memory
|
||||
static void uninit(struct af_instance* af)
|
||||
{
|
||||
int i;
|
||||
|
||||
for(i=0;i<AF_NCH;i++)
|
||||
free(((af_delay_t*)(af->priv))->q[i]);
|
||||
}
|
||||
|
||||
static int filter_frame(struct af_instance *af, struct mp_audio *c)
|
||||
{
|
||||
if (!c)
|
||||
return 0;
|
||||
af_delay_t* s = af->priv; // Setup for this instance
|
||||
int nch = c->nch; // Number of channels
|
||||
int len = mp_audio_psize(c)/c->bps; // Number of sample in data chunk
|
||||
int ri = 0;
|
||||
int ch,i;
|
||||
if (af_make_writeable(af, c) < 0) {
|
||||
talloc_free(c);
|
||||
return -1;
|
||||
}
|
||||
for(ch=0;ch<nch;ch++){
|
||||
switch(c->bps){
|
||||
case 1:{
|
||||
int8_t* a = c->planes[0];
|
||||
int8_t* q = s->q[ch];
|
||||
int wi = s->wi[ch];
|
||||
ri = s->ri;
|
||||
for(i=ch;i<len;i+=nch){
|
||||
q[wi] = a[i];
|
||||
a[i] = q[ri];
|
||||
UPDATEQI(wi);
|
||||
UPDATEQI(ri);
|
||||
}
|
||||
s->wi[ch] = wi;
|
||||
break;
|
||||
}
|
||||
case 2:{
|
||||
int16_t* a = c->planes[0];
|
||||
int16_t* q = s->q[ch];
|
||||
int wi = s->wi[ch];
|
||||
ri = s->ri;
|
||||
for(i=ch;i<len;i+=nch){
|
||||
q[wi] = a[i];
|
||||
a[i] = q[ri];
|
||||
UPDATEQI(wi);
|
||||
UPDATEQI(ri);
|
||||
}
|
||||
s->wi[ch] = wi;
|
||||
break;
|
||||
}
|
||||
case 4:{
|
||||
int32_t* a = c->planes[0];
|
||||
int32_t* q = s->q[ch];
|
||||
int wi = s->wi[ch];
|
||||
ri = s->ri;
|
||||
for(i=ch;i<len;i+=nch){
|
||||
q[wi] = a[i];
|
||||
a[i] = q[ri];
|
||||
UPDATEQI(wi);
|
||||
UPDATEQI(ri);
|
||||
}
|
||||
s->wi[ch] = wi;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
s->ri = ri;
|
||||
af_add_output_frame(af, c);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Allocate memory and set function pointers
|
||||
static int af_open(struct af_instance* af){
|
||||
af->control=control;
|
||||
af->uninit=uninit;
|
||||
af->filter_frame = filter_frame;
|
||||
af_delay_t *s = af->priv;
|
||||
int n = 1;
|
||||
int i = 0;
|
||||
char* cl = s->delaystr;
|
||||
while(cl && n && i < AF_NCH ){
|
||||
sscanf(cl,"%f%n",&s->d[i],&n);
|
||||
if(n==0 || cl[n-1] == '\0')
|
||||
break;
|
||||
cl=&cl[n];
|
||||
if (*cl != ',')
|
||||
break;
|
||||
cl++;
|
||||
i++;
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT af_delay_t
|
||||
const struct af_info af_info_delay = {
|
||||
.info = "Delay audio filter",
|
||||
.name = "delay",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(af_delay_t),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_STRING("delays", delaystr, 0),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
@@ -29,10 +29,10 @@ struct priv {
|
||||
|
||||
int in_format;
|
||||
int in_srate;
|
||||
struct mp_chmap in_channels;
|
||||
struct m_channels in_channels;
|
||||
int out_format;
|
||||
int out_srate;
|
||||
struct mp_chmap out_channels;
|
||||
struct m_channels out_channels;
|
||||
|
||||
int fail;
|
||||
};
|
||||
@@ -44,8 +44,8 @@ static void force_in_params(struct af_instance *af, struct mp_audio *in)
|
||||
if (priv->in_format != AF_FORMAT_UNKNOWN)
|
||||
mp_audio_set_format(in, priv->in_format);
|
||||
|
||||
if (priv->in_channels.num)
|
||||
mp_audio_set_channels(in, &priv->in_channels);
|
||||
if (priv->in_channels.num_chmaps > 0)
|
||||
mp_audio_set_channels(in, &priv->in_channels.chmaps[0]);
|
||||
|
||||
if (priv->in_srate)
|
||||
in->rate = priv->in_srate;
|
||||
@@ -58,8 +58,8 @@ static void force_out_params(struct af_instance *af, struct mp_audio *out)
|
||||
if (priv->out_format != AF_FORMAT_UNKNOWN)
|
||||
mp_audio_set_format(out, priv->out_format);
|
||||
|
||||
if (priv->out_channels.num)
|
||||
mp_audio_set_channels(out, &priv->out_channels);
|
||||
if (priv->out_channels.num_chmaps > 0)
|
||||
mp_audio_set_channels(out, &priv->out_channels.chmaps[0]);
|
||||
|
||||
if (priv->out_srate)
|
||||
out->rate = priv->out_srate;
|
||||
@@ -124,10 +124,10 @@ const struct af_info af_info_format = {
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_AUDIOFORMAT("format", in_format, 0),
|
||||
OPT_INTRANGE("srate", in_srate, 0, 1000, 8*48000),
|
||||
OPT_CHMAP("channels", in_channels, CONF_MIN, .min = 0),
|
||||
OPT_CHANNELS("channels", in_channels, 0, .min = 1),
|
||||
OPT_AUDIOFORMAT("out-format", out_format, 0),
|
||||
OPT_INTRANGE("out-srate", out_srate, 0, 1000, 8*48000),
|
||||
OPT_CHMAP("out-channels", out_channels, CONF_MIN, .min = 0),
|
||||
OPT_CHANNELS("out-channels", out_channels, 0, .min = 1),
|
||||
OPT_FLAG("fail", fail, 0),
|
||||
{0}
|
||||
},
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "common/av_common.h"
|
||||
#include "common/common.h"
|
||||
#include "af.h"
|
||||
#include "audio/audio_buffer.h"
|
||||
@@ -57,11 +58,13 @@ typedef struct af_ac3enc_s {
|
||||
struct mp_audio *pending; // unconsumed input data
|
||||
int in_samples; // samples of input per AC3 frame
|
||||
int out_samples; // upper bound on encoded output per AC3 frame
|
||||
int64_t encoder_buffered;
|
||||
|
||||
int cfg_add_iec61937_header;
|
||||
int cfg_bit_rate;
|
||||
int cfg_min_channel_num;
|
||||
char *cfg_encoder;
|
||||
char **cfg_avopts;
|
||||
} af_ac3enc_t;
|
||||
|
||||
// fmt carries the input format. Change it to the best next-possible format
|
||||
@@ -168,6 +171,7 @@ static int control(struct af_instance *af, int cmd, void *arg)
|
||||
s->in_samples = s->lavc_actx->frame_size;
|
||||
mp_audio_realloc(s->input, s->in_samples);
|
||||
s->input->samples = 0;
|
||||
s->encoder_buffered = 0;
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_RESET:
|
||||
@@ -176,6 +180,7 @@ static int control(struct af_instance *af, int cmd, void *arg)
|
||||
talloc_free(s->pending);
|
||||
s->pending = NULL;
|
||||
s->input->samples = 0;
|
||||
s->encoder_buffered = 0;
|
||||
return AF_OK;
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
@@ -198,8 +203,8 @@ static void uninit(struct af_instance* af)
|
||||
static void update_delay(struct af_instance *af)
|
||||
{
|
||||
af_ac3enc_t *s = af->priv;
|
||||
af->delay = ((s->pending ? s->pending->samples : 0) + s->input->samples) /
|
||||
(double)s->input->rate;
|
||||
af->delay = ((s->pending ? s->pending->samples : 0) + s->input->samples +
|
||||
s->encoder_buffered) / (double)s->input->rate;
|
||||
}
|
||||
|
||||
static int filter_frame(struct af_instance *af, struct mp_audio *audio)
|
||||
@@ -232,6 +237,9 @@ static bool fill_buffer(struct af_instance *af)
|
||||
af->delay = 0;
|
||||
|
||||
if (s->pending) {
|
||||
if (!mp_audio_is_writeable(s->input))
|
||||
assert(s->input->samples == 0); // we can't have sent a partial frame
|
||||
mp_audio_realloc_min(s->input, s->in_samples);
|
||||
int copy = MPMIN(s->in_samples - s->input->samples, s->pending->samples);
|
||||
s->input->samples += copy;
|
||||
mp_audio_copy(s->input, s->input->samples - copy, s->pending, 0, copy);
|
||||
@@ -249,17 +257,8 @@ static int read_input_frame(struct af_instance *af, AVFrame *frame)
|
||||
if (!fill_buffer(af))
|
||||
return 0; // need more input
|
||||
|
||||
frame->nb_samples = s->in_samples;
|
||||
frame->format = s->lavc_actx->sample_fmt;
|
||||
frame->channel_layout = s->lavc_actx->channel_layout;
|
||||
#if LIBAVUTIL_VERSION_MICRO >= 100
|
||||
frame->channels = s->lavc_actx->channels;
|
||||
#endif
|
||||
assert(s->input->num_planes <= AV_NUM_DATA_POINTERS);
|
||||
frame->extended_data = frame->data;
|
||||
for (int n = 0; n < s->input->num_planes; n++)
|
||||
frame->data[n] = s->input->planes[n];
|
||||
frame->linesize[0] = s->input->samples * s->input->sstride;
|
||||
if (mp_audio_to_avframe(s->input, frame) < 0)
|
||||
return -1;
|
||||
|
||||
return 1;
|
||||
}
|
||||
@@ -268,6 +267,9 @@ static int filter_out(struct af_instance *af)
|
||||
{
|
||||
af_ac3enc_t *s = af->priv;
|
||||
|
||||
if (!s->pending)
|
||||
return 0;
|
||||
|
||||
AVFrame *frame = av_frame_alloc();
|
||||
if (!frame) {
|
||||
MP_FATAL(af, "Could not allocate memory \n");
|
||||
@@ -295,6 +297,7 @@ static int filter_out(struct af_instance *af)
|
||||
MP_FATAL(af, "Encode failed.\n");
|
||||
goto done;
|
||||
}
|
||||
s->encoder_buffered += s->input->samples;
|
||||
s->input->samples = 0;
|
||||
}
|
||||
int lavc_ret = avcodec_receive_packet(s->lavc_actx, &pkt);
|
||||
@@ -303,6 +306,10 @@ static int filter_out(struct af_instance *af)
|
||||
err = 0;
|
||||
goto done;
|
||||
}
|
||||
if (lavc_ret < 0) {
|
||||
MP_FATAL(af, "Encode failed.\n");
|
||||
goto done;
|
||||
}
|
||||
#else
|
||||
err = read_input_frame(af, frame);
|
||||
if (err < 0)
|
||||
@@ -312,7 +319,6 @@ static int filter_out(struct af_instance *af)
|
||||
err = -1;
|
||||
int ok;
|
||||
int lavc_ret = avcodec_encode_audio2(s->lavc_actx, &pkt, frame, &ok);
|
||||
av_frame_free(&frame);
|
||||
s->input->samples = 0;
|
||||
if (lavc_ret < 0 || !ok) {
|
||||
MP_FATAL(af, "Encode failed.\n");
|
||||
@@ -321,7 +327,9 @@ static int filter_out(struct af_instance *af)
|
||||
#endif
|
||||
|
||||
MP_DBG(af, "avcodec_encode_audio got %d, pending %d.\n",
|
||||
pkt.size, s->pending->samples);
|
||||
pkt.size, s->pending->samples + s->input->samples);
|
||||
|
||||
s->encoder_buffered -= AC3_FRAME_SIZE;
|
||||
|
||||
struct mp_audio *out =
|
||||
mp_audio_pool_get(af->out_pool, af->data, s->out_samples);
|
||||
@@ -358,11 +366,12 @@ static int filter_out(struct af_instance *af)
|
||||
swap_16((uint16_t *)(buf + header_len), pkt.size / 2);
|
||||
out->samples = frame_size / out->sstride;
|
||||
af_add_output_frame(af, out);
|
||||
update_delay(af);
|
||||
|
||||
err = 0;
|
||||
done:
|
||||
av_packet_unref(&pkt);
|
||||
av_frame_free(&frame);
|
||||
update_delay(af);
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -385,6 +394,10 @@ static int af_open(struct af_instance* af){
|
||||
MP_ERR(af, "Audio LAVC, couldn't allocate context!\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
if (mp_set_avopts(af->log, s->lavc_actx, s->cfg_avopts) < 0)
|
||||
return AF_ERROR;
|
||||
|
||||
// For this one, we require the decoder to expert lists of all supported
|
||||
// parameters. (Not all decoders do that, but the ones we're interested
|
||||
// in do.)
|
||||
@@ -434,6 +447,7 @@ const struct af_info af_info_lavcac3enc = {
|
||||
({"auto", 0}, {"default", 0})),
|
||||
OPT_INTRANGE("minch", cfg_min_channel_num, 0, 2, 6),
|
||||
OPT_STRING("encoder", cfg_encoder, 0),
|
||||
OPT_KEYVALUELIST("o", cfg_avopts, 0),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
@@ -173,12 +173,6 @@ static int check_output_conversion(int mp_format)
|
||||
return af_to_avformat(mp_format);
|
||||
}
|
||||
|
||||
bool af_lavrresample_test_conversion(int src_format, int dst_format)
|
||||
{
|
||||
return af_to_avformat(src_format) != AV_SAMPLE_FMT_NONE &&
|
||||
check_output_conversion(dst_format) != AV_SAMPLE_FMT_NONE;
|
||||
}
|
||||
|
||||
static struct mp_chmap fudge_pairs[][2] = {
|
||||
{MP_CHMAP2(BL, BR), MP_CHMAP2(SL, SR)},
|
||||
{MP_CHMAP2(SL, SR), MP_CHMAP2(BL, BR)},
|
||||
@@ -407,21 +401,6 @@ static int control(struct af_instance *af, int cmd, void *arg)
|
||||
r = configure_lavrr(af, in, out, true);
|
||||
return r;
|
||||
}
|
||||
case AF_CONTROL_SET_FORMAT: {
|
||||
int format = *(int *)arg;
|
||||
if (format && check_output_conversion(format) == AV_SAMPLE_FMT_NONE)
|
||||
return AF_FALSE;
|
||||
|
||||
mp_audio_set_format(af->data, format);
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_SET_CHANNELS: {
|
||||
mp_audio_set_channels(af->data, (struct mp_chmap *)arg);
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_SET_RESAMPLE_RATE:
|
||||
af->data->rate = *(int *)arg;
|
||||
return AF_OK;
|
||||
case AF_CONTROL_SET_PLAYBACK_SPEED_RESAMPLE: {
|
||||
s->playback_speed = *(double *)arg;
|
||||
return AF_OK;
|
||||
|
||||
@@ -71,15 +71,19 @@ static int control(struct af_instance *af, int cmd, void *arg)
|
||||
if (af_fmt_is_planar(in->format))
|
||||
mp_audio_set_format(af->data, af_fmt_to_planar(af->data->format));
|
||||
s->rgain = 1.0;
|
||||
if ((s->rgain_track || s->rgain_album) && af->replaygain_data) {
|
||||
float gain, peak;
|
||||
struct replaygain_data *rg = af->replaygain_data;
|
||||
if ((s->rgain_track || s->rgain_album) && rg) {
|
||||
MP_VERBOSE(af, "Replaygain: Track=%f/%f Album=%f/%f\n",
|
||||
rg->track_gain, rg->track_peak,
|
||||
rg->album_gain, rg->album_peak);
|
||||
|
||||
float gain, peak;
|
||||
if (s->rgain_track) {
|
||||
gain = af->replaygain_data->track_gain;
|
||||
peak = af->replaygain_data->track_peak;
|
||||
gain = rg->track_gain;
|
||||
peak = rg->track_peak;
|
||||
} else {
|
||||
gain = af->replaygain_data->album_gain;
|
||||
peak = af->replaygain_data->album_peak;
|
||||
gain = rg->album_gain;
|
||||
peak = rg->album_peak;
|
||||
}
|
||||
|
||||
gain += s->rgain_preamp;
|
||||
@@ -115,7 +119,7 @@ static void filter_plane(struct af_instance *af, struct mp_audio *data, int p)
|
||||
{
|
||||
struct priv *s = af->priv;
|
||||
|
||||
float level = s->level * s->rgain;
|
||||
float level = s->level * s->rgain * from_dB(s->cfg_volume, 20.0, -200.0, 60.0);
|
||||
int num_samples = data->samples * data->spf;
|
||||
|
||||
if (af_fmt_from_planar(af->data->format) == AF_FORMAT_S16) {
|
||||
@@ -158,7 +162,7 @@ static int af_open(struct af_instance *af)
|
||||
struct priv *s = af->priv;
|
||||
af->control = control;
|
||||
af->filter_frame = filter;
|
||||
s->level = from_dB(s->cfg_volume, 20.0, -200.0, 60.0);
|
||||
s->level = 1.0;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
|
||||
-403
@@ -1,403 +0,0 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <libavutil/common.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "audio/out/ao.h"
|
||||
#include "audio/filter/af.h"
|
||||
#include "common/global.h"
|
||||
#include "common/msg.h"
|
||||
#include "mpv_talloc.h"
|
||||
#include "mixer.h"
|
||||
|
||||
struct mixer {
|
||||
struct mp_log *log;
|
||||
struct MPOpts *opts;
|
||||
struct ao *ao;
|
||||
struct af_stream *af;
|
||||
// Static, dependent on ao/softvol settings
|
||||
bool softvol; // use AO (false) or af_volume (true)
|
||||
bool persistent_volume; // volume does not need to be restored
|
||||
bool emulate_mute; // if true, emulate mute with volume=0
|
||||
// Last known values (possibly out of sync with reality)
|
||||
float vol_l, vol_r;
|
||||
bool muted;
|
||||
// Used to decide whether we should unmute on uninit
|
||||
bool muted_by_us;
|
||||
/* Contains ao driver name or "softvol" if volume is not persistent
|
||||
* and needs to be restored after the driver is reinitialized. */
|
||||
const char *driver;
|
||||
// Other stuff
|
||||
float balance;
|
||||
};
|
||||
|
||||
struct mixer *mixer_init(void *talloc_ctx, struct mpv_global *global)
|
||||
{
|
||||
struct mixer *mixer = talloc_ptrtype(talloc_ctx, mixer);
|
||||
*mixer = (struct mixer) {
|
||||
.log = mp_log_new(mixer, global->log, "mixer"),
|
||||
.opts = global->opts,
|
||||
.vol_l = 100,
|
||||
.vol_r = 100,
|
||||
.driver = "",
|
||||
};
|
||||
return mixer;
|
||||
}
|
||||
|
||||
bool mixer_audio_initialized(struct mixer *mixer)
|
||||
{
|
||||
return !!mixer->ao;
|
||||
}
|
||||
|
||||
float mixer_getmaxvolume(struct mixer *mixer)
|
||||
{
|
||||
// gain == 1
|
||||
return mixer->softvol ? mixer->opts->softvol_max : 100;
|
||||
}
|
||||
|
||||
static void checkvolume(struct mixer *mixer)
|
||||
{
|
||||
if (!mixer->ao)
|
||||
return;
|
||||
|
||||
ao_control_vol_t vol = {mixer->vol_l, mixer->vol_r};
|
||||
if (mixer->softvol) {
|
||||
float gain;
|
||||
if (!af_control_any_rev(mixer->af, AF_CONTROL_GET_VOLUME, &gain))
|
||||
gain = 1.0;
|
||||
vol.left = gain * 100.0;
|
||||
vol.right = gain * 100.0;
|
||||
} else {
|
||||
MP_DBG(mixer, "Reading volume from AO.\n");
|
||||
// Rely on the values not changing if the query is not supported
|
||||
ao_control(mixer->ao, AOCONTROL_GET_VOLUME, &vol);
|
||||
ao_control(mixer->ao, AOCONTROL_GET_MUTE, &mixer->muted);
|
||||
}
|
||||
float l = mixer->vol_l;
|
||||
float r = mixer->vol_r;
|
||||
if (mixer->emulate_mute && mixer->muted)
|
||||
l = r = 0;
|
||||
/* Try to detect cases where the volume has been changed by some external
|
||||
* action (such as something else changing a shared system-wide volume).
|
||||
* We don't test for exact equality, as some AOs may round the value
|
||||
* we last set to some nearby supported value. 3 has been the default
|
||||
* volume step for increase/decrease keys, and is apparently big enough
|
||||
* to step to the next possible value in most setups.
|
||||
*/
|
||||
if (FFABS(vol.left - l) >= 3 || FFABS(vol.right - r) >= 3) {
|
||||
mixer->vol_l = vol.left;
|
||||
mixer->vol_r = vol.right;
|
||||
if (mixer->emulate_mute)
|
||||
mixer->muted = false;
|
||||
}
|
||||
mixer->muted_by_us &= mixer->muted;
|
||||
}
|
||||
|
||||
void mixer_getvolume(struct mixer *mixer, float *l, float *r)
|
||||
{
|
||||
checkvolume(mixer);
|
||||
*l = mixer->vol_l;
|
||||
*r = mixer->vol_r;
|
||||
}
|
||||
|
||||
static void setvolume_internal(struct mixer *mixer)
|
||||
{
|
||||
float l = mixer->vol_l, r = mixer->vol_r;
|
||||
if (mixer->emulate_mute && mixer->muted)
|
||||
l = r = 0;
|
||||
if (!mixer->softvol) {
|
||||
MP_DBG(mixer, "Setting volume on AO.\n");
|
||||
struct ao_control_vol vol = {.left = l, .right = r};
|
||||
if (ao_control(mixer->ao, AOCONTROL_SET_VOLUME, &vol) != CONTROL_OK)
|
||||
MP_ERR(mixer, "Failed to change audio output volume.\n");
|
||||
return;
|
||||
}
|
||||
float gain = (l + r) / 2.0 / 100.0;
|
||||
if (!af_control_any_rev(mixer->af, AF_CONTROL_SET_VOLUME, &gain)) {
|
||||
if (gain == 1.0)
|
||||
return;
|
||||
MP_VERBOSE(mixer, "Inserting volume filter.\n");
|
||||
if (!(af_add(mixer->af, "volume", "softvol", NULL)
|
||||
&& af_control_any_rev(mixer->af, AF_CONTROL_SET_VOLUME, &gain)))
|
||||
MP_ERR(mixer, "No volume control available.\n");
|
||||
}
|
||||
}
|
||||
|
||||
void mixer_setvolume(struct mixer *mixer, float l, float r)
|
||||
{
|
||||
checkvolume(mixer); // to check mute status
|
||||
|
||||
float max = mixer_getmaxvolume(mixer);
|
||||
mixer->vol_l = MPCLAMP(l, 0, max);
|
||||
mixer->vol_r = MPCLAMP(r, 0, max);
|
||||
if (mixer->ao)
|
||||
setvolume_internal(mixer);
|
||||
}
|
||||
|
||||
void mixer_getbothvolume(struct mixer *mixer, float *b)
|
||||
{
|
||||
float mixer_l, mixer_r;
|
||||
mixer_getvolume(mixer, &mixer_l, &mixer_r);
|
||||
*b = (mixer_l + mixer_r) / 2;
|
||||
}
|
||||
|
||||
void mixer_setmute(struct mixer *mixer, bool mute)
|
||||
{
|
||||
checkvolume(mixer);
|
||||
if (mute == mixer->muted)
|
||||
return;
|
||||
if (mixer->ao) {
|
||||
mixer->muted = mute;
|
||||
mixer->muted_by_us = mute;
|
||||
if (mixer->emulate_mute) {
|
||||
setvolume_internal(mixer);
|
||||
} else {
|
||||
ao_control(mixer->ao, AOCONTROL_SET_MUTE, &mute);
|
||||
}
|
||||
checkvolume(mixer);
|
||||
} else {
|
||||
mixer->muted = mute;
|
||||
mixer->muted_by_us = mute;
|
||||
}
|
||||
}
|
||||
|
||||
bool mixer_getmute(struct mixer *mixer)
|
||||
{
|
||||
checkvolume(mixer);
|
||||
return mixer->muted;
|
||||
}
|
||||
|
||||
void mixer_addvolume(struct mixer *mixer, float step)
|
||||
{
|
||||
float vol_l, vol_r;
|
||||
mixer_getvolume(mixer, &vol_l, &vol_r);
|
||||
mixer_setvolume(mixer, vol_l + step, vol_r + step);
|
||||
}
|
||||
|
||||
void mixer_getbalance(struct mixer *mixer, float *val)
|
||||
{
|
||||
if (mixer->af)
|
||||
af_control_any_rev(mixer->af, AF_CONTROL_GET_PAN_BALANCE, &mixer->balance);
|
||||
*val = mixer->balance;
|
||||
}
|
||||
|
||||
/* NOTE: Currently the balance code is seriously buggy: it always changes
|
||||
* the af_pan mapping between the first two input channels and first two
|
||||
* output channels to particular values. These values make sense for an
|
||||
* af_pan instance that was automatically inserted for balance control
|
||||
* only and is otherwise an identity transform, but if the filter was
|
||||
* there for another reason, then ignoring and overriding the original
|
||||
* values is completely wrong.
|
||||
*/
|
||||
|
||||
void mixer_setbalance(struct mixer *mixer, float val)
|
||||
{
|
||||
struct af_instance *af_pan_balance;
|
||||
|
||||
mixer->balance = val;
|
||||
|
||||
if (!mixer->af)
|
||||
return;
|
||||
|
||||
if (af_control_any_rev(mixer->af, AF_CONTROL_SET_PAN_BALANCE, &val))
|
||||
return;
|
||||
|
||||
if (val == 0)
|
||||
return;
|
||||
|
||||
if (!(af_pan_balance = af_add(mixer->af, "pan", "autopan", NULL))) {
|
||||
MP_ERR(mixer, "No balance control available.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* make all other channels pass through since by default pan blocks all */
|
||||
for (int i = 2; i < AF_NCH; i++) {
|
||||
float level[AF_NCH] = {0};
|
||||
level[i] = 1.f;
|
||||
af_control_ext_t arg_ext = { .ch = i, .arg = level };
|
||||
af_pan_balance->control(af_pan_balance, AF_CONTROL_SET_PAN_LEVEL,
|
||||
&arg_ext);
|
||||
}
|
||||
|
||||
af_pan_balance->control(af_pan_balance, AF_CONTROL_SET_PAN_BALANCE, &val);
|
||||
}
|
||||
|
||||
char *mixer_get_volume_restore_data(struct mixer *mixer)
|
||||
{
|
||||
if (!mixer->driver[0])
|
||||
return NULL;
|
||||
return talloc_asprintf(NULL, "%s:%f:%f:%d", mixer->driver, mixer->vol_l,
|
||||
mixer->vol_r, mixer->muted_by_us);
|
||||
}
|
||||
|
||||
static void probe_softvol(struct mixer *mixer)
|
||||
{
|
||||
bool ao_perapp = ao_control(mixer->ao, AOCONTROL_HAS_PER_APP_VOLUME, 0) == 1;
|
||||
bool ao_softvol = ao_control(mixer->ao, AOCONTROL_HAS_SOFT_VOLUME, 0) == 1;
|
||||
assert(!(ao_perapp && ao_softvol));
|
||||
mixer->persistent_volume = !ao_softvol;
|
||||
|
||||
if (mixer->opts->softvol == SOFTVOL_AUTO) {
|
||||
// No system-wide volume => fine with AO volume control.
|
||||
mixer->softvol = !ao_softvol && !ao_perapp;
|
||||
} else {
|
||||
mixer->softvol = mixer->opts->softvol == SOFTVOL_YES;
|
||||
}
|
||||
|
||||
if (mixer->softvol)
|
||||
mixer->persistent_volume = false;
|
||||
|
||||
MP_DBG(mixer, "Will use af_volume: %s\n", mixer->softvol ? "yes" : "no");
|
||||
|
||||
// If we can't use real volume control => force softvol.
|
||||
if (!mixer->softvol) {
|
||||
ao_control_vol_t vol;
|
||||
if (ao_control(mixer->ao, AOCONTROL_GET_VOLUME, &vol) != CONTROL_OK) {
|
||||
mixer->softvol = true;
|
||||
MP_WARN(mixer, "Hardware volume control unavailable.\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Probe native mute support.
|
||||
mixer->emulate_mute = true;
|
||||
if (!mixer->softvol) {
|
||||
if (ao_control(mixer->ao, AOCONTROL_GET_MUTE, &(bool){0}) == CONTROL_OK)
|
||||
mixer->emulate_mute = false;
|
||||
}
|
||||
}
|
||||
|
||||
static void restore_volume(struct mixer *mixer)
|
||||
{
|
||||
struct MPOpts *opts = mixer->opts;
|
||||
struct ao *ao = mixer->ao;
|
||||
|
||||
float force_vol_l = -1, force_vol_r = -1;
|
||||
int force_mute = -1;
|
||||
|
||||
const char *prev_driver = mixer->driver;
|
||||
mixer->driver = mixer->softvol ? "softvol" : ao_get_name(ao);
|
||||
if (!prev_driver[0])
|
||||
prev_driver = mixer->driver;
|
||||
|
||||
// Restore old parameters if volume won't survive reinitialization.
|
||||
// But not if volume scale is possibly different.
|
||||
if (!mixer->persistent_volume && strcmp(mixer->driver, prev_driver) == 0) {
|
||||
force_vol_l = mixer->vol_l;
|
||||
force_vol_r = mixer->vol_r;
|
||||
}
|
||||
|
||||
// Set mute if we disabled it on uninit last time.
|
||||
if (mixer->muted_by_us)
|
||||
force_mute = 1;
|
||||
|
||||
// Set parameters from command line.
|
||||
if (opts->mixer_init_volume >= 0)
|
||||
force_vol_l = force_vol_r = opts->mixer_init_volume;
|
||||
if (opts->mixer_init_mute >= 0)
|
||||
force_mute = opts->mixer_init_mute;
|
||||
|
||||
// Set parameters from playback resume.
|
||||
char *data = mixer->opts->mixer_restore_volume_data;
|
||||
if (!mixer->persistent_volume && data && data[0]) {
|
||||
char drv[40];
|
||||
float v_l, v_r;
|
||||
int m;
|
||||
if (sscanf(data, "%39[^:]:%f:%f:%d", drv, &v_l, &v_r, &m) == 4) {
|
||||
if (strcmp(mixer->driver, drv) == 0) {
|
||||
force_vol_l = v_l;
|
||||
force_vol_r = v_r;
|
||||
force_mute = !!m;
|
||||
MP_DBG(mixer, "Restoring volume from resume config.\n");
|
||||
}
|
||||
}
|
||||
talloc_free(mixer->opts->mixer_restore_volume_data);
|
||||
mixer->opts->mixer_restore_volume_data = NULL;
|
||||
}
|
||||
|
||||
// Using --volume should not reset the volume on every file (i.e. reinit),
|
||||
// OTOH mpv --{ --volume 10 f1.mkv --} --{ --volume 20 f2.mkv --} must work.
|
||||
// Resetting the option volumes to "auto" (-1) is easiest. If file local
|
||||
// options (as shown above) are used, the option handler code will reset
|
||||
// them to other values, and force the volume to be reset as well.
|
||||
opts->mixer_init_volume = -1;
|
||||
opts->mixer_init_mute = -1;
|
||||
|
||||
checkvolume(mixer);
|
||||
if (force_vol_l >= 0 && force_vol_r >= 0) {
|
||||
MP_DBG(mixer, "Restoring previous volume.\n");
|
||||
mixer_setvolume(mixer, force_vol_l, force_vol_r);
|
||||
}
|
||||
if (force_mute >= 0) {
|
||||
MP_DBG(mixer, "Restoring previous mute toggle.\n");
|
||||
mixer_setmute(mixer, force_mute);
|
||||
}
|
||||
}
|
||||
|
||||
// Called after the audio filter chain is built or rebuilt.
|
||||
// (Can be called multiple times, even without mixer_uninit() in-between.)
|
||||
void mixer_reinit_audio(struct mixer *mixer, struct ao *ao, struct af_stream *af)
|
||||
{
|
||||
if (!ao || !af)
|
||||
return;
|
||||
mixer->ao = ao;
|
||||
mixer->af = af;
|
||||
|
||||
MP_DBG(mixer, "Reinit...\n");
|
||||
|
||||
probe_softvol(mixer);
|
||||
restore_volume(mixer);
|
||||
|
||||
if (mixer->balance != 0)
|
||||
mixer_setbalance(mixer, mixer->balance);
|
||||
}
|
||||
|
||||
/* Called before uninitializing the audio filter chain. The main purpose is to
|
||||
* turn off mute, in case it's a global/persistent setting which might
|
||||
* otherwise be left enabled even after this player instance exits.
|
||||
*/
|
||||
void mixer_uninit_audio(struct mixer *mixer)
|
||||
{
|
||||
if (!mixer->ao)
|
||||
return;
|
||||
|
||||
MP_DBG(mixer, "Uninit...\n");
|
||||
|
||||
checkvolume(mixer);
|
||||
if (mixer->muted_by_us && mixer->persistent_volume) {
|
||||
MP_DBG(mixer, "Draining.\n");
|
||||
/* Current audio output API combines playing the remaining buffered
|
||||
* audio and uninitializing the AO into one operation, even though
|
||||
* ideally unmute would happen between those two steps. We can't do
|
||||
* volume changes after uninitialization, but we don't want the
|
||||
* remaining audio to play at full volume either. Thus this
|
||||
* workaround to drop remaining audio first. */
|
||||
ao_reset(mixer->ao);
|
||||
mixer_setmute(mixer, false);
|
||||
/* We remember mute status and re-enable it if we play more audio
|
||||
* in the same process. */
|
||||
mixer->muted_by_us = true;
|
||||
}
|
||||
mixer->ao = NULL;
|
||||
mixer->af = NULL;
|
||||
mixer->softvol = false;
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MPLAYER_MIXER_H
|
||||
#define MPLAYER_MIXER_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
// Values of MPOpts.softvol
|
||||
enum {
|
||||
SOFTVOL_NO = 0,
|
||||
SOFTVOL_YES = 1,
|
||||
SOFTVOL_AUTO = 2,
|
||||
};
|
||||
|
||||
struct mpv_global;
|
||||
struct ao;
|
||||
struct af_stream;
|
||||
struct mixer;
|
||||
|
||||
struct mixer *mixer_init(void *talloc_ctx, struct mpv_global *global);
|
||||
void mixer_reinit_audio(struct mixer *mixer, struct ao *ao, struct af_stream *af);
|
||||
void mixer_uninit_audio(struct mixer *mixer);
|
||||
bool mixer_audio_initialized(struct mixer *mixer);
|
||||
void mixer_getvolume(struct mixer *mixer, float *l, float *r);
|
||||
void mixer_setvolume(struct mixer *mixer, float l, float r);
|
||||
void mixer_addvolume(struct mixer *mixer, float step);
|
||||
void mixer_getbothvolume(struct mixer *mixer, float *b);
|
||||
void mixer_setmute(struct mixer *mixer, bool mute);
|
||||
bool mixer_getmute(struct mixer *mixer);
|
||||
void mixer_getbalance(struct mixer *mixer, float *bal);
|
||||
void mixer_setbalance(struct mixer *mixer, float bal);
|
||||
float mixer_getmaxvolume(struct mixer *mixer);
|
||||
char *mixer_get_volume_restore_data(struct mixer *mixer);
|
||||
|
||||
#endif /* MPLAYER_MIXER_H */
|
||||
+33
-8
@@ -158,7 +158,7 @@ error:
|
||||
|
||||
static struct ao *ao_init(bool probing, struct mpv_global *global,
|
||||
struct input_ctx *input_ctx,
|
||||
struct encode_lavc_context *encode_lavc_ctx,
|
||||
struct encode_lavc_context *encode_lavc_ctx, int flags,
|
||||
int samplerate, int format, struct mp_chmap channels,
|
||||
char *dev, char *name, char **args)
|
||||
{
|
||||
@@ -169,6 +169,7 @@ static struct ao *ao_init(bool probing, struct mpv_global *global,
|
||||
ao->channels = channels;
|
||||
ao->format = format;
|
||||
ao->encode_lavc_ctx = encode_lavc_ctx;
|
||||
ao->init_flags = flags;
|
||||
if (ao->driver->encode != !!ao->encode_lavc_ctx)
|
||||
goto fail;
|
||||
|
||||
@@ -182,6 +183,8 @@ static struct ao *ao_init(bool probing, struct mpv_global *global,
|
||||
ao->api_priv = talloc_zero_size(ao, ao->api->priv_size);
|
||||
assert(!ao->api->priv_defaults && !ao->api->options);
|
||||
|
||||
ao->stream_silence = flags & AO_INIT_STREAM_SILENCE;
|
||||
|
||||
int r = ao->driver->init(ao);
|
||||
if (r < 0) {
|
||||
// Silly exception for coreaudio spdif redirection
|
||||
@@ -190,7 +193,7 @@ static struct ao *ao_init(bool probing, struct mpv_global *global,
|
||||
snprintf(redirect, sizeof(redirect), "%s", ao->redirect);
|
||||
snprintf(rdevice, sizeof(rdevice), "%s", ao->device ? ao->device : "");
|
||||
talloc_free(ao);
|
||||
return ao_init(probing, global, input_ctx, encode_lavc_ctx,
|
||||
return ao_init(probing, global, input_ctx, encode_lavc_ctx, flags,
|
||||
samplerate, format, channels, rdevice, redirect, NULL);
|
||||
}
|
||||
goto fail;
|
||||
@@ -240,7 +243,7 @@ static void split_ao_device(void *tmp, char *opt, char **out_ao, char **out_dev)
|
||||
}
|
||||
|
||||
struct ao *ao_init_best(struct mpv_global *global,
|
||||
bool ao_null_fallback,
|
||||
int init_flags,
|
||||
struct input_ctx *input_ctx,
|
||||
struct encode_lavc_context *encode_lavc_ctx,
|
||||
int samplerate, int format, struct mp_chmap channels)
|
||||
@@ -283,7 +286,7 @@ struct ao *ao_init_best(struct mpv_global *global,
|
||||
}
|
||||
}
|
||||
|
||||
if (ao_null_fallback) {
|
||||
if (init_flags & AO_INIT_NULL_FALLBACK) {
|
||||
MP_TARRAY_APPEND(tmp, ao_list, ao_num,
|
||||
(struct m_obj_settings){.name = "null"});
|
||||
}
|
||||
@@ -297,7 +300,7 @@ struct ao *ao_init_best(struct mpv_global *global,
|
||||
dev = pref_dev;
|
||||
mp_verbose(log, "Using preferred device '%s'\n", dev);
|
||||
}
|
||||
ao = ao_init(probing, global, input_ctx, encode_lavc_ctx,
|
||||
ao = ao_init(probing, global, input_ctx, encode_lavc_ctx, init_flags,
|
||||
samplerate, format, channels, dev,
|
||||
entry->name, entry->attribs);
|
||||
if (ao)
|
||||
@@ -429,6 +432,29 @@ bool ao_chmap_sel_adjust(struct ao *ao, const struct mp_chmap_sel *s,
|
||||
return r;
|
||||
}
|
||||
|
||||
// safe_multichannel=true behaves like ao_chmap_sel_adjust.
|
||||
// safe_multichannel=false is a helper for callers which do not support safe
|
||||
// handling of arbitrary channel layouts. If the multichannel layouts are not
|
||||
// considered "always safe" (e.g. HDMI), then allow only stereo or mono, if
|
||||
// they are part of the list in *s.
|
||||
bool ao_chmap_sel_adjust2(struct ao *ao, const struct mp_chmap_sel *s,
|
||||
struct mp_chmap *map, bool safe_multichannel)
|
||||
{
|
||||
if (!safe_multichannel && (ao->init_flags & AO_INIT_SAFE_MULTICHANNEL_ONLY)) {
|
||||
struct mp_chmap res = *map;
|
||||
if (mp_chmap_sel_adjust(s, &res)) {
|
||||
if (!mp_chmap_equals(&res, &(struct mp_chmap)MP_CHMAP_INIT_MONO) &&
|
||||
!mp_chmap_equals(&res, &(struct mp_chmap)MP_CHMAP_INIT_STEREO))
|
||||
{
|
||||
MP_WARN(ao, "Disabling multichannel output.\n");
|
||||
*map = (struct mp_chmap)MP_CHMAP_INIT_STEREO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ao_chmap_sel_adjust(ao, s, map);
|
||||
}
|
||||
|
||||
bool ao_chmap_sel_get_def(struct ao *ao, const struct mp_chmap_sel *s,
|
||||
struct mp_chmap *map, int num)
|
||||
{
|
||||
@@ -490,10 +516,9 @@ struct ao_hotplug *ao_hotplug_create(struct mpv_global *global,
|
||||
static void get_devices(struct ao *ao, struct ao_device_list *list)
|
||||
{
|
||||
int num = list->num_devices;
|
||||
if (ao->driver->list_devs)
|
||||
if (ao->driver->list_devs) {
|
||||
ao->driver->list_devs(ao, list);
|
||||
// Add at least a default entry
|
||||
if (list->num_devices == num) {
|
||||
} else {
|
||||
char name[80] = "Default";
|
||||
if (num > 1)
|
||||
mp_snprintf_cat(name, sizeof(name), " (%s)", ao->driver->name);
|
||||
|
||||
+11
-1
@@ -50,6 +50,16 @@ enum {
|
||||
AO_EVENT_HOTPLUG = 2,
|
||||
};
|
||||
|
||||
enum {
|
||||
// Allow falling back to ao_null if nothing else works.
|
||||
AO_INIT_NULL_FALLBACK = 1 << 0,
|
||||
// Only accept multichannel configurations that are guaranteed to work
|
||||
// (i.e. not sending arbitrary layouts over HDMI).
|
||||
AO_INIT_SAFE_MULTICHANNEL_ONLY = 1 << 1,
|
||||
// Stream silence as long as no audio is playing.
|
||||
AO_INIT_STREAM_SILENCE = 1 << 2,
|
||||
};
|
||||
|
||||
typedef struct ao_control_vol {
|
||||
float left;
|
||||
float right;
|
||||
@@ -72,7 +82,7 @@ struct encode_lavc_context;
|
||||
struct mp_audio;
|
||||
|
||||
struct ao *ao_init_best(struct mpv_global *global,
|
||||
bool ao_null_fallback,
|
||||
int init_flags,
|
||||
struct input_ctx *input_ctx,
|
||||
struct encode_lavc_context *encode_lavc_ctx,
|
||||
int samplerate, int format, struct mp_chmap channels);
|
||||
|
||||
+61
-16
@@ -53,6 +53,7 @@ struct priv {
|
||||
bool device_lost;
|
||||
snd_pcm_format_t alsa_fmt;
|
||||
bool can_pause;
|
||||
bool paused;
|
||||
snd_pcm_sframes_t prepause_frames;
|
||||
double delay_before_pause;
|
||||
snd_pcm_uframes_t buffersize;
|
||||
@@ -360,7 +361,7 @@ static bool query_chmaps(struct ao *ao, struct mp_chmap *chmap)
|
||||
|
||||
snd_pcm_free_chmaps(maps);
|
||||
|
||||
return ao_chmap_sel_adjust(ao, &chmap_sel, chmap);
|
||||
return ao_chmap_sel_adjust2(ao, &chmap_sel, chmap, false);
|
||||
}
|
||||
|
||||
// Map back our selected channel layout to an ALSA one. This is done this way so
|
||||
@@ -785,6 +786,14 @@ static int init_device(struct ao *ao, int mode)
|
||||
MP_VERBOSE(ao, "buffersize: %d samples\n", (int)p->buffersize);
|
||||
MP_VERBOSE(ao, "period size: %d samples\n", (int)p->outburst);
|
||||
|
||||
ao->device_buffer = p->buffersize;
|
||||
|
||||
// ao_alsa implements this by relying on underrun behavior (no data means
|
||||
// underrun, during which silence is played). Trigger by playing some
|
||||
// initial silence.
|
||||
if (ao->stream_silence)
|
||||
ao_play_silence(ao, p->outburst);
|
||||
|
||||
return 0;
|
||||
|
||||
alsa_error:
|
||||
@@ -874,7 +883,7 @@ static double get_delay(struct ao *ao)
|
||||
struct priv *p = ao->priv;
|
||||
snd_pcm_sframes_t delay;
|
||||
|
||||
if (snd_pcm_state(p->alsa) == SND_PCM_STATE_PAUSED)
|
||||
if (p->paused)
|
||||
return p->delay_before_pause;
|
||||
|
||||
if (snd_pcm_delay(p->alsa, &delay) < 0)
|
||||
@@ -888,27 +897,47 @@ static double get_delay(struct ao *ao)
|
||||
return delay / (double)ao->samplerate;
|
||||
}
|
||||
|
||||
// For stream-silence mode: replace remaining buffer with silence.
|
||||
// Tries to cause an instant buffer underrun.
|
||||
static void soft_reset(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
snd_pcm_sframes_t frames = snd_pcm_rewindable(p->alsa);
|
||||
if (frames > 0 && snd_pcm_state(p->alsa) == SND_PCM_STATE_RUNNING) {
|
||||
frames = snd_pcm_rewind(p->alsa, frames);
|
||||
if (frames < 0) {
|
||||
int err = frames;
|
||||
CHECK_ALSA_WARN("pcm rewind error");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void audio_pause(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
int err;
|
||||
|
||||
if (p->can_pause) {
|
||||
if (p->paused)
|
||||
return;
|
||||
|
||||
p->delay_before_pause = get_delay(ao);
|
||||
p->prepause_frames = p->delay_before_pause * ao->samplerate;
|
||||
|
||||
if (ao->stream_silence) {
|
||||
soft_reset(ao);
|
||||
} else if (p->can_pause) {
|
||||
if (snd_pcm_state(p->alsa) == SND_PCM_STATE_RUNNING) {
|
||||
p->delay_before_pause = get_delay(ao);
|
||||
err = snd_pcm_pause(p->alsa, 1);
|
||||
CHECK_ALSA_ERROR("pcm pause error");
|
||||
p->prepause_frames = 0;
|
||||
}
|
||||
} else {
|
||||
if (snd_pcm_delay(p->alsa, &p->prepause_frames) < 0
|
||||
|| p->prepause_frames < 0)
|
||||
p->prepause_frames = 0;
|
||||
p->delay_before_pause = p->prepause_frames / (double)ao->samplerate;
|
||||
|
||||
err = snd_pcm_drop(p->alsa);
|
||||
CHECK_ALSA_ERROR("pcm drop error");
|
||||
}
|
||||
|
||||
p->paused = true;
|
||||
|
||||
alsa_error: ;
|
||||
}
|
||||
|
||||
@@ -930,9 +959,15 @@ static void audio_resume(struct ao *ao)
|
||||
struct priv *p = ao->priv;
|
||||
int err;
|
||||
|
||||
if (!p->paused)
|
||||
return;
|
||||
|
||||
resume_device(ao);
|
||||
|
||||
if (p->can_pause) {
|
||||
if (ao->stream_silence) {
|
||||
p->paused = false;
|
||||
get_delay(ao); // recovers from underrun (as a side-effect)
|
||||
} else if (p->can_pause) {
|
||||
if (snd_pcm_state(p->alsa) == SND_PCM_STATE_PAUSED) {
|
||||
err = snd_pcm_pause(p->alsa, 0);
|
||||
CHECK_ALSA_ERROR("pcm resume error");
|
||||
@@ -941,11 +976,13 @@ static void audio_resume(struct ao *ao)
|
||||
MP_VERBOSE(ao, "resume not supported by hardware\n");
|
||||
err = snd_pcm_prepare(p->alsa);
|
||||
CHECK_ALSA_ERROR("pcm prepare error");
|
||||
if (p->prepause_frames)
|
||||
ao_play_silence(ao, p->prepause_frames);
|
||||
}
|
||||
|
||||
if (p->prepause_frames)
|
||||
ao_play_silence(ao, p->prepause_frames);
|
||||
|
||||
alsa_error: ;
|
||||
p->paused = false;
|
||||
}
|
||||
|
||||
static void reset(struct ao *ao)
|
||||
@@ -953,12 +990,18 @@ static void reset(struct ao *ao)
|
||||
struct priv *p = ao->priv;
|
||||
int err;
|
||||
|
||||
p->paused = false;
|
||||
p->prepause_frames = 0;
|
||||
p->delay_before_pause = 0;
|
||||
err = snd_pcm_drop(p->alsa);
|
||||
CHECK_ALSA_ERROR("pcm prepare error");
|
||||
err = snd_pcm_prepare(p->alsa);
|
||||
CHECK_ALSA_ERROR("pcm prepare error");
|
||||
|
||||
if (ao->stream_silence) {
|
||||
soft_reset(ao);
|
||||
} else {
|
||||
err = snd_pcm_drop(p->alsa);
|
||||
CHECK_ALSA_ERROR("pcm prepare error");
|
||||
err = snd_pcm_prepare(p->alsa);
|
||||
CHECK_ALSA_ERROR("pcm prepare error");
|
||||
}
|
||||
|
||||
alsa_error: ;
|
||||
}
|
||||
@@ -997,6 +1040,8 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
}
|
||||
} while (res == 0);
|
||||
|
||||
p->paused = false;
|
||||
|
||||
return res < 0 ? -1 : res;
|
||||
|
||||
alsa_error:
|
||||
|
||||
@@ -419,7 +419,7 @@ const struct ao_driver audio_out_coreaudio = {
|
||||
.uninit = uninit,
|
||||
.init = init,
|
||||
.control = control,
|
||||
.pause = stop,
|
||||
.reset = stop,
|
||||
.resume = start,
|
||||
.hotplug_init = hotplug_init,
|
||||
.hotplug_uninit = hotplug_uninit,
|
||||
|
||||
@@ -399,7 +399,7 @@ const struct ao_driver audio_out_coreaudio_exclusive = {
|
||||
.name = "coreaudio_exclusive",
|
||||
.uninit = uninit,
|
||||
.init = init,
|
||||
.pause = audio_pause,
|
||||
.reset = audio_pause,
|
||||
.resume = audio_resume,
|
||||
.list_devs = ca_get_device_list,
|
||||
.priv_size = sizeof(struct priv),
|
||||
|
||||
@@ -70,7 +70,7 @@ OSStatus ca_get_ary(AudioObjectID id, ca_scope scope, ca_sel selector,
|
||||
|
||||
return err;
|
||||
coreaudio_error_free:
|
||||
free(*data);
|
||||
talloc_free(*data);
|
||||
coreaudio_error:
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -114,6 +114,13 @@ OSStatus ca_select_device(struct ao *ao, char* name, AudioDeviceID *device)
|
||||
kAudioObjectSystemObject, &p_addr, 0, 0, &size, &v);
|
||||
CFRelease(uid);
|
||||
CHECK_CA_ERROR("unable to query for device UID");
|
||||
|
||||
uint32_t is_alive = 1;
|
||||
err = CA_GET(*device, kAudioDevicePropertyDeviceIsAlive, &is_alive);
|
||||
CHECK_CA_ERROR("could not check whether device is alive (invalid device?)");
|
||||
|
||||
if (!is_alive)
|
||||
MP_WARN(ao, "device is not alive!\n");
|
||||
} else {
|
||||
// device not set by user, get the default one
|
||||
err = CA_GET(kAudioObjectSystemObject,
|
||||
@@ -140,7 +147,7 @@ bool check_ca_st(struct ao *ao, int level, OSStatus code, const char *message)
|
||||
{
|
||||
if (code == noErr) return true;
|
||||
|
||||
mp_msg(ao->log, level, "%s (%s)\n", message, mp_tag_str(code));
|
||||
mp_msg(ao->log, level, "%s (%s/%d)\n", message, mp_tag_str(code), (int)code);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
+122
-77
@@ -39,8 +39,6 @@
|
||||
#include "common/encode_lavc.h"
|
||||
|
||||
struct priv {
|
||||
uint8_t *buffer;
|
||||
size_t buffer_size;
|
||||
AVStream *stream;
|
||||
AVCodecContext *codec;
|
||||
int pcmhack;
|
||||
@@ -125,7 +123,7 @@ static int init(struct ao *ao)
|
||||
|
||||
struct mp_chmap_sel sel = {0};
|
||||
mp_chmap_sel_add_any(&sel);
|
||||
if (!ao_chmap_sel_adjust(ao, &sel, &ao->channels))
|
||||
if (!ao_chmap_sel_adjust2(ao, &sel, &ao->channels, false))
|
||||
goto fail;
|
||||
mp_chmap_reorder_to_lavc(&ao->channels);
|
||||
ac->codec->channels = ao->channels.num;
|
||||
@@ -146,18 +144,10 @@ static int init(struct ao *ao)
|
||||
if (ac->codec->frame_size <= 1)
|
||||
ac->pcmhack = av_get_bits_per_sample(ac->codec->codec_id) / 8;
|
||||
|
||||
if (ac->pcmhack) {
|
||||
if (ac->pcmhack)
|
||||
ac->aframesize = 16384; // "enough"
|
||||
ac->buffer_size =
|
||||
ac->aframesize * ac->pcmhack * ao->channels.num * 2 + 200;
|
||||
} else {
|
||||
else
|
||||
ac->aframesize = ac->codec->frame_size;
|
||||
ac->buffer_size =
|
||||
ac->aframesize * ac->sample_size * ao->channels.num * 2 + 200;
|
||||
}
|
||||
if (ac->buffer_size < FF_MIN_BUFFER_SIZE)
|
||||
ac->buffer_size = FF_MIN_BUFFER_SIZE;
|
||||
ac->buffer = talloc_size(ac, ac->buffer_size);
|
||||
|
||||
// enough frames for at least 0.25 seconds
|
||||
ac->framecount = ceil(ao->samplerate * 0.25 / ac->aframesize);
|
||||
@@ -182,7 +172,7 @@ fail:
|
||||
}
|
||||
|
||||
// close audio device
|
||||
static int encode(struct ao *ao, double apts, void **data);
|
||||
static void encode(struct ao *ao, double apts, void **data);
|
||||
static void uninit(struct ao *ao)
|
||||
{
|
||||
struct priv *ac = ao->priv;
|
||||
@@ -199,12 +189,12 @@ static void uninit(struct ao *ao)
|
||||
return;
|
||||
}
|
||||
|
||||
if (ac->buffer) {
|
||||
if (ac->stream) {
|
||||
double outpts = ac->expected_next_pts;
|
||||
if (!ectx->options->rawts && ectx->options->copyts)
|
||||
outpts += ectx->discontinuity_pts_offset;
|
||||
outpts += encode_lavc_getoffset(ectx, ac->codec);
|
||||
while (encode(ao, outpts, NULL) > 0) ;
|
||||
encode(ao, outpts, NULL);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&ectx->lock);
|
||||
@@ -220,24 +210,130 @@ static int get_space(struct ao *ao)
|
||||
return ac->aframesize * ac->framecount;
|
||||
}
|
||||
|
||||
static void write_packet(struct ao *ao, AVPacket *packet)
|
||||
{
|
||||
// TODO: Can we unify this with the equivalent video code path?
|
||||
struct priv *ac = ao->priv;
|
||||
|
||||
packet->stream_index = ac->stream->index;
|
||||
if (packet->pts != AV_NOPTS_VALUE) {
|
||||
packet->pts = av_rescale_q(packet->pts,
|
||||
ac->codec->time_base,
|
||||
ac->stream->time_base);
|
||||
} else {
|
||||
// Do we need this at all? Better be safe than sorry...
|
||||
MP_WARN(ao, "encoder lost pts, why?\n");
|
||||
if (ac->savepts != MP_NOPTS_VALUE) {
|
||||
packet->pts = av_rescale_q(ac->savepts,
|
||||
ac->codec->time_base,
|
||||
ac->stream->time_base);
|
||||
}
|
||||
}
|
||||
if (packet->dts != AV_NOPTS_VALUE) {
|
||||
packet->dts = av_rescale_q(packet->dts,
|
||||
ac->codec->time_base,
|
||||
ac->stream->time_base);
|
||||
}
|
||||
if (packet->duration > 0) {
|
||||
packet->duration = av_rescale_q(packet->duration,
|
||||
ac->codec->time_base,
|
||||
ac->stream->time_base);
|
||||
}
|
||||
|
||||
ac->savepts = AV_NOPTS_VALUE;
|
||||
|
||||
if (encode_lavc_write_frame(ao->encode_lavc_ctx,
|
||||
ac->stream, packet) < 0) {
|
||||
MP_ERR(ao, "error writing at %d %d/%d\n",
|
||||
(int) packet->pts,
|
||||
ac->stream->time_base.num,
|
||||
ac->stream->time_base.den);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void encode_audio_and_write(struct ao *ao, AVFrame *frame)
|
||||
{
|
||||
// TODO: Can we unify this with the equivalent video code path?
|
||||
struct priv *ac = ao->priv;
|
||||
AVPacket packet = {0};
|
||||
|
||||
#if HAVE_AVCODEC_NEW_CODEC_API
|
||||
int status = avcodec_send_frame(ac->codec, frame);
|
||||
if (status < 0) {
|
||||
MP_ERR(ao, "error encoding at %d %d/%d\n",
|
||||
frame ? (int) frame->pts : -1,
|
||||
ac->codec->time_base.num,
|
||||
ac->codec->time_base.den);
|
||||
return;
|
||||
}
|
||||
for (;;) {
|
||||
av_init_packet(&packet);
|
||||
status = avcodec_receive_packet(ac->codec, &packet);
|
||||
if (status == AVERROR(EAGAIN)) { // No more packets for now.
|
||||
if (frame == NULL) {
|
||||
MP_ERR(ao, "sent flush frame, got EAGAIN");
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (status == AVERROR_EOF) { // No more packets, ever.
|
||||
if (frame != NULL) {
|
||||
MP_ERR(ao, "sent audio frame, got EOF");
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (status < 0) {
|
||||
MP_ERR(ao, "error encoding at %d %d/%d\n",
|
||||
frame ? (int) frame->pts : -1,
|
||||
ac->codec->time_base.num,
|
||||
ac->codec->time_base.den);
|
||||
break;
|
||||
}
|
||||
if (frame) {
|
||||
if (ac->savepts == AV_NOPTS_VALUE)
|
||||
ac->savepts = frame->pts;
|
||||
}
|
||||
encode_lavc_write_stats(ao->encode_lavc_ctx, ac->codec);
|
||||
write_packet(ao, &packet);
|
||||
av_packet_unref(&packet);
|
||||
}
|
||||
#else
|
||||
av_init_packet(&packet);
|
||||
int got_packet = 0;
|
||||
int status = avcodec_encode_audio2(ac->codec, &packet, frame, &got_packet);
|
||||
if (status < 0) {
|
||||
MP_ERR(ao, "error encoding at %d %d/%d\n",
|
||||
frame ? (int) frame->pts : -1,
|
||||
ac->codec->time_base.num,
|
||||
ac->codec->time_base.den);
|
||||
return;
|
||||
}
|
||||
if (!got_packet) {
|
||||
return;
|
||||
}
|
||||
if (frame) {
|
||||
if (ac->savepts == AV_NOPTS_VALUE)
|
||||
ac->savepts = frame->pts;
|
||||
}
|
||||
encode_lavc_write_stats(ao->encode_lavc_ctx, ac->codec);
|
||||
write_packet(ao, &packet);
|
||||
av_packet_unref(&packet);
|
||||
#endif
|
||||
}
|
||||
|
||||
// must get exactly ac->aframesize amount of data
|
||||
static int encode(struct ao *ao, double apts, void **data)
|
||||
static void encode(struct ao *ao, double apts, void **data)
|
||||
{
|
||||
AVPacket packet;
|
||||
struct priv *ac = ao->priv;
|
||||
struct encode_lavc_context *ectx = ao->encode_lavc_ctx;
|
||||
double realapts = ac->aframecount * (double) ac->aframesize /
|
||||
ao->samplerate;
|
||||
int status, gotpacket;
|
||||
|
||||
ac->aframecount++;
|
||||
|
||||
if (data)
|
||||
ectx->audio_pts_offset = realapts - apts;
|
||||
|
||||
av_init_packet(&packet);
|
||||
packet.data = ac->buffer;
|
||||
packet.size = ac->buffer_size;
|
||||
if(data) {
|
||||
AVFrame *frame = av_frame_alloc();
|
||||
frame->format = af_to_avformat(ao->format);
|
||||
@@ -270,64 +366,11 @@ static int encode(struct ao *ao, double apts, void **data)
|
||||
ac->lastpts = frame_pts;
|
||||
|
||||
frame->quality = ac->codec->global_quality;
|
||||
status = avcodec_encode_audio2(ac->codec, &packet, frame, &gotpacket);
|
||||
|
||||
if (!status) {
|
||||
if (ac->savepts == AV_NOPTS_VALUE)
|
||||
ac->savepts = frame->pts;
|
||||
}
|
||||
|
||||
encode_audio_and_write(ao, frame);
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
else
|
||||
{
|
||||
status = avcodec_encode_audio2(ac->codec, &packet, NULL, &gotpacket);
|
||||
}
|
||||
|
||||
if(status) {
|
||||
MP_ERR(ao, "error encoding\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(!gotpacket)
|
||||
return 0;
|
||||
|
||||
MP_DBG(ao, "got pts %f (playback time: %f); out size: %d\n",
|
||||
apts, realapts, packet.size);
|
||||
|
||||
encode_lavc_write_stats(ao->encode_lavc_ctx, ac->codec);
|
||||
|
||||
packet.stream_index = ac->stream->index;
|
||||
|
||||
// Do we need this at all? Better be safe than sorry...
|
||||
if (packet.pts == AV_NOPTS_VALUE) {
|
||||
MP_WARN(ao, "encoder lost pts, why?\n");
|
||||
if (ac->savepts != MP_NOPTS_VALUE)
|
||||
packet.pts = ac->savepts;
|
||||
}
|
||||
|
||||
if (packet.pts != AV_NOPTS_VALUE)
|
||||
packet.pts = av_rescale_q(packet.pts, ac->codec->time_base,
|
||||
ac->stream->time_base);
|
||||
|
||||
if (packet.dts != AV_NOPTS_VALUE)
|
||||
packet.dts = av_rescale_q(packet.dts, ac->codec->time_base,
|
||||
ac->stream->time_base);
|
||||
|
||||
if(packet.duration > 0)
|
||||
packet.duration = av_rescale_q(packet.duration, ac->codec->time_base,
|
||||
ac->stream->time_base);
|
||||
|
||||
ac->savepts = AV_NOPTS_VALUE;
|
||||
|
||||
if (encode_lavc_write_frame(ao->encode_lavc_ctx, ac->stream, &packet) < 0) {
|
||||
MP_ERR(ao, "error writing at %f %f/%f\n",
|
||||
realapts, (double) ac->stream->time_base.num,
|
||||
(double) ac->stream->time_base.den);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return packet.size;
|
||||
encode_audio_and_write(ao, NULL);
|
||||
}
|
||||
|
||||
// this should round samples down to frame sizes
|
||||
@@ -492,3 +535,5 @@ const struct ao_driver audio_out_lavc = {
|
||||
.play = play,
|
||||
.drain = drain,
|
||||
};
|
||||
|
||||
// vim: sw=4 ts=4 et tw=80
|
||||
+5
-11
@@ -57,7 +57,7 @@ struct priv {
|
||||
// (This is not needed by the AO API, but many AOs behave this way.)
|
||||
int outburst; // samples
|
||||
|
||||
char **channel_layouts;
|
||||
struct m_channels channel_layouts;
|
||||
};
|
||||
|
||||
static void drain(struct ao *ao)
|
||||
@@ -91,15 +91,9 @@ static int init(struct ao *ao)
|
||||
ao->untimed = priv->untimed;
|
||||
|
||||
struct mp_chmap_sel sel = {.tmp = ao};
|
||||
if (priv->channel_layouts) {
|
||||
for (int n = 0; priv->channel_layouts[n]; n++) {
|
||||
struct mp_chmap map = {0};
|
||||
if (!mp_chmap_from_str(&map, bstr0(priv->channel_layouts[n]))) {
|
||||
MP_FATAL(ao, "Invalid channel map in option.\n");
|
||||
return -1;
|
||||
}
|
||||
mp_chmap_sel_add_map(&sel, &map);
|
||||
}
|
||||
if (priv->channel_layouts.num_chmaps) {
|
||||
for (int n = 0; n < priv->channel_layouts.num_chmaps; n++)
|
||||
mp_chmap_sel_add_map(&sel, &priv->channel_layouts.chmaps[n]);
|
||||
} else {
|
||||
mp_chmap_sel_add_any(&sel);
|
||||
}
|
||||
@@ -244,7 +238,7 @@ const struct ao_driver audio_out_null = {
|
||||
OPT_FLOATRANGE("latency", latency_sec, 0, 0, 100),
|
||||
OPT_FLAG("broken-eof", broken_eof, 0),
|
||||
OPT_FLAG("broken-delay", broken_delay, 0),
|
||||
OPT_STRINGLIST("channel-layouts", channel_layouts, 0),
|
||||
OPT_CHANNELS("channel-layouts", channel_layouts, 0),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
@@ -612,6 +612,12 @@ static int audio_wait(struct ao *ao, pthread_mutex_t *lock)
|
||||
return r;
|
||||
}
|
||||
|
||||
static void list_devs(struct ao *ao, struct ao_device_list *list)
|
||||
{
|
||||
if (stat(PATH_DEV_DSP, &(struct stat){0}) == 0)
|
||||
ao_device_list_add(list, ao, &(struct ao_device_desc){"", "Default"});
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
const struct ao_driver audio_out_oss = {
|
||||
@@ -629,6 +635,7 @@ const struct ao_driver audio_out_oss = {
|
||||
.drain = drain,
|
||||
.wait = audio_wait,
|
||||
.wakeup = ao_wakeup_poll,
|
||||
.list_devs = list_devs,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.priv_defaults = &(const struct priv) {
|
||||
.audio_fd = -1,
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <math.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include <pulse/pulseaudio.h>
|
||||
@@ -34,8 +35,8 @@
|
||||
#include "ao.h"
|
||||
#include "internal.h"
|
||||
|
||||
#define VOL_PA2MP(v) ((v) * 100 / PA_VOLUME_NORM)
|
||||
#define VOL_MP2PA(v) ((v) * PA_VOLUME_NORM / 100)
|
||||
#define VOL_PA2MP(v) ((v) * 100.0 / PA_VOLUME_NORM)
|
||||
#define VOL_MP2PA(v) lrint((v) * PA_VOLUME_NORM / 100)
|
||||
|
||||
struct priv {
|
||||
// PulseAudio playback stream object
|
||||
|
||||
@@ -132,7 +132,7 @@ static bool thread_feed(struct ao *ao)
|
||||
mp_time_us() + (int64_t)llrint(delay_us));
|
||||
|
||||
// note, we can't use ao_read_data return value here since we already
|
||||
// commited to frame_count above in the GetBuffer call
|
||||
// committed to frame_count above in the GetBuffer call
|
||||
hr = IAudioRenderClient_ReleaseBuffer(state->pRenderClient,
|
||||
frame_count, 0);
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
@@ -95,7 +95,7 @@ static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnDeviceAdded(
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// maybe MPV can go over to the prefered device once it is plugged in?
|
||||
// maybe MPV can go over to the preferred device once it is plugged in?
|
||||
static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnDeviceRemoved(
|
||||
IMMNotificationClient *This,
|
||||
LPCWSTR pwstrDeviceId)
|
||||
|
||||
@@ -407,7 +407,7 @@ static bool search_channels(struct ao *ao, WAVEFORMATEXTENSIBLE *wformat)
|
||||
}
|
||||
|
||||
entry = ao->channels;
|
||||
if (ao_chmap_sel_adjust(ao, &chmap_sel, &entry)){
|
||||
if (ao_chmap_sel_adjust2(ao, &chmap_sel, &entry, !state->opt_exclusive)){
|
||||
change_waveformat_channels(wformat, &entry);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -43,6 +43,8 @@ struct ao {
|
||||
struct encode_lavc_context *encode_lavc_ctx;
|
||||
struct input_ctx *input_ctx;
|
||||
struct mp_log *log; // Using e.g. "[ao/coreaudio]" as prefix
|
||||
int init_flags; // AO_INIT_* flags
|
||||
bool stream_silence; // if audio inactive, just play silence
|
||||
|
||||
// The device as selected by the user, usually using ao_device_desc.name
|
||||
// from an entry from the list returned by driver->list_devices. If the
|
||||
@@ -191,6 +193,8 @@ void ao_wakeup_poll(struct ao *ao);
|
||||
|
||||
bool ao_chmap_sel_adjust(struct ao *ao, const struct mp_chmap_sel *s,
|
||||
struct mp_chmap *map);
|
||||
bool ao_chmap_sel_adjust2(struct ao *ao, const struct mp_chmap_sel *s,
|
||||
struct mp_chmap *map, bool safe_multichannel);
|
||||
bool ao_chmap_sel_get_def(struct ao *ao, const struct mp_chmap_sel *s,
|
||||
struct mp_chmap *map, int num);
|
||||
|
||||
|
||||
+6
-2
@@ -185,7 +185,7 @@ static double get_delay(struct ao *ao)
|
||||
static void reset(struct ao *ao)
|
||||
{
|
||||
struct ao_pull_state *p = ao->api_priv;
|
||||
if (ao->driver->reset)
|
||||
if (!ao->stream_silence && ao->driver->reset)
|
||||
ao->driver->reset(ao); // assumes the audio callback thread is stopped
|
||||
set_state(ao, AO_STATE_NONE);
|
||||
for (int n = 0; n < ao->num_planes; n++)
|
||||
@@ -195,7 +195,7 @@ static void reset(struct ao *ao)
|
||||
|
||||
static void pause(struct ao *ao)
|
||||
{
|
||||
if (ao->driver->reset)
|
||||
if (!ao->stream_silence && ao->driver->reset)
|
||||
ao->driver->reset(ao);
|
||||
set_state(ao, AO_STATE_NONE);
|
||||
}
|
||||
@@ -244,6 +244,10 @@ static int init(struct ao *ao)
|
||||
p->buffers[n] = mp_ring_new(ao, ao->buffer * ao->sstride);
|
||||
atomic_store(&p->state, AO_STATE_NONE);
|
||||
assert(ao->driver->resume);
|
||||
|
||||
if (ao->stream_silence)
|
||||
ao->driver->resume(ao);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
+2
-4
@@ -497,10 +497,8 @@ int ao_wait_poll(struct ao *ao, struct pollfd *fds, int num_fds,
|
||||
bool wakeup = false;
|
||||
if (p_fds[num_fds].revents & POLLIN) {
|
||||
wakeup = true;
|
||||
// flush the wakeup pipe contents - might "drown" some wakeups, but
|
||||
// that's ok for our use-case
|
||||
char buf[100];
|
||||
(void)read(p->wakeup_pipe[0], buf, sizeof(buf));
|
||||
// might "drown" some wakeups, but that's ok for our use-case
|
||||
mp_flush_wakeup_pipe(p->wakeup_pipe[0]);
|
||||
}
|
||||
return (r >= 0 || r == -EINTR) ? wakeup : -1;
|
||||
}
|
||||
|
||||
+2
-9
@@ -189,7 +189,7 @@ struct lib {
|
||||
unsigned runv;
|
||||
};
|
||||
|
||||
void print_libav_versions(struct mp_log *log, int v)
|
||||
bool print_libav_versions(struct mp_log *log, int v)
|
||||
{
|
||||
const struct lib libs[] = {
|
||||
{"libavutil", LIBAVUTIL_VERSION_INT, avutil_version()},
|
||||
@@ -222,14 +222,7 @@ void print_libav_versions(struct mp_log *log, int v)
|
||||
mp_msg(log, v, "%s version: %s\n", LIB_PREFIX, av_version_info());
|
||||
#endif
|
||||
|
||||
if (mismatch) {
|
||||
// Using mismatched libraries can be legitimate, but even then it's
|
||||
// a bad idea. We don't acknowledge its usefulness and stability.
|
||||
mp_warn(log, "Warning: mpv was compiled against a different version of "
|
||||
"%s than the shared\nlibrary it is linked against. This is "
|
||||
"most likely a broken build\nand misbehavior and crashes are "
|
||||
"to be expected.\n", LIB_PREFIX);
|
||||
}
|
||||
return !mismatch;
|
||||
}
|
||||
|
||||
#undef V
|
||||
+4
-1
@@ -1,8 +1,11 @@
|
||||
#ifndef MP_AV_LOG_H
|
||||
#define MP_AV_LOG_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
struct mpv_global;
|
||||
struct mp_log;
|
||||
void init_libav(struct mpv_global *global);
|
||||
void uninit_libav(struct mpv_global *global);
|
||||
void print_libav_versions(struct mp_log *log, int v);
|
||||
bool print_libav_versions(struct mp_log *log, int v);
|
||||
#endif
|
||||
+223
-117
@@ -119,16 +119,20 @@ struct demux_internal {
|
||||
int max_packs;
|
||||
int max_bytes;
|
||||
|
||||
// Set if we know that we are at the start of the file. This is used to
|
||||
// avoid a redundant initial seek after enabling streams. We could just
|
||||
// allow it, but to avoid buggy seeking affecting normal playback, we don't.
|
||||
bool initial_state;
|
||||
|
||||
bool tracks_switched; // thread needs to inform demuxer of this
|
||||
|
||||
bool seeking; // there's a seek queued
|
||||
int seek_flags; // flags for next seek (if seeking==true)
|
||||
double seek_pts;
|
||||
|
||||
bool refresh_seeks_enabled;
|
||||
bool start_refresh_seek;
|
||||
double ref_pts; // assumed player position (only for track switches)
|
||||
|
||||
double ts_offset; // timestamp offset to apply everything
|
||||
double ts_offset; // timestamp offset to apply to everything
|
||||
|
||||
void (*run_fn)(void *); // if non-NULL, function queued to be run on
|
||||
void *run_fn_arg; // the thread as run_fn(run_fn_arg)
|
||||
@@ -152,7 +156,10 @@ struct demux_stream {
|
||||
// if false, this stream is disabled, or passively
|
||||
// read (like subtitles)
|
||||
bool eof; // end of demuxed stream? (true if all buffer empty)
|
||||
bool need_refresh; // enabled mid-stream
|
||||
bool refreshing;
|
||||
bool correct_dts; // packet DTS is strictly monotonically increasing
|
||||
bool correct_pos; // packet pos is strictly monotonically increasing
|
||||
size_t packs; // number of packets in buffer
|
||||
size_t bytes; // total bytes of packets in buffer
|
||||
double base_ts; // timestamp of the last packet returned to decoder
|
||||
@@ -161,12 +168,12 @@ struct demux_stream {
|
||||
size_t last_br_bytes; // summed packet sizes since last bitrate calculation
|
||||
double bitrate;
|
||||
int64_t last_pos;
|
||||
double last_dts;
|
||||
struct demux_packet *head;
|
||||
struct demux_packet *tail;
|
||||
|
||||
// for closed captions (demuxer_feed_caption)
|
||||
struct sh_stream *cc;
|
||||
|
||||
};
|
||||
|
||||
// Return "a", or if that is NOPTS, return "def".
|
||||
@@ -199,7 +206,10 @@ static void ds_flush(struct demux_stream *ds)
|
||||
ds->eof = false;
|
||||
ds->active = false;
|
||||
ds->refreshing = false;
|
||||
ds->need_refresh = false;
|
||||
ds->last_pos = -1;
|
||||
ds->last_dts = MP_NOPTS_VALUE;
|
||||
ds->correct_dts = ds->correct_pos = true;
|
||||
}
|
||||
|
||||
void demux_set_ts_offset(struct demuxer *demuxer, double offset)
|
||||
@@ -222,6 +232,7 @@ struct sh_stream *demux_alloc_sh_stream(enum stream_type type)
|
||||
.ff_index = -1, // may be overwritten by demuxer
|
||||
.demuxer_id = -1, // ... same
|
||||
.codec = talloc_zero(sh, struct mp_codec_params),
|
||||
.tags = talloc_zero(sh, struct mp_tags),
|
||||
};
|
||||
sh->codec->type = type;
|
||||
return sh;
|
||||
@@ -266,6 +277,33 @@ void demux_add_sh_stream(struct demuxer *demuxer, struct sh_stream *sh)
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
}
|
||||
|
||||
// Update sh->tags (lazily). This must be called by demuxers which update
|
||||
// stream tags after init. (sh->tags can be accessed by the playback thread,
|
||||
// which means the demuxer thread cannot write or read it directly.)
|
||||
// Before init is finished, sh->tags can still be accessed freely.
|
||||
// Ownership of tags goes to the function.
|
||||
void demux_set_stream_tags(struct demuxer *demuxer, struct sh_stream *sh,
|
||||
struct mp_tags *tags)
|
||||
{
|
||||
struct demux_internal *in = demuxer->in;
|
||||
assert(demuxer == in->d_thread);
|
||||
|
||||
if (sh->ds) {
|
||||
while (demuxer->num_update_stream_tags <= sh->index) {
|
||||
MP_TARRAY_APPEND(demuxer, demuxer->update_stream_tags,
|
||||
demuxer->num_update_stream_tags, NULL);
|
||||
}
|
||||
talloc_free(demuxer->update_stream_tags[sh->index]);
|
||||
demuxer->update_stream_tags[sh->index] = talloc_steal(demuxer, tags);
|
||||
|
||||
demux_changed(demuxer, DEMUX_EVENT_METADATA);
|
||||
} else {
|
||||
// not added yet
|
||||
talloc_free(sh->tags);
|
||||
sh->tags = talloc_steal(sh, tags);
|
||||
}
|
||||
}
|
||||
|
||||
// Return a stream with the given index. Since streams can only be added during
|
||||
// the lifetime of the demuxer, it is guaranteed that an index within the valid
|
||||
// range [0, demux_get_num_stream()) always returns a valid sh_stream pointer,
|
||||
@@ -389,6 +427,59 @@ void demuxer_feed_caption(struct sh_stream *stream, demux_packet_t *dp)
|
||||
demux_add_packet(sh, dp);
|
||||
}
|
||||
|
||||
// An obscure mechanism to get stream switching to be executed faster.
|
||||
// On a switch, it seeks back, and then grabs all packets that were
|
||||
// "missing" from the packet queue of the newly selected stream.
|
||||
// Returns MP_NOPTS_VALUE if no seek should happen.
|
||||
static double get_refresh_seek_pts(struct demux_internal *in)
|
||||
{
|
||||
struct demuxer *demux = in->d_thread;
|
||||
|
||||
double start_ts = in->ref_pts;
|
||||
bool needed = false;
|
||||
bool normal_seek = true;
|
||||
bool refresh_possible = true;
|
||||
for (int n = 0; n < in->num_streams; n++) {
|
||||
struct demux_stream *ds = in->streams[n]->ds;
|
||||
|
||||
if (!ds->selected)
|
||||
continue;
|
||||
|
||||
if (ds->type == STREAM_VIDEO || ds->type == STREAM_AUDIO)
|
||||
start_ts = MP_PTS_MIN(start_ts, ds->base_ts);
|
||||
|
||||
needed |= ds->need_refresh;
|
||||
// If there were no other streams selected, we can use a normal seek.
|
||||
normal_seek &= ds->need_refresh;
|
||||
ds->need_refresh = false;
|
||||
|
||||
refresh_possible &= ds->correct_dts || ds->correct_pos;
|
||||
}
|
||||
|
||||
if (!needed || start_ts == MP_NOPTS_VALUE || !demux->desc->seek ||
|
||||
!demux->seekable || demux->partially_seekable)
|
||||
return MP_NOPTS_VALUE;
|
||||
|
||||
if (normal_seek)
|
||||
return start_ts;
|
||||
|
||||
if (!refresh_possible) {
|
||||
MP_VERBOSE(in, "can't issue refresh seek\n");
|
||||
return MP_NOPTS_VALUE;
|
||||
}
|
||||
|
||||
for (int n = 0; n < in->num_streams; n++) {
|
||||
struct demux_stream *ds = in->streams[n]->ds;
|
||||
// Streams which didn't have any packets yet will return all packets,
|
||||
// other streams return packets only starting from the last position.
|
||||
if (ds->last_pos != -1 || ds->last_dts != MP_NOPTS_VALUE)
|
||||
ds->refreshing = true;
|
||||
}
|
||||
|
||||
// Seek back to player's current position, with a small offset added.
|
||||
return start_ts - 1.0;
|
||||
}
|
||||
|
||||
void demux_add_packet(struct sh_stream *stream, demux_packet_t *dp)
|
||||
{
|
||||
struct demux_stream *ds = stream ? stream->ds : NULL;
|
||||
@@ -399,25 +490,34 @@ void demux_add_packet(struct sh_stream *stream, demux_packet_t *dp)
|
||||
struct demux_internal *in = ds->in;
|
||||
pthread_mutex_lock(&in->lock);
|
||||
|
||||
bool drop = false;
|
||||
bool drop = ds->refreshing;
|
||||
if (ds->refreshing) {
|
||||
// Resume reading once the old position was reached (i.e. we start
|
||||
// returning packets where we left off before the refresh).
|
||||
drop = dp->pos <= ds->last_pos;
|
||||
if (dp->pos >= ds->last_pos)
|
||||
ds->refreshing = false;
|
||||
// If it's the same position, drop, but continue normally next time.
|
||||
if (ds->correct_dts) {
|
||||
ds->refreshing = dp->dts < ds->last_dts;
|
||||
} else if (ds->correct_pos) {
|
||||
ds->refreshing = dp->pos < ds->last_pos;
|
||||
} else {
|
||||
ds->refreshing = false; // should not happen
|
||||
}
|
||||
}
|
||||
|
||||
if (!ds->selected || in->seeking || drop) {
|
||||
if (!ds->selected || ds->need_refresh || in->seeking || drop) {
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
talloc_free(dp);
|
||||
return;
|
||||
}
|
||||
|
||||
ds->correct_pos &= dp->pos >= 0 && dp->pos > ds->last_pos;
|
||||
ds->correct_dts &= dp->dts != MP_NOPTS_VALUE && dp->dts > ds->last_dts;
|
||||
ds->last_pos = dp->pos;
|
||||
ds->last_dts = dp->dts;
|
||||
|
||||
dp->stream = stream->index;
|
||||
dp->next = NULL;
|
||||
|
||||
ds->last_pos = dp->pos;
|
||||
ds->packs++;
|
||||
ds->bytes += dp->len;
|
||||
if (ds->tail) {
|
||||
@@ -505,27 +605,40 @@ static bool read_packet(struct demux_internal *in)
|
||||
if (!read_more)
|
||||
return false;
|
||||
|
||||
double seek_pts = get_refresh_seek_pts(in);
|
||||
|
||||
// Actually read a packet. Drop the lock while doing so, because waiting
|
||||
// for disk or network I/O can take time.
|
||||
in->idle = false;
|
||||
in->initial_state = false;
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
|
||||
struct demuxer *demux = in->d_thread;
|
||||
|
||||
if (seek_pts != MP_NOPTS_VALUE) {
|
||||
MP_VERBOSE(in, "refresh seek to %f\n", seek_pts);
|
||||
demux->desc->seek(demux, seek_pts, SEEK_BACKWARD | SEEK_HR);
|
||||
}
|
||||
|
||||
bool eof = !demux->desc->fill_buffer || demux->desc->fill_buffer(demux) <= 0;
|
||||
update_cache(in);
|
||||
|
||||
pthread_mutex_lock(&in->lock);
|
||||
|
||||
if (eof) {
|
||||
for (int n = 0; n < in->num_streams; n++)
|
||||
in->streams[n]->ds->eof = true;
|
||||
// If we had EOF previously, then don't wakeup (avoids wakeup loop)
|
||||
if (!in->last_eof) {
|
||||
if (in->wakeup_cb)
|
||||
in->wakeup_cb(in->wakeup_cb_ctx);
|
||||
pthread_cond_signal(&in->wakeup);
|
||||
MP_VERBOSE(in, "EOF reached.\n");
|
||||
if (!in->seeking) {
|
||||
if (eof) {
|
||||
for (int n = 0; n < in->num_streams; n++)
|
||||
in->streams[n]->ds->eof = true;
|
||||
// If we had EOF previously, then don't wakeup (avoids wakeup loop)
|
||||
if (!in->last_eof) {
|
||||
if (in->wakeup_cb)
|
||||
in->wakeup_cb(in->wakeup_cb_ctx);
|
||||
pthread_cond_signal(&in->wakeup);
|
||||
MP_VERBOSE(in, "EOF reached.\n");
|
||||
}
|
||||
}
|
||||
in->eof = in->last_eof = eof;
|
||||
}
|
||||
in->eof = in->last_eof = eof;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -550,42 +663,6 @@ static void ds_get_packets(struct demux_stream *ds)
|
||||
}
|
||||
}
|
||||
|
||||
// An obscure mechanism to get stream switching to be executed faster.
|
||||
// On a switch, it seeks back, and then grabs all packets that were
|
||||
// "missing" from the packet queue of the newly selected stream.
|
||||
static void start_refreshing(struct demux_internal *in)
|
||||
{
|
||||
struct demuxer *demux = in->d_thread;
|
||||
|
||||
in->start_refresh_seek = false;
|
||||
|
||||
double start_ts = MP_NOPTS_VALUE;
|
||||
for (int n = 0; n < in->num_streams; n++) {
|
||||
struct demux_stream *ds = in->streams[n]->ds;
|
||||
if (ds->type == STREAM_VIDEO || ds->type == STREAM_AUDIO)
|
||||
start_ts = MP_PTS_MIN(start_ts, ds->base_ts);
|
||||
}
|
||||
|
||||
if (start_ts == MP_NOPTS_VALUE || !demux->desc->seek || !demux->seekable ||
|
||||
demux->partially_seekable || !demux->allow_refresh_seeks)
|
||||
return;
|
||||
|
||||
for (int n = 0; n < in->num_streams; n++) {
|
||||
struct demux_stream *ds = in->streams[n]->ds;
|
||||
// Streams which didn't read any packets yet can return all packets,
|
||||
// or they'd be stuck forever; affects newly selected streams too.
|
||||
if (ds->last_pos != -1)
|
||||
ds->refreshing = true;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
|
||||
// Seek back to player's current position, with a small offset added.
|
||||
in->d_thread->desc->seek(in->d_thread, start_ts - 1.0, SEEK_BACKWARD | SEEK_HR);
|
||||
|
||||
pthread_mutex_lock(&in->lock);
|
||||
}
|
||||
|
||||
static void execute_trackswitch(struct demux_internal *in)
|
||||
{
|
||||
in->tracks_switched = false;
|
||||
@@ -603,9 +680,6 @@ static void execute_trackswitch(struct demux_internal *in)
|
||||
&(int){any_selected});
|
||||
|
||||
pthread_mutex_lock(&in->lock);
|
||||
|
||||
if (in->start_refresh_seek)
|
||||
start_refreshing(in);
|
||||
}
|
||||
|
||||
static void execute_seek(struct demux_internal *in)
|
||||
@@ -613,6 +687,7 @@ static void execute_seek(struct demux_internal *in)
|
||||
int flags = in->seek_flags;
|
||||
double pts = in->seek_pts;
|
||||
in->seeking = false;
|
||||
in->initial_state = false;
|
||||
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
|
||||
@@ -853,17 +928,18 @@ static int decode_float(char *str, float *out)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int decode_gain(demuxer_t *demuxer, const char *tag, float *out)
|
||||
static int decode_gain(struct mp_log *log, struct mp_tags *tags,
|
||||
const char *tag, float *out)
|
||||
{
|
||||
char *tag_val = NULL;
|
||||
float dec_val;
|
||||
|
||||
tag_val = mp_tags_get_str(demuxer->metadata, tag);
|
||||
tag_val = mp_tags_get_str(tags, tag);
|
||||
if (!tag_val)
|
||||
return -1;
|
||||
|
||||
if (decode_float(tag_val, &dec_val)) {
|
||||
mp_msg(demuxer->log, MSGL_ERR, "Invalid replaygain value\n");
|
||||
if (decode_float(tag_val, &dec_val) < 0) {
|
||||
mp_msg(log, MSGL_ERR, "Invalid replaygain value\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -871,62 +947,68 @@ static int decode_gain(demuxer_t *demuxer, const char *tag, float *out)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int decode_peak(demuxer_t *demuxer, const char *tag, float *out)
|
||||
static int decode_peak(struct mp_log *log, struct mp_tags *tags,
|
||||
const char *tag, float *out)
|
||||
{
|
||||
char *tag_val = NULL;
|
||||
float dec_val;
|
||||
|
||||
*out = 1.0;
|
||||
|
||||
tag_val = mp_tags_get_str(demuxer->metadata, tag);
|
||||
tag_val = mp_tags_get_str(tags, tag);
|
||||
if (!tag_val)
|
||||
return 0;
|
||||
|
||||
if (decode_float(tag_val, &dec_val))
|
||||
return 0;
|
||||
|
||||
if (dec_val == 0.0)
|
||||
return 0;
|
||||
if (decode_float(tag_val, &dec_val) < 0 || dec_val <= 0.0)
|
||||
return -1;
|
||||
|
||||
*out = dec_val;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void apply_replaygain(demuxer_t *demuxer, struct replaygain_data *rg)
|
||||
static struct replaygain_data *decode_rgain(struct mp_log *log,
|
||||
struct mp_tags *tags)
|
||||
{
|
||||
struct replaygain_data rg = {0};
|
||||
|
||||
if (decode_gain(log, tags, "REPLAYGAIN_TRACK_GAIN", &rg.track_gain) >= 0 &&
|
||||
decode_peak(log, tags, "REPLAYGAIN_TRACK_PEAK", &rg.track_peak) >= 0)
|
||||
{
|
||||
if (decode_gain(log, tags, "REPLAYGAIN_ALBUM_GAIN", &rg.album_gain) < 0 ||
|
||||
decode_peak(log, tags, "REPLAYGAIN_ALBUM_PEAK", &rg.album_peak) < 0)
|
||||
{
|
||||
rg.album_gain = rg.track_gain;
|
||||
rg.album_peak = rg.track_peak;
|
||||
}
|
||||
return talloc_memdup(NULL, &rg, sizeof(rg));
|
||||
}
|
||||
|
||||
if (decode_gain(log, tags, "REPLAYGAIN_GAIN", &rg.track_gain) >= 0 &&
|
||||
decode_peak(log, tags, "REPLAYGAIN_PEAK", &rg.track_peak) >= 0)
|
||||
{
|
||||
rg.album_gain = rg.track_gain;
|
||||
rg.album_peak = rg.track_peak;
|
||||
return talloc_memdup(NULL, &rg, sizeof(rg));
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void demux_update_replaygain(demuxer_t *demuxer)
|
||||
{
|
||||
struct demux_internal *in = demuxer->in;
|
||||
for (int n = 0; n < in->num_streams; n++) {
|
||||
struct sh_stream *sh = in->streams[n];
|
||||
if (sh->type == STREAM_AUDIO && !sh->codec->replaygain_data) {
|
||||
MP_VERBOSE(demuxer, "Replaygain: Track=%f/%f Album=%f/%f\n",
|
||||
rg->track_gain, rg->track_peak,
|
||||
rg->album_gain, rg->album_peak);
|
||||
sh->codec->replaygain_data = talloc_memdup(in, rg, sizeof(*rg));
|
||||
struct replaygain_data *rg = decode_rgain(demuxer->log, sh->tags);
|
||||
if (!rg)
|
||||
rg = decode_rgain(demuxer->log, demuxer->metadata);
|
||||
if (rg)
|
||||
sh->codec->replaygain_data = talloc_steal(in, rg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void demux_export_replaygain(demuxer_t *demuxer)
|
||||
{
|
||||
struct replaygain_data rg = {0};
|
||||
|
||||
if (!decode_gain(demuxer, "REPLAYGAIN_TRACK_GAIN", &rg.track_gain) &&
|
||||
!decode_peak(demuxer, "REPLAYGAIN_TRACK_PEAK", &rg.track_peak) &&
|
||||
!decode_gain(demuxer, "REPLAYGAIN_ALBUM_GAIN", &rg.album_gain) &&
|
||||
!decode_peak(demuxer, "REPLAYGAIN_ALBUM_PEAK", &rg.album_peak))
|
||||
{
|
||||
apply_replaygain(demuxer, &rg);
|
||||
}
|
||||
|
||||
if (!decode_gain(demuxer, "REPLAYGAIN_GAIN", &rg.track_gain) &&
|
||||
!decode_peak(demuxer, "REPLAYGAIN_PEAK", &rg.track_peak))
|
||||
{
|
||||
rg.album_gain = rg.track_gain;
|
||||
rg.album_peak = rg.track_peak;
|
||||
apply_replaygain(demuxer, &rg);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy all fields from src to dst, depending on event flags.
|
||||
static void demux_copy(struct demuxer *dst, struct demuxer *src)
|
||||
{
|
||||
@@ -947,15 +1029,29 @@ static void demux_copy(struct demuxer *dst, struct demuxer *src)
|
||||
dst->partially_seekable = src->partially_seekable;
|
||||
dst->filetype = src->filetype;
|
||||
dst->ts_resets_possible = src->ts_resets_possible;
|
||||
dst->allow_refresh_seeks = src->allow_refresh_seeks;
|
||||
dst->fully_read = src->fully_read;
|
||||
dst->start_time = src->start_time;
|
||||
dst->priv = src->priv;
|
||||
}
|
||||
|
||||
if (src->events & DEMUX_EVENT_METADATA) {
|
||||
talloc_free(dst->metadata);
|
||||
dst->metadata = mp_tags_dup(dst, src->metadata);
|
||||
|
||||
if (dst->num_update_stream_tags != src->num_update_stream_tags) {
|
||||
talloc_free(dst->update_stream_tags);
|
||||
dst->update_stream_tags =
|
||||
talloc_zero_array(dst, struct mp_tags *, dst->num_update_stream_tags);
|
||||
dst->num_update_stream_tags = src->num_update_stream_tags;
|
||||
}
|
||||
for (int n = 0; n < dst->num_update_stream_tags; n++) {
|
||||
talloc_free(dst->update_stream_tags[n]);
|
||||
dst->update_stream_tags[n] =
|
||||
talloc_steal(dst->update_stream_tags, src->update_stream_tags[n]);
|
||||
src->update_stream_tags[n] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
dst->events |= src->events;
|
||||
src->events = 0;
|
||||
}
|
||||
@@ -977,8 +1073,6 @@ void demux_changed(demuxer_t *demuxer, int events)
|
||||
|
||||
if (demuxer->events & DEMUX_EVENT_INIT)
|
||||
demuxer_sort_chapters(demuxer);
|
||||
if (demuxer->events & (DEMUX_EVENT_METADATA | DEMUX_EVENT_STREAMS))
|
||||
demux_export_replaygain(demuxer);
|
||||
|
||||
demux_copy(in->d_buffer, demuxer);
|
||||
|
||||
@@ -1001,8 +1095,28 @@ void demux_update(demuxer_t *demuxer)
|
||||
demux_copy(demuxer, in->d_buffer);
|
||||
demuxer->events |= in->events;
|
||||
in->events = 0;
|
||||
if (in->stream_metadata && (demuxer->events & DEMUX_EVENT_METADATA))
|
||||
mp_tags_merge(demuxer->metadata, in->stream_metadata);
|
||||
if (demuxer->events & DEMUX_EVENT_METADATA) {
|
||||
int num_streams = MPMIN(in->num_streams, demuxer->num_update_stream_tags);
|
||||
for (int n = 0; n < num_streams; n++) {
|
||||
struct mp_tags *tags = demuxer->update_stream_tags[n];
|
||||
demuxer->update_stream_tags[n] = NULL;
|
||||
if (tags) {
|
||||
struct sh_stream *sh = in->streams[n];
|
||||
talloc_free(sh->tags);
|
||||
sh->tags = talloc_steal(sh, tags);
|
||||
}
|
||||
}
|
||||
|
||||
// Often useful audio-only files, which have metadata in the audio track
|
||||
// metadata instead of the main metadata (especially OGG).
|
||||
if (in->num_streams == 1)
|
||||
mp_tags_merge(demuxer->metadata, in->streams[0]->tags);
|
||||
|
||||
if (in->stream_metadata)
|
||||
mp_tags_merge(demuxer->metadata, in->stream_metadata);
|
||||
}
|
||||
if (demuxer->events & (DEMUX_EVENT_METADATA | DEMUX_EVENT_STREAMS))
|
||||
demux_update_replaygain(demuxer);
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
}
|
||||
|
||||
@@ -1074,6 +1188,7 @@ static struct demuxer *open_given_type(struct mpv_global *global,
|
||||
.min_secs = demuxer->opts->demuxer_min_secs,
|
||||
.max_packs = demuxer->opts->demuxer_max_packs,
|
||||
.max_bytes = demuxer->opts->demuxer_max_bytes,
|
||||
.initial_state = true,
|
||||
};
|
||||
pthread_mutex_init(&in->lock, NULL);
|
||||
pthread_cond_init(&in->wakeup, NULL);
|
||||
@@ -1277,18 +1392,6 @@ int demux_seek(demuxer_t *demuxer, double seek_pts, int flags)
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Enable doing a "refresh seek" on the next stream switch.
|
||||
// Note that this by design does not disable ongoing refresh seeks, and
|
||||
// does not affect previous stream switch commands (even if they were
|
||||
// asynchronous).
|
||||
void demux_set_enable_refresh_seeks(struct demuxer *demuxer, bool enabled)
|
||||
{
|
||||
struct demux_internal *in = demuxer->in;
|
||||
pthread_mutex_lock(&in->lock);
|
||||
in->refresh_seeks_enabled = enabled;
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
}
|
||||
|
||||
struct sh_stream *demuxer_stream_by_demuxer_id(struct demuxer *d,
|
||||
enum stream_type t, int id)
|
||||
{
|
||||
@@ -1301,19 +1404,22 @@ struct sh_stream *demuxer_stream_by_demuxer_id(struct demuxer *d,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Set whether the given stream should return packets.
|
||||
// ref_pts is used only if the stream is enabled. Then it serves as approximate
|
||||
// start pts for this stream (in the worst case it is ignored).
|
||||
void demuxer_select_track(struct demuxer *demuxer, struct sh_stream *stream,
|
||||
bool selected)
|
||||
double ref_pts, bool selected)
|
||||
{
|
||||
struct demux_internal *in = demuxer->in;
|
||||
pthread_mutex_lock(&in->lock);
|
||||
// don't flush buffers if stream is already selected / unselected
|
||||
if (stream->ds->selected != selected) {
|
||||
stream->ds->selected = selected;
|
||||
stream->ds->active = false;
|
||||
ds_flush(stream->ds);
|
||||
in->tracks_switched = true;
|
||||
if (selected && in->refresh_seeks_enabled)
|
||||
in->start_refresh_seek = true;
|
||||
stream->ds->need_refresh = selected && !in->initial_state;
|
||||
if (stream->ds->need_refresh)
|
||||
in->ref_pts = MP_ADD_PTS(ref_pts, -in->ts_offset);
|
||||
if (in->threading) {
|
||||
pthread_cond_signal(&in->wakeup);
|
||||
} else {
|
||||
|
||||
+7
-9
@@ -178,12 +178,6 @@ typedef struct demuxer {
|
||||
double start_time;
|
||||
// File format allows PTS resets (even if the current file is without)
|
||||
bool ts_resets_possible;
|
||||
// Enable fast track switching hacks. This requires from the demuxer:
|
||||
// - seeking is somewhat reliable; packet contents must not change
|
||||
// - packet position (demux_packet.pos) is set, not negative, unique, and
|
||||
// monotonically increasing
|
||||
// - seeking leaves packet positions invariant
|
||||
bool allow_refresh_seeks;
|
||||
// The file data was fully read, and there is no need to keep the stream
|
||||
// open, keep the cache active, or to run the demuxer thread. Generating
|
||||
// packets is not slow either (unlike e.g. libavdevice pseudo-demuxers).
|
||||
@@ -216,7 +210,10 @@ typedef struct demuxer {
|
||||
struct mp_log *log, *glog;
|
||||
struct demuxer_params *params;
|
||||
|
||||
struct demux_internal *in; // internal to demux.c
|
||||
// internal to demux.c
|
||||
struct demux_internal *in;
|
||||
struct mp_tags **update_stream_tags;
|
||||
int num_update_stream_tags;
|
||||
|
||||
// Since the demuxer can run in its own thread, and the stream is not
|
||||
// thread-safe, only the demuxer is allowed to access the stream directly.
|
||||
@@ -265,13 +262,12 @@ bool demux_cancel_test(struct demuxer *demuxer);
|
||||
|
||||
void demux_flush(struct demuxer *demuxer);
|
||||
int demux_seek(struct demuxer *demuxer, double rel_seek_secs, int flags);
|
||||
void demux_set_enable_refresh_seeks(struct demuxer *demuxer, bool enabled);
|
||||
void demux_set_ts_offset(struct demuxer *demuxer, double offset);
|
||||
|
||||
int demux_control(struct demuxer *demuxer, int cmd, void *arg);
|
||||
|
||||
void demuxer_select_track(struct demuxer *demuxer, struct sh_stream *stream,
|
||||
bool selected);
|
||||
double ref_pts, bool selected);
|
||||
void demux_set_stream_autoselect(struct demuxer *demuxer, bool autoselect);
|
||||
|
||||
void demuxer_help(struct mp_log *log);
|
||||
@@ -280,6 +276,8 @@ int demuxer_add_attachment(struct demuxer *demuxer, char *name,
|
||||
char *type, void *data, size_t data_size);
|
||||
int demuxer_add_chapter(demuxer_t *demuxer, char *name,
|
||||
double pts, uint64_t demuxer_id);
|
||||
void demux_set_stream_tags(struct demuxer *demuxer, struct sh_stream *sh,
|
||||
struct mp_tags *tags);
|
||||
|
||||
double demuxer_get_time_length(struct demuxer *demuxer);
|
||||
|
||||
|
||||
+1
-1
@@ -57,7 +57,7 @@ static void reselect_streams(demuxer_t *demuxer)
|
||||
for (int n = 0; n < MPMIN(num_slave, p->num_streams); n++) {
|
||||
if (p->streams[n]) {
|
||||
demuxer_select_track(p->slave, demux_get_stream(p->slave, n),
|
||||
demux_stream_is_selected(p->streams[n]));
|
||||
MP_NOPTS_VALUE, demux_stream_is_selected(p->streams[n]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+22
-45
@@ -168,7 +168,6 @@ typedef struct lavf_priv {
|
||||
int num_streams;
|
||||
int cur_program;
|
||||
char *mime_type;
|
||||
bool merge_track_metadata;
|
||||
double seek_delay;
|
||||
} lavf_priv_t;
|
||||
|
||||
@@ -502,7 +501,7 @@ static void select_tracks(struct demuxer *demuxer, int start)
|
||||
}
|
||||
}
|
||||
|
||||
static void export_replaygain(demuxer_t *demuxer, struct mp_codec_params *c,
|
||||
static void export_replaygain(demuxer_t *demuxer, struct sh_stream *sh,
|
||||
AVStream *st)
|
||||
{
|
||||
for (int i = 0; i < st->nb_side_data; i++) {
|
||||
@@ -528,7 +527,10 @@ static void export_replaygain(demuxer_t *demuxer, struct mp_codec_params *c,
|
||||
rgain->album_peak = (av_rgain->album_peak != 0.0) ?
|
||||
av_rgain->album_peak / 100000.0f : 1.0;
|
||||
|
||||
c->replaygain_data = rgain;
|
||||
// This must be run only before the stream was added, otherwise there
|
||||
// will be race conditions with accesses from the user thread.
|
||||
assert(!sh->ds);
|
||||
sh->codec->replaygain_data = rgain;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -575,7 +577,7 @@ static void handle_new_stream(demuxer_t *demuxer, int i)
|
||||
delay = lavc_delay / (double)codec->sample_rate;
|
||||
priv->seek_delay = MPMAX(priv->seek_delay, delay);
|
||||
|
||||
export_replaygain(demuxer, sh->codec, st);
|
||||
export_replaygain(demuxer, sh, st);
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -681,6 +683,7 @@ static void handle_new_stream(demuxer_t *demuxer, int i)
|
||||
if (!sh->title && sh->hls_bitrate > 0)
|
||||
sh->title = talloc_asprintf(sh, "bitrate %d", sh->hls_bitrate);
|
||||
sh->missing_timestamps = !!(priv->avif_flags & AVFMT_NOTIMESTAMPS);
|
||||
mp_tags_copy_from_av_dictionary(sh->tags, st->metadata);
|
||||
demux_add_sh_stream(demuxer, sh);
|
||||
}
|
||||
|
||||
@@ -703,14 +706,14 @@ static void update_metadata(demuxer_t *demuxer, AVPacket *pkt)
|
||||
priv->avfc->event_flags = 0;
|
||||
demux_changed(demuxer, DEMUX_EVENT_METADATA);
|
||||
}
|
||||
if (priv->merge_track_metadata) {
|
||||
for (int n = 0; n < priv->num_streams; n++) {
|
||||
AVStream *st = priv->streams[n] ? priv->avfc->streams[n] : NULL;
|
||||
if (st && st->event_flags & AVSTREAM_EVENT_FLAG_METADATA_UPDATED) {
|
||||
mp_tags_copy_from_av_dictionary(demuxer->metadata, st->metadata);
|
||||
st->event_flags = 0;
|
||||
demux_changed(demuxer, DEMUX_EVENT_METADATA);
|
||||
}
|
||||
|
||||
for (int n = 0; n < priv->num_streams; n++) {
|
||||
AVStream *st = priv->streams[n] ? priv->avfc->streams[n] : NULL;
|
||||
if (st && st->event_flags & AVSTREAM_EVENT_FLAG_METADATA_UPDATED) {
|
||||
st->event_flags = 0;
|
||||
struct mp_tags *tags = talloc_zero(NULL, struct mp_tags);
|
||||
mp_tags_copy_from_av_dictionary(tags, st->metadata);
|
||||
demux_set_stream_tags(demuxer, priv->streams[n], tags);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -846,16 +849,6 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
|
||||
add_new_streams(demuxer);
|
||||
|
||||
// Often useful with OGG audio-only files, which have metadata in the audio
|
||||
// track metadata instead of the main metadata.
|
||||
if (demux_get_num_stream(demuxer) == 1) {
|
||||
priv->merge_track_metadata = true;
|
||||
for (int n = 0; n < priv->num_streams; n++) {
|
||||
if (priv->streams[n])
|
||||
mp_tags_copy_from_av_dictionary(demuxer->metadata, avfc->streams[n]->metadata);
|
||||
}
|
||||
}
|
||||
|
||||
mp_tags_copy_from_av_dictionary(demuxer->metadata, avfc->metadata);
|
||||
update_metadata(demuxer, NULL);
|
||||
|
||||
@@ -865,7 +858,6 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
demuxer->start_time = priv->avfc->start_time == AV_NOPTS_VALUE ?
|
||||
0 : (double)priv->avfc->start_time / AV_TIME_BASE;
|
||||
|
||||
demuxer->allow_refresh_seeks = matches_avinputformat_name(priv, "mp4");
|
||||
demuxer->fully_read = priv->format_hack.fully_read;
|
||||
|
||||
return 0;
|
||||
@@ -953,30 +945,15 @@ static void demux_seek_lavf(demuxer_t *demuxer, double seek_pts, int flags)
|
||||
seek_pts_av = seek_pts * AV_TIME_BASE;
|
||||
}
|
||||
|
||||
int r;
|
||||
if (!priv->avfc->iformat->read_seek2) {
|
||||
// Normal seeking.
|
||||
int r = av_seek_frame(priv->avfc, -1, seek_pts_av, avsflags);
|
||||
if (r < 0 && (avsflags & AVSEEK_FLAG_BACKWARD)) {
|
||||
// When seeking before the beginning of the file, and seeking fails,
|
||||
// try again without the backwards flag to make it seek to the
|
||||
// beginning.
|
||||
avsflags &= ~AVSEEK_FLAG_BACKWARD;
|
||||
r = av_seek_frame(priv->avfc, -1, seek_pts_av, avsflags);
|
||||
if (r < 0 && (avsflags & AVSEEK_FLAG_BACKWARD)) {
|
||||
// When seeking before the beginning of the file, and seeking fails,
|
||||
// try again without the backwards flag to make it seek to the
|
||||
// beginning.
|
||||
avsflags &= ~AVSEEK_FLAG_BACKWARD;
|
||||
r = av_seek_frame(priv->avfc, -1, seek_pts_av, avsflags);
|
||||
}
|
||||
} else {
|
||||
// av_seek_frame() won't work. Use "new" seeking API. We don't use this
|
||||
// API by default, because there are some major issues.
|
||||
// Set max_ts==ts, so that demuxing starts from an earlier position in
|
||||
// the worst case.
|
||||
r = avformat_seek_file(priv->avfc, -1, INT64_MIN,
|
||||
seek_pts_av, seek_pts_av, avsflags);
|
||||
// Similar issue as in the normal seeking codepath.
|
||||
if (r < 0) {
|
||||
r = avformat_seek_file(priv->avfc, -1, INT64_MIN,
|
||||
seek_pts_av, INT64_MAX, avsflags);
|
||||
}
|
||||
}
|
||||
|
||||
if (r < 0) {
|
||||
char buf[180];
|
||||
av_strerror(r, buf, sizeof(buf));
|
||||
|
||||
@@ -65,23 +65,10 @@ static int open_file(struct demuxer *demuxer, enum demux_check check)
|
||||
char **files = NULL;
|
||||
int num_files = 0;
|
||||
|
||||
for (;;) {
|
||||
struct archive_entry *entry;
|
||||
int r = archive_read_next_header(mpa->arch, &entry);
|
||||
if (r == ARCHIVE_EOF)
|
||||
break;
|
||||
if (r < ARCHIVE_OK)
|
||||
MP_ERR(demuxer, "%s\n", archive_error_string(mpa->arch));
|
||||
if (r < ARCHIVE_WARN)
|
||||
break;
|
||||
if (archive_entry_filetype(entry) != AE_IFREG)
|
||||
continue;
|
||||
const char *fn = archive_entry_pathname(entry);
|
||||
// Some archives may have no filenames.
|
||||
if (!fn)
|
||||
fn = talloc_asprintf(mpa, "mpv_unknown#%d\n", num_files);
|
||||
while (mp_archive_next_entry(mpa)) {
|
||||
// stream_libarchive.c does the real work
|
||||
char *f = talloc_asprintf(mpa, "archive://%s|%s", prefix, fn);
|
||||
char *f = talloc_asprintf(mpa, "archive://%s|%s", prefix,
|
||||
mpa->entry_filename);
|
||||
MP_TARRAY_APPEND(mpa, files, num_files, f);
|
||||
}
|
||||
|
||||
|
||||
+16
-5
@@ -93,6 +93,7 @@ typedef struct mkv_content_encoding {
|
||||
|
||||
typedef struct mkv_track {
|
||||
int tnum;
|
||||
uint64_t uid;
|
||||
char *name;
|
||||
struct sh_stream *stream;
|
||||
|
||||
@@ -595,6 +596,7 @@ static void parse_trackentry(struct demuxer *demuxer,
|
||||
} else {
|
||||
MP_ERR(demuxer, "Missing track number!\n");
|
||||
}
|
||||
track->uid = entry->track_uid;
|
||||
|
||||
if (entry->name) {
|
||||
track->name = talloc_strdup(track, entry->name);
|
||||
@@ -986,9 +988,6 @@ static void process_tags(demuxer_t *demuxer)
|
||||
|
||||
for (int i = 0; i < tags->n_tag; i++) {
|
||||
struct ebml_tag tag = tags->tag[i];
|
||||
if (tag.targets.target_track_uid || tag.targets.target_attachment_uid)
|
||||
continue;
|
||||
|
||||
struct mp_tags *dst = NULL;
|
||||
|
||||
if (tag.targets.target_chapter_uid) {
|
||||
@@ -1009,6 +1008,19 @@ static void process_tags(demuxer_t *demuxer)
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (tag.targets.target_track_uid) {
|
||||
for (int n = 0; n < mkv_d->num_tracks; n++) {
|
||||
if (mkv_d->tracks[n]->uid ==
|
||||
tag.targets.target_track_uid)
|
||||
{
|
||||
struct sh_stream *sh = mkv_d->tracks[n]->stream;
|
||||
if (sh)
|
||||
dst = sh->tags;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (tag.targets.target_attachment_uid) {
|
||||
/* ignore */
|
||||
} else {
|
||||
dst = demuxer->metadata;
|
||||
}
|
||||
@@ -1924,10 +1936,9 @@ static int demux_mkv_open(demuxer_t *demuxer, enum demux_check check)
|
||||
|
||||
MP_VERBOSE(demuxer, "All headers are parsed!\n");
|
||||
|
||||
process_tags(demuxer);
|
||||
display_create_tracks(demuxer);
|
||||
add_coverart(demuxer);
|
||||
demuxer->allow_refresh_seeks = true;
|
||||
process_tags(demuxer);
|
||||
|
||||
probe_first_timestamp(demuxer);
|
||||
if (opts->demux_mkv->probe_duration)
|
||||
|
||||
+14
-5
@@ -36,7 +36,7 @@
|
||||
#include "osdep/endian.h"
|
||||
|
||||
struct demux_rawaudio_opts {
|
||||
struct mp_chmap channels;
|
||||
struct m_channels channels;
|
||||
int samplerate;
|
||||
int aformat;
|
||||
};
|
||||
@@ -49,13 +49,13 @@ struct demux_rawaudio_opts {
|
||||
#define OPT_BASE_STRUCT struct demux_rawaudio_opts
|
||||
const struct m_sub_options demux_rawaudio_conf = {
|
||||
.opts = (const m_option_t[]) {
|
||||
OPT_CHMAP("channels", channels, CONF_MIN, .min = 1),
|
||||
OPT_CHANNELS("channels", channels, 0, .min = 1),
|
||||
OPT_INTRANGE("rate", samplerate, 0, 1000, 8 * 48000),
|
||||
OPT_CHOICE("format", aformat, 0,
|
||||
({"u8", PCM(0, 0, 8, 0)},
|
||||
{"s8", PCM(1, 0, 8, 0)},
|
||||
{"u16le", PCM(0, 0, 16, 0)}, {"u16be", PCM(0, 0, 16, 1)},
|
||||
{"s16le", PCM(1, 0, 16, 0)}, {"u16be", PCM(1, 0, 16, 1)},
|
||||
{"s16le", PCM(1, 0, 16, 0)}, {"s16be", PCM(1, 0, 16, 1)},
|
||||
{"u24le", PCM(0, 0, 24, 0)}, {"u24be", PCM(0, 0, 24, 1)},
|
||||
{"s24le", PCM(1, 0, 24, 0)}, {"s24be", PCM(1, 0, 24, 1)},
|
||||
{"u32le", PCM(0, 0, 32, 0)}, {"u32be", PCM(0, 0, 32, 1)},
|
||||
@@ -75,7 +75,11 @@ const struct m_sub_options demux_rawaudio_conf = {
|
||||
.size = sizeof(struct demux_rawaudio_opts),
|
||||
.defaults = &(const struct demux_rawaudio_opts){
|
||||
// Note that currently, stream_cdda expects exactly these parameters!
|
||||
.channels = MP_CHMAP_INIT_STEREO,
|
||||
.channels = {
|
||||
.set = 1,
|
||||
.chmaps = (struct mp_chmap[]){ MP_CHMAP_INIT_STEREO, },
|
||||
.num_chmaps = 1,
|
||||
},
|
||||
.samplerate = 44100,
|
||||
.aformat = PCM(1, 0, 16, 0), // s16le
|
||||
},
|
||||
@@ -130,9 +134,14 @@ static int demux_rawaudio_open(demuxer_t *demuxer, enum demux_check check)
|
||||
if (check != DEMUX_CHECK_REQUEST && check != DEMUX_CHECK_FORCE)
|
||||
return -1;
|
||||
|
||||
if (opts->channels.num_chmaps != 1) {
|
||||
MP_ERR(demuxer, "Invalid channels option given.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct sh_stream *sh = demux_alloc_sh_stream(STREAM_AUDIO);
|
||||
struct mp_codec_params *c = sh->codec;
|
||||
c->channels = opts->channels;
|
||||
c->channels = opts->channels.chmaps[0];
|
||||
c->force_channels = true;
|
||||
c->samplerate = opts->samplerate;
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ static void reselect_streams(struct demuxer *demuxer)
|
||||
// This stops demuxer readahead for inactive segments.
|
||||
if (!p->current || seg->d != p->current->d)
|
||||
selected = false;
|
||||
demuxer_select_track(seg->d, sh, selected);
|
||||
demuxer_select_track(seg->d, sh, MP_NOPTS_VALUE, selected);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -197,6 +197,11 @@ static int d_fill_buffer(struct demuxer *demuxer)
|
||||
if (pkt->stream < 0)
|
||||
goto drop;
|
||||
|
||||
// for refresh seeks, demux.c prefers monotonically increasing packet pos
|
||||
// since the packet pos is meaningless anyway for timeline, use it
|
||||
if (pkt->pos >= 0)
|
||||
pkt->pos |= (seg->index & 0x7FFFULL) << 48;
|
||||
|
||||
struct virtual_stream *vs = &p->streams[pkt->stream];
|
||||
|
||||
if (pkt->pts != MP_NOPTS_VALUE && pkt->pts >= seg->end) {
|
||||
@@ -301,6 +306,9 @@ static int d_open(struct demuxer *demuxer, enum demux_check check)
|
||||
demuxer->metadata = meta->metadata;
|
||||
demuxer->attachments = meta->attachments;
|
||||
demuxer->num_attachments = meta->num_attachments;
|
||||
demuxer->editions = meta->editions;
|
||||
demuxer->num_editions = meta->num_editions;
|
||||
demuxer->edition = meta->edition;
|
||||
|
||||
int num_streams = demux_get_num_stream(meta);
|
||||
for (int n = 0; n < num_streams; n++) {
|
||||
|
||||
@@ -46,6 +46,8 @@ struct sh_stream {
|
||||
bool forced_track; // container forced track flag
|
||||
int hls_bitrate;
|
||||
|
||||
struct mp_tags *tags;
|
||||
|
||||
bool missing_timestamps;
|
||||
|
||||
// stream is a picture (such as album art)
|
||||
|
||||
+1
-1
@@ -79,7 +79,7 @@
|
||||
#> playlist-next # skip to next file
|
||||
#ENTER playlist-next # skip to next file
|
||||
#< playlist-prev # skip to previous file
|
||||
#O no-osd cycle_values osd-level 3 1 # cycle through OSD mode
|
||||
#O no-osd cycle-values osd-level 3 1 # cycle through OSD mode
|
||||
#o show-progress
|
||||
#P show-progress
|
||||
#I show-text "${filename}" # display filename in osd
|
||||
|
||||
@@ -24,19 +24,19 @@ SHARP cycle audio # switch audio streams
|
||||
BS set speed 1.0 # reset speed to normal
|
||||
q quit
|
||||
ESC quit
|
||||
ENTER playlist_next force # skip to next file
|
||||
ENTER playlist-next force # skip to next file
|
||||
p cycle pause
|
||||
. frame_step # advance one frame and pause
|
||||
. frame-step # advance one frame and pause
|
||||
SPACE cycle pause
|
||||
HOME set playlist-pos 0 # not the same as MPlayer
|
||||
#END pt_up_step -1
|
||||
> playlist_next # skip to next file
|
||||
< playlist_prev # previous
|
||||
> playlist-next # skip to next file
|
||||
< playlist-prev # previous
|
||||
#INS alt_src_step 1
|
||||
#DEL alt_src_step -1
|
||||
o osd
|
||||
I show_text "${filename}" # display filename in osd
|
||||
P show_progress
|
||||
I show-text "${filename}" # display filename in osd
|
||||
P show-progress
|
||||
z add sub-delay -0.1 # subtract 100 ms delay from subs
|
||||
x add sub-delay +0.1 # add
|
||||
9 add volume -1
|
||||
@@ -57,8 +57,8 @@ d cycle framedrop
|
||||
D cycle deinterlace # toggle deinterlacer (auto-inserted filter)
|
||||
r add sub-pos -1 # move subtitles up
|
||||
t add sub-pos +1 # down
|
||||
#? sub_step +1 # immediately display next subtitle
|
||||
#? sub_step -1 # previous
|
||||
#? sub-step +1 # immediately display next subtitle
|
||||
#? sub-step -1 # previous
|
||||
#? add sub-scale +0.1 # increase subtitle font size
|
||||
#? add sub-scale -0.1 # decrease subtitle font size
|
||||
f cycle fullscreen
|
||||
|
||||
@@ -17,7 +17,7 @@ d cycle framedrop
|
||||
|
||||
# changed in mpv 0.7.0
|
||||
|
||||
ENTER playlist_next force
|
||||
ENTER playlist-next force
|
||||
|
||||
# changed in mpv 0.6.0
|
||||
|
||||
@@ -38,4 +38,4 @@ TAB cycle program
|
||||
A cycle angle
|
||||
U stop
|
||||
o osd
|
||||
I show_text "${filename}"
|
||||
I show-text "${filename}"
|
||||
+1
-1
@@ -257,7 +257,7 @@ static const struct legacy_cmd legacy_cmds[] = {
|
||||
{"saturation", "add saturation"},
|
||||
{"hue", "add hue"},
|
||||
{"switch_vsync", "cycle vsync"},
|
||||
{"sub_load", "sub_add"},
|
||||
{"sub_load", "sub-add"},
|
||||
{"sub_select", "cycle sub"},
|
||||
{"sub_pos", "add sub-pos"},
|
||||
{"sub_delay", "add sub-delay"},
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ void mp_event_drop_files(struct input_ctx *ictx, int num_files, char **files,
|
||||
for (int i = 0; i < num_files; i++) {
|
||||
const char *cmd[] = {
|
||||
"osd-auto",
|
||||
"sub_add",
|
||||
"sub-add",
|
||||
files[i],
|
||||
NULL
|
||||
};
|
||||
|
||||
+1
-4
@@ -1247,12 +1247,9 @@ void mp_input_load(struct input_ctx *ictx)
|
||||
void *tmp = talloc_new(NULL);
|
||||
char **files = mp_find_all_config_files(tmp, ictx->global, "input.conf");
|
||||
for (int n = 0; files && files[n]; n++)
|
||||
config_ok = config_ok | parse_config_file(ictx, files[n], false);
|
||||
parse_config_file(ictx, files[n], false);
|
||||
talloc_free(tmp);
|
||||
}
|
||||
if (!config_ok) {
|
||||
MP_VERBOSE(ictx, "Falling back on default (hardcoded) input config\n");
|
||||
}
|
||||
|
||||
if (input_conf->use_alt_gr) {
|
||||
ictx->using_alt_gr = true;
|
||||
|
||||
+1
-2
@@ -133,8 +133,7 @@ static void *client_thread(void *p)
|
||||
}
|
||||
|
||||
if (fds[0].revents & POLLIN) {
|
||||
char discard[100];
|
||||
(void)read(pipe_fd, discard, sizeof(discard));
|
||||
mp_flush_wakeup_pipe(pipe_fd);
|
||||
|
||||
while (1) {
|
||||
mpv_event *event = mpv_wait_event(arg->client, 0);
|
||||
|
||||
+12
-8
@@ -215,7 +215,7 @@ extern "C" {
|
||||
* relational operators (<, >, <=, >=).
|
||||
*/
|
||||
#define MPV_MAKE_VERSION(major, minor) (((major) << 16) | (minor) | 0UL)
|
||||
#define MPV_CLIENT_API_VERSION MPV_MAKE_VERSION(1, 21)
|
||||
#define MPV_CLIENT_API_VERSION MPV_MAKE_VERSION(1, 22)
|
||||
|
||||
/**
|
||||
* Return the MPV_CLIENT_API_VERSION the mpv source has been compiled with.
|
||||
@@ -248,7 +248,7 @@ typedef enum mpv_error {
|
||||
* making asynchronous requests. (Bugs in the client API implementation
|
||||
* could also trigger this, e.g. if events become "lost".)
|
||||
*/
|
||||
MPV_ERROR_EVENT_QUEUE_FULL = -1,
|
||||
MPV_ERROR_EVENT_QUEUE_FULL = -1,
|
||||
/**
|
||||
* Memory allocation failed.
|
||||
*/
|
||||
@@ -299,7 +299,7 @@ typedef enum mpv_error {
|
||||
*/
|
||||
MPV_ERROR_COMMAND = -12,
|
||||
/**
|
||||
* Generic error on loading (used with mpv_event_end_file.error).
|
||||
* Generic error on loading (usually used with mpv_event_end_file.error).
|
||||
*/
|
||||
MPV_ERROR_LOADING_FAILED = -13,
|
||||
/**
|
||||
@@ -329,7 +329,11 @@ typedef enum mpv_error {
|
||||
/**
|
||||
* The API function which was called is a stub only.
|
||||
*/
|
||||
MPV_ERROR_NOT_IMPLEMENTED = -19
|
||||
MPV_ERROR_NOT_IMPLEMENTED = -19,
|
||||
/**
|
||||
* Unspecified error.
|
||||
*/
|
||||
MPV_ERROR_GENERIC = -20
|
||||
} mpv_error;
|
||||
|
||||
/**
|
||||
@@ -660,7 +664,7 @@ typedef enum mpv_format {
|
||||
MPV_FORMAT_NODE_MAP = 8,
|
||||
/**
|
||||
* A raw, untyped byte array. Only used only with mpv_node, and only in
|
||||
* some very special situations. (Currently, only for the screenshot_raw
|
||||
* some very special situations. (Currently, only for the screenshot-raw
|
||||
* command.)
|
||||
*/
|
||||
MPV_FORMAT_BYTE_ARRAY = 9
|
||||
@@ -1148,13 +1152,13 @@ typedef enum mpv_event_id {
|
||||
* @deprecated This was used internally with the internal "script_dispatch"
|
||||
* command to dispatch keyboard and mouse input for the OSC.
|
||||
* It was never useful in general and has been completely
|
||||
* replaced with "script_binding".
|
||||
* replaced with "script-binding".
|
||||
* This event never happens anymore, and is included in this
|
||||
* header only for compatibility.
|
||||
*/
|
||||
MPV_EVENT_SCRIPT_INPUT_DISPATCH = 15,
|
||||
/**
|
||||
* Triggered by the script_message input command. The command uses the
|
||||
* Triggered by the script-message input command. The command uses the
|
||||
* first argument of the command as client name (see mpv_client_name()) to
|
||||
* dispatch the message, and passes along all arguments starting from the
|
||||
* second argument as strings.
|
||||
@@ -1446,7 +1450,7 @@ int mpv_request_event(mpv_handle *ctx, mpv_event_id event, int enable);
|
||||
* required log level for a message to be received with MPV_EVENT_LOG_MESSAGE.
|
||||
*
|
||||
* @param min_level Minimal log level as string. Valid log levels:
|
||||
* no fatal error warn info status v debug trace
|
||||
* no fatal error warn info v debug trace
|
||||
* The value "no" disables all messages. This is the default.
|
||||
* An exception is the value "terminal-default", which uses the
|
||||
* log level as set by the "--msg-level" option. This works
|
||||
|
||||
@@ -37,6 +37,7 @@ mpv_set_property
|
||||
mpv_set_property_async
|
||||
mpv_set_property_string
|
||||
mpv_set_wakeup_callback
|
||||
mpv_stream_cb_add_ro
|
||||
mpv_suspend
|
||||
mpv_terminate_destroy
|
||||
mpv_unobserve_property
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
/* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Note: the client API is licensed under ISC (see above) to ease
|
||||
* interoperability with other licenses. But keep in mind that the
|
||||
* mpv core is still mostly GPLv2+. It's up to lawyers to decide
|
||||
* whether applications using this API are affected by the GPL.
|
||||
* One argument against this is that proprietary applications
|
||||
* using mplayer in slave mode is apparently tolerated, and this
|
||||
* API is basically equivalent to slave mode.
|
||||
*/
|
||||
|
||||
#ifndef MPV_CLIENT_API_STREAM_CB_H_
|
||||
#define MPV_CLIENT_API_STREAM_CB_H_
|
||||
|
||||
#include "client.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Warning: this API is not stable yet.
|
||||
*
|
||||
* Overview
|
||||
* --------
|
||||
*
|
||||
* This API can be used to make mpv read from a stream with a custom
|
||||
* implementation. This interface is inspired by funopen on BSD and
|
||||
* fopencookie on linux. The stream is backed by user-defined callbacks
|
||||
* which can implement customized open, read, seek, size and close behaviors.
|
||||
*
|
||||
* Usage
|
||||
* -----
|
||||
*
|
||||
* Register your stream callbacks with the mpv_stream_cb_add_ro() function. You
|
||||
* have to provide a mpv_stream_cb_open_ro_fn callback to it (open_fn argument).
|
||||
*
|
||||
* Once registered, you can `loadfile myprotocol://myfile`. Your open_fn will be
|
||||
* invoked with the URI and you must fill out the provided mpv_stream_cb_info
|
||||
* struct. This includes your stream callbacks (like read_fn), and an opaque
|
||||
* cookie, which will be passed as the first argument to all the remaining
|
||||
* stream callbacks.
|
||||
*
|
||||
* Note that your custom callbacks must not invoke libmpv APIs as that would
|
||||
* cause a deadlock.
|
||||
*
|
||||
* Stream lifetime
|
||||
* ---------------
|
||||
*
|
||||
* A stream remains valid until its close callback has been called. It's up to
|
||||
* libmpv to call the close callback, and the libmpv user cannot close it
|
||||
* directly with the stream_cb API.
|
||||
*
|
||||
* For example, if you consider your custom stream to become suddenly invalid
|
||||
* (maybe because the underlying stream died), libmpv will continue using your
|
||||
* stream. All you can do is returning errors from each callback, until libmpv
|
||||
* gives up and closes it.
|
||||
*
|
||||
* Protocol registration and lifetime
|
||||
* ----------------------------------
|
||||
*
|
||||
* Protocols remain registered until the mpv instance is terminated. This means
|
||||
* in particular that it can outlive the mpv_handle that was used to register
|
||||
* it, but once mpv_terminate_destroy() is called, your registered callbacks
|
||||
* will not be called again.
|
||||
*
|
||||
* Protocol unregistration is finished after the mpv core has been destroyed
|
||||
* (e.g. after mpv_terminate_destroy() has returned).
|
||||
*
|
||||
* If you do not call mpv_terminate_destroy() yourself (e.g. plugin-style code),
|
||||
* you will have to deal with the registration or even streams outliving your
|
||||
* code. Here are some possible ways to do this:
|
||||
* - call mpv_terminate_destroy(), which destroys the core, and will make sure
|
||||
* all streams are closed once this function returns
|
||||
* - you refcount all resources your stream "cookies" reference, so that it
|
||||
* doesn't matter if streams live longer than expected
|
||||
* - create "cancellation" semantics: after your protocol has been unregistered,
|
||||
* notify all your streams that are still opened, and make them drop all
|
||||
* referenced resources - then return errors from the stream callbacks as
|
||||
* long as the stream is still opened
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Read callback used to implement a custom stream. The semantics of the
|
||||
* callback match read(2) in blocking mode. Short reads are allowed (you can
|
||||
* return less bytes than requested, and libmpv will retry reading the rest
|
||||
* with a nother call). If no data can be immediately read, the callback must
|
||||
* block until there is new data. A return of 0 will be interpreted as final
|
||||
* EOF, although libmpv might retry the read, or seek to a different position.
|
||||
*
|
||||
* @param cookie opaque cookie identifying the stream,
|
||||
* returned from mpv_stream_cb_open_fn
|
||||
* @param buf buffer to read data into
|
||||
* @param size of the buffer
|
||||
* @return number of bytes read into the buffer
|
||||
* @return 0 on EOF
|
||||
* @return -1 on error
|
||||
*/
|
||||
typedef int64_t (*mpv_stream_cb_read_fn)(void *cookie, char *buf, uint64_t nbytes);
|
||||
|
||||
/**
|
||||
* Seek callback used to implement a custom stream.
|
||||
*
|
||||
* Note that mpv will issue a seek to position 0 immediately after opening. This
|
||||
* is used to test whether the stream is seekable (since seekability might
|
||||
* depend on the URI contents, not just the protocol). Return
|
||||
* MPV_ERROR_UNSUPPORTED if seeking is not implemented for this stream. This
|
||||
* seek also servies to establish the fact that streams start at position 0.
|
||||
*
|
||||
* This callback can be NULL, in which it behaves as if always returning
|
||||
* MPV_ERROR_UNSUPPORTED.
|
||||
*
|
||||
* @param cookie opaque cookie identifying the stream,
|
||||
* returned from mpv_stream_cb_open_fn
|
||||
* @param offset target absolut stream position
|
||||
* @return the resulting offset of the stream
|
||||
* MPV_ERROR_UNSUPPORTED or MPV_ERROR_GENERIC if the seek failed
|
||||
*/
|
||||
typedef int64_t (*mpv_stream_cb_seek_fn)(void *cookie, int64_t offset);
|
||||
|
||||
/**
|
||||
* Size callback used to implement a custom stream.
|
||||
*
|
||||
* Return MPV_ERROR_UNSUPPORTED if no size is known.
|
||||
*
|
||||
* This callback can be NULL, in which it behaves as if always returning
|
||||
* MPV_ERROR_UNSUPPORTED.
|
||||
*
|
||||
* @param cookie opaque cookie identifying the stream,
|
||||
* returned from mpv_stream_cb_open_fn
|
||||
* @return the total size in bytes of the stream
|
||||
*/
|
||||
typedef int64_t (*mpv_stream_cb_size_fn)(void *cookie);
|
||||
|
||||
/**
|
||||
* Close callback used to implement a custom stream.
|
||||
*
|
||||
* @param cookie opaque cookie identifying the stream,
|
||||
* returned from mpv_stream_cb_open_fn
|
||||
*/
|
||||
typedef void (*mpv_stream_cb_close_fn)(void *cookie);
|
||||
|
||||
/**
|
||||
* See mpv_stream_cb_open_ro_fn callback.
|
||||
*/
|
||||
typedef struct mpv_stream_cb_info {
|
||||
/**
|
||||
* Opaque user-provided value, which will be passed to the other callbacks.
|
||||
* The close callback will be called to release the cookie. It is not
|
||||
* interpreted by mpv. It doesn't even need to be a valid pointer.
|
||||
*
|
||||
* The user sets this in the mpv_stream_cb_open_ro_fn callback.
|
||||
*/
|
||||
void *cookie;
|
||||
|
||||
/**
|
||||
* Callbacks set by the user in the mpv_stream_cb_open_ro_fn callback. Some
|
||||
* of them are optional, and can be left unset.
|
||||
*
|
||||
* The following callbacks are mandatory: read_fn, close_fn
|
||||
*/
|
||||
mpv_stream_cb_read_fn read_fn;
|
||||
mpv_stream_cb_seek_fn seek_fn;
|
||||
mpv_stream_cb_size_fn size_fn;
|
||||
mpv_stream_cb_close_fn close_fn;
|
||||
} mpv_stream_cb_info;
|
||||
|
||||
/**
|
||||
* Open callback used to implement a custom read-only (ro) stream. The user
|
||||
* must set the callback fields in the passed info struct. The cookie field
|
||||
* also can be set to store state associated to the stream instance.
|
||||
*
|
||||
* Note that the info struct is valid only for the duration of this callback.
|
||||
* You can't change the callbacks or the pointer to the cookie at a later point.
|
||||
*
|
||||
* Each stream instance created by the open callback can have different
|
||||
* callbacks.
|
||||
*
|
||||
* The close_fn callback will terminate the stream instance. The pointers to
|
||||
* your callbacks and cookie will be discarded, and the callbacks will not be
|
||||
* called again.
|
||||
*
|
||||
* @param user_data opaque user data provided via mpv_stream_cb_add()
|
||||
* @param uri name of the stream to be opened (with protocol prefix)
|
||||
* @param info fields which the user should fill
|
||||
* @return opaque cookie identifing the newly opened stream
|
||||
* @return 0 on success, MPV_ERROR_LOADING_FAILED if the URI cannot be opened.
|
||||
*/
|
||||
typedef int (*mpv_stream_cb_open_ro_fn)(void *user_data, char *uri,
|
||||
mpv_stream_cb_info *info);
|
||||
|
||||
/**
|
||||
* Add a custom stream protocol. This will register a protocol handler under
|
||||
* the given protocol prefix, and invoke the given callbacks if an URI with the
|
||||
* matching protocol prefix is opened.
|
||||
*
|
||||
* The "ro" is for read-only - only read-only streams can be registered with
|
||||
* this function.
|
||||
*
|
||||
* The callback remains registered until the mpv core is registered.
|
||||
*
|
||||
* If a custom stream with the same name is already registered, then the
|
||||
* MPV_ERROR_INVALID_PARAMETER error is returned.
|
||||
*
|
||||
* @param protocol protocol prefix, for example "foo" for "foo://" URIs
|
||||
* @param user_data opaque pointer passed into the mpv_stream_cb_open_fn
|
||||
* callback.
|
||||
* @return error code
|
||||
*/
|
||||
int mpv_stream_cb_add_ro(mpv_handle *ctx, const char *protocol, void *user_data,
|
||||
mpv_stream_cb_open_ro_fn open_fn);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+1
-1
@@ -116,7 +116,7 @@ int bstr_validate_utf8(struct bstr s);
|
||||
// talloc, with talloc_ctx as parent.
|
||||
struct bstr bstr_sanitize_utf8_latin1(void *talloc_ctx, struct bstr s);
|
||||
|
||||
// Return the text before the occurance of a character, and return it. Change
|
||||
// Return the text before the occurrence of a character, and return it. Change
|
||||
// *rest to point to the text following this character. (rest can be NULL.)
|
||||
struct bstr bstr_splitchar(struct bstr str, struct bstr *rest, const char c);
|
||||
|
||||
|
||||
@@ -499,6 +499,13 @@ struct m_config_option *m_config_get_co(const struct m_config *config,
|
||||
co->warning_was_printed = true;
|
||||
}
|
||||
return NULL;
|
||||
} else if (co->opt->deprecation_message) {
|
||||
if (!co->warning_was_printed) {
|
||||
MP_WARN(config, "Warning: option %s%s is deprecated "
|
||||
"and might be removed in the future (%s).\n",
|
||||
prefix, co->name, co->opt->deprecation_message);
|
||||
co->warning_was_printed = true;
|
||||
}
|
||||
}
|
||||
return co;
|
||||
}
|
||||
|
||||
+85
-25
@@ -2287,51 +2287,111 @@ const m_option_type_t m_option_type_afmt = {
|
||||
|
||||
#include "audio/chmap.h"
|
||||
|
||||
static int parse_chmap(struct mp_log *log, const m_option_t *opt,
|
||||
struct bstr name, struct bstr param, void *dst)
|
||||
static int parse_channels(struct mp_log *log, const m_option_t *opt,
|
||||
struct bstr name, struct bstr param, void *dst)
|
||||
{
|
||||
// min>0: at least min channels, min=0: empty ok
|
||||
int min_ch = (opt->flags & M_OPT_MIN) ? opt->min : 1;
|
||||
assert(min_ch >= 0);
|
||||
// see OPT_CHANNELS for semantics.
|
||||
bool limited = opt->min;
|
||||
|
||||
struct m_channels res = {0};
|
||||
|
||||
if (bstr_equals0(param, "help")) {
|
||||
mp_chmap_print_help(log);
|
||||
if (!limited) {
|
||||
mp_info(log, "\nOther values:\n"
|
||||
" auto-safe\n");
|
||||
}
|
||||
return M_OPT_EXIT - 1;
|
||||
}
|
||||
|
||||
if (param.len == 0 && min_ch >= 1)
|
||||
return M_OPT_MISSING_PARAM;
|
||||
|
||||
struct mp_chmap res = {0};
|
||||
if (!mp_chmap_from_str(&res, param)) {
|
||||
mp_err(log, "Error parsing channel layout: %.*s\n", BSTR_P(param));
|
||||
return M_OPT_INVALID;
|
||||
bool auto_safe = bstr_equals0(param, "auto-safe");
|
||||
if (bstr_equals0(param, "auto") || bstr_equals0(param, "empty") || auto_safe) {
|
||||
if (limited) {
|
||||
mp_err(log, "Disallowed parameter.\n");
|
||||
return M_OPT_INVALID;
|
||||
}
|
||||
param.len = 0;
|
||||
res.set = true;
|
||||
res.auto_safe = auto_safe;
|
||||
}
|
||||
|
||||
if (!mp_chmap_is_valid(&res) && !(min_ch == 0 && mp_chmap_is_empty(&res))) {
|
||||
mp_err(log, "Invalid channel layout: %.*s\n", BSTR_P(param));
|
||||
return M_OPT_INVALID;
|
||||
while (param.len) {
|
||||
bstr item;
|
||||
if (limited) {
|
||||
item = param;
|
||||
param.len = 0;
|
||||
} else {
|
||||
bstr_split_tok(param, ",", &item, ¶m);
|
||||
}
|
||||
|
||||
struct mp_chmap map = {0};
|
||||
if (!mp_chmap_from_str(&map, item) || !mp_chmap_is_valid(&map)) {
|
||||
mp_err(log, "Invalid channel layout: %.*s\n", BSTR_P(item));
|
||||
talloc_free(res.chmaps);
|
||||
return M_OPT_INVALID;
|
||||
}
|
||||
|
||||
MP_TARRAY_APPEND(NULL, res.chmaps, res.num_chmaps, map);
|
||||
res.set = true;
|
||||
}
|
||||
|
||||
if (dst)
|
||||
*(struct mp_chmap *)dst = res;
|
||||
if (dst) {
|
||||
*(struct m_channels *)dst = res;
|
||||
} else {
|
||||
talloc_free(res.chmaps);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static char *print_chmap(const m_option_t *opt, const void *val)
|
||||
static char *print_channels(const m_option_t *opt, const void *val)
|
||||
{
|
||||
const struct mp_chmap *chmap = val;
|
||||
return talloc_strdup(NULL, mp_chmap_to_str(chmap));
|
||||
const struct m_channels *ch = val;
|
||||
if (!ch->set)
|
||||
return talloc_strdup(NULL, "");
|
||||
if (ch->auto_safe)
|
||||
return talloc_strdup(NULL, "auto-safe");
|
||||
if (ch->num_chmaps > 0) {
|
||||
char *res = talloc_strdup(NULL, "");
|
||||
for (int n = 0; n < ch->num_chmaps; n++) {
|
||||
if (n > 0)
|
||||
res = talloc_strdup_append(res, ",");
|
||||
res = talloc_strdup_append(res, mp_chmap_to_str(&ch->chmaps[n]));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
return talloc_strdup(NULL, "auto");
|
||||
}
|
||||
|
||||
static void free_channels(void *src)
|
||||
{
|
||||
if (!src)
|
||||
return;
|
||||
|
||||
const m_option_type_t m_option_type_chmap = {
|
||||
struct m_channels *ch = src;
|
||||
talloc_free(ch->chmaps);
|
||||
*ch = (struct m_channels){0};
|
||||
}
|
||||
|
||||
static void copy_channels(const m_option_t *opt, void *dst, const void *src)
|
||||
{
|
||||
if (!(dst && src))
|
||||
return;
|
||||
|
||||
struct m_channels *ch = dst;
|
||||
free_channels(dst);
|
||||
*ch = *(struct m_channels *)src;
|
||||
ch->chmaps =
|
||||
talloc_memdup(NULL, ch->chmaps, sizeof(ch->chmaps[0]) * ch->num_chmaps);
|
||||
}
|
||||
|
||||
const m_option_type_t m_option_type_channels = {
|
||||
.name = "Audio channels or channel map",
|
||||
.size = sizeof(struct mp_chmap),
|
||||
.parse = parse_chmap,
|
||||
.print = print_chmap,
|
||||
.copy = copy_opt,
|
||||
.size = sizeof(struct m_channels),
|
||||
.parse = parse_channels,
|
||||
.print = print_channels,
|
||||
.copy = copy_channels,
|
||||
.free = free_channels,
|
||||
};
|
||||
|
||||
static int parse_timestring(struct bstr str, double *time, char endchar)
|
||||
|
||||
+18
-6
@@ -61,7 +61,7 @@ extern const m_option_type_t m_option_type_afmt;
|
||||
extern const m_option_type_t m_option_type_color;
|
||||
extern const m_option_type_t m_option_type_geometry;
|
||||
extern const m_option_type_t m_option_type_size_box;
|
||||
extern const m_option_type_t m_option_type_chmap;
|
||||
extern const m_option_type_t m_option_type_channels;
|
||||
extern const m_option_type_t m_option_type_node;
|
||||
|
||||
// Used internally by m_config.c
|
||||
@@ -98,6 +98,13 @@ struct m_geometry {
|
||||
void m_geometry_apply(int *xpos, int *ypos, int *widw, int *widh,
|
||||
int scrw, int scrh, struct m_geometry *gm);
|
||||
|
||||
struct m_channels {
|
||||
bool set : 1;
|
||||
bool auto_safe : 1;
|
||||
struct mp_chmap *chmaps;
|
||||
int num_chmaps;
|
||||
};
|
||||
|
||||
struct m_obj_desc {
|
||||
// Name which will be used in the option string
|
||||
const char *name;
|
||||
@@ -218,7 +225,7 @@ union m_option_value {
|
||||
struct m_color color;
|
||||
struct m_geometry geometry;
|
||||
struct m_geometry size_box;
|
||||
struct mp_chmap chmap;
|
||||
struct m_channels channels;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
@@ -333,6 +340,10 @@ struct m_option {
|
||||
|
||||
// Initialize variable to given default before parsing options
|
||||
const void *defval;
|
||||
|
||||
// Print a warning when this option is used (for options with no direct
|
||||
// replacement.)
|
||||
const char *deprecation_message;
|
||||
};
|
||||
|
||||
|
||||
@@ -394,7 +405,7 @@ struct m_option {
|
||||
|
||||
// Dynamic data type.
|
||||
/** This flag indicates that the data is dynamically allocated (m_option::p
|
||||
* points to a pointer). It enables a little hack in the \ref Config wich
|
||||
* points to a pointer). It enables a little hack in the \ref Config which
|
||||
* replaces the initial value of such variables with a dynamic copy in case
|
||||
* the initial value is statically allocated (pretty common with strings).
|
||||
*/
|
||||
@@ -622,9 +633,10 @@ extern const char m_option_path_separator;
|
||||
#define OPT_AUDIOFORMAT(...) \
|
||||
OPT_GENERAL(int, __VA_ARGS__, .type = &m_option_type_afmt)
|
||||
|
||||
#define OPT_CHMAP(...) \
|
||||
OPT_GENERAL(struct mp_chmap, __VA_ARGS__, .type = &m_option_type_chmap)
|
||||
|
||||
// If .min==1, then passing auto is disallowed, but "" is still accepted, and
|
||||
// limit channel list to 1 item.
|
||||
#define OPT_CHANNELS(...) \
|
||||
OPT_GENERAL(struct m_channels, __VA_ARGS__, .type = &m_option_type_channels)
|
||||
|
||||
#define M_CHOICES(choices) \
|
||||
.priv = (void *)&(const struct m_opt_choice_alternatives[]){ \
|
||||
|
||||
+18
-12
@@ -40,7 +40,6 @@
|
||||
#include "video/csputils.h"
|
||||
#include "video/hwdec.h"
|
||||
#include "sub/osd.h"
|
||||
#include "audio/mixer.h"
|
||||
#include "audio/filter/af.h"
|
||||
#include "audio/decode/dec_audio.h"
|
||||
#include "player/core.h"
|
||||
@@ -85,6 +84,7 @@ const struct m_opt_choice_alternatives mp_hwdec_names[] = {
|
||||
{"auto-copy", HWDEC_AUTO_COPY},
|
||||
{"vdpau", HWDEC_VDPAU},
|
||||
{"videotoolbox",HWDEC_VIDEOTOOLBOX},
|
||||
{"videotoolbox-copy",HWDEC_VIDEOTOOLBOX_COPY},
|
||||
{"vaapi", HWDEC_VAAPI},
|
||||
{"vaapi-copy", HWDEC_VAAPI_COPY},
|
||||
{"dxva2", HWDEC_DXVA2},
|
||||
@@ -279,7 +279,7 @@ const m_option_t mp_opts[] = {
|
||||
// force video/audio rate:
|
||||
OPT_DOUBLE("fps", force_fps, CONF_MIN, .min = 0),
|
||||
OPT_INTRANGE("audio-samplerate", force_srate, 0, 1000, 16*48000),
|
||||
OPT_CHMAP("audio-channels", audio_output_channels, CONF_MIN, .min = 0),
|
||||
OPT_CHANNELS("audio-channels", audio_output_channels, 0),
|
||||
OPT_AUDIOFORMAT("audio-format", audio_output_format, 0),
|
||||
OPT_FLAG("audio-normalize-downmix", audio_normalize, 0),
|
||||
OPT_DOUBLE("speed", playback_speed, M_OPT_RANGE | M_OPT_FIXED,
|
||||
@@ -288,7 +288,7 @@ const m_option_t mp_opts[] = {
|
||||
OPT_FLAG("audio-pitch-correction", pitch_correction, 0),
|
||||
|
||||
// set a-v distance
|
||||
OPT_FLOATRANGE("audio-delay", audio_delay, 0, -100.0, 100.0),
|
||||
OPT_FLOAT("audio-delay", audio_delay, 0),
|
||||
|
||||
// ------------------------- codec/vfilter options --------------------
|
||||
|
||||
@@ -342,6 +342,7 @@ const m_option_t mp_opts[] = {
|
||||
OPT_PATHLIST("sub-paths", sub_paths, 0),
|
||||
OPT_PATHLIST("audio-file-paths", audiofile_paths, 0),
|
||||
OPT_STRING_APPEND_LIST("external-file", external_files, M_OPT_FILE),
|
||||
OPT_FLAG("autoload-files", autoload_files, 0),
|
||||
OPT_STRING("sub-codepage", sub_cp, 0),
|
||||
OPT_FLOAT("sub-delay", sub_delay, 0),
|
||||
OPT_FLOAT("sub-fps", sub_fps, 0),
|
||||
@@ -396,6 +397,8 @@ const m_option_t mp_opts[] = {
|
||||
OPT_STRING("audio-device", audio_device, 0),
|
||||
OPT_STRING("audio-client-name", audio_client_name, 0),
|
||||
OPT_FLAG("audio-fallback-to-null", ao_null_fallback, 0),
|
||||
OPT_FLAG("audio-stream-silence", audio_stream_silence, 0),
|
||||
OPT_FLOATRANGE("audio-wait-open", audio_wait_open, 0, 0, 60),
|
||||
OPT_CHOICE("force-window", force_vo, 0,
|
||||
({"no", 0}, {"yes", 1}, {"immediate", 2})),
|
||||
OPT_FLAG("taskbar-progress", vo.taskbar_progress, 0),
|
||||
@@ -410,19 +413,20 @@ const m_option_t mp_opts[] = {
|
||||
({"no", SOFTVOL_NO},
|
||||
{"yes", SOFTVOL_YES},
|
||||
{"auto", SOFTVOL_AUTO})),
|
||||
OPT_FLOATRANGE("softvol-max", softvol_max, 0, 100, 1000),
|
||||
OPT_FLOATRANGE("volume", mixer_init_volume, 0, -1, 1000),
|
||||
OPT_CHOICE("mute", mixer_init_mute, 0,
|
||||
OPT_FLOATRANGE("volume-max", softvol_max, 0, 100, 1000),
|
||||
// values <0 for volume and mute are legacy and ignored
|
||||
OPT_FLOATRANGE("volume", softvol_volume, 0, -1, 1000),
|
||||
OPT_CHOICE("mute", softvol_mute, 0,
|
||||
({"auto", -1},
|
||||
{"no", 0},
|
||||
{"yes", 1})),
|
||||
OPT_STRING("volume-restore-data", mixer_restore_volume_data, 0),
|
||||
OPT_CHOICE("gapless-audio", gapless_audio, 0,
|
||||
({"no", 0},
|
||||
{"yes", 1},
|
||||
{"weak", -1})),
|
||||
OPT_DOUBLE("audio-buffer", audio_buffer, M_OPT_MIN | M_OPT_MAX,
|
||||
.min = 0, .max = 10),
|
||||
OPT_FLOATRANGE("balance", balance, 0, -1, 1),
|
||||
|
||||
OPT_GEOMETRY("geometry", vo.geometry, 0),
|
||||
OPT_SIZE_BOX("autofit", vo.autofit, 0),
|
||||
@@ -469,7 +473,8 @@ const m_option_t mp_opts[] = {
|
||||
OPT_STRING("vo-mmcss-profile", vo.mmcss_profile, M_OPT_FIXED),
|
||||
#endif
|
||||
|
||||
OPT_STRING("heartbeat-cmd", heartbeat_cmd, 0),
|
||||
OPT_STRING("heartbeat-cmd", heartbeat_cmd, 0,
|
||||
.deprecation_message = "use Lua scripting instead"),
|
||||
OPT_FLOAT("heartbeat-interval", heartbeat_interval, CONF_MIN, 0),
|
||||
|
||||
OPT_CHOICE_OR_INT("screen", vo.screen_id, 0, 0, 32,
|
||||
@@ -684,6 +689,7 @@ const m_option_t mp_opts[] = {
|
||||
OPT_REPLACED("ass-use-margins", "sub-use-margins"),
|
||||
OPT_REPLACED("media-title", "force-media-title"),
|
||||
OPT_REPLACED("input-unix-socket", "input-ipc-server"),
|
||||
OPT_REPLACED("softvol-max", "volume-max"),
|
||||
|
||||
{0}
|
||||
};
|
||||
@@ -697,8 +703,8 @@ const struct MPOpts mp_default_opts = {
|
||||
.deinterlace = -1,
|
||||
.softvol = SOFTVOL_AUTO,
|
||||
.softvol_max = 130,
|
||||
.mixer_init_volume = -1,
|
||||
.mixer_init_mute = -1,
|
||||
.softvol_volume = 100,
|
||||
.softvol_mute = 0,
|
||||
.gapless_audio = -1,
|
||||
.audio_buffer = 0.2,
|
||||
.audio_device = "auto",
|
||||
@@ -758,6 +764,7 @@ const struct MPOpts mp_default_opts = {
|
||||
.sync_audio_drop_size = 0.020,
|
||||
.load_config = 1,
|
||||
.position_resume = 1,
|
||||
.autoload_files = 1,
|
||||
.stream_cache = {
|
||||
.size = -1,
|
||||
.def_size = 75000,
|
||||
@@ -802,7 +809,6 @@ const struct MPOpts mp_default_opts = {
|
||||
.sub_visibility = 1,
|
||||
.sub_pos = 100,
|
||||
.sub_speed = 1.0,
|
||||
.audio_output_channels = MP_CHMAP_INIT_STEREO,
|
||||
.audio_output_format = 0, // AF_FORMAT_UNKNOWN
|
||||
.playback_speed = 1.,
|
||||
.pitch_correction = 1,
|
||||
@@ -825,7 +831,7 @@ const struct MPOpts mp_default_opts = {
|
||||
.sub_cp = "auto",
|
||||
.screenshot_template = "mpv-shot%n",
|
||||
|
||||
.hwdec_codecs = "h264,vc1,wmv3,hevc,mpeg2video",
|
||||
.hwdec_codecs = "h264,vc1,wmv3,hevc,mpeg2video,vp9",
|
||||
.videotoolbox_format = IMGFMT_NV12,
|
||||
|
||||
.index_mode = 1,
|
||||
|
||||
+7
-4
@@ -87,11 +87,13 @@ typedef struct MPOpts {
|
||||
char *audio_device;
|
||||
char *audio_client_name;
|
||||
int ao_null_fallback;
|
||||
int audio_stream_silence;
|
||||
float audio_wait_open;
|
||||
int force_vo;
|
||||
int softvol;
|
||||
float mixer_init_volume;
|
||||
int mixer_init_mute;
|
||||
char *mixer_restore_volume_data;
|
||||
float softvol_volume;
|
||||
float balance;
|
||||
int softvol_mute;
|
||||
float softvol_max;
|
||||
int gapless_audio;
|
||||
double audio_buffer;
|
||||
@@ -226,7 +228,7 @@ typedef struct MPOpts {
|
||||
double force_fps;
|
||||
int index_mode;
|
||||
|
||||
struct mp_chmap audio_output_channels;
|
||||
struct m_channels audio_output_channels;
|
||||
int audio_output_format;
|
||||
int audio_normalize;
|
||||
int force_srate;
|
||||
@@ -243,6 +245,7 @@ typedef struct MPOpts {
|
||||
char **sub_paths;
|
||||
char **audiofile_paths;
|
||||
char **external_files;
|
||||
int autoload_files;
|
||||
int sub_auto;
|
||||
int audiofile_auto;
|
||||
int osd_bar_visible;
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
typedef enum
|
||||
{
|
||||
kHIDRemoteModeNone = 0L,
|
||||
kHIDRemoteModeShared, // Share the remote with others - let's you listen to the remote control events as long as noone has an exclusive lock on it
|
||||
kHIDRemoteModeShared, // Share the remote with others - let's you listen to the remote control events as long as no one has an exclusive lock on it
|
||||
// (RECOMMENDED ONLY FOR SPECIAL PURPOSES)
|
||||
|
||||
kHIDRemoteModeExclusive, // Try to acquire an exclusive lock on the remote (NOT RECOMMENDED)
|
||||
@@ -182,7 +182,7 @@ typedef enum
|
||||
- (BOOL)hidRemote:(HIDRemote *)hidRemote // Invoked when new hardware is inspected
|
||||
inspectNewHardwareWithService:(io_service_t)service //
|
||||
prematchResult:(BOOL)prematchResult; // Return YES if HIDRemote should go on with this hardware and try
|
||||
// to use it, or NO if it should not be persued further.
|
||||
// to use it, or NO if it should not be pursued further.
|
||||
|
||||
// Exlusive lock lending
|
||||
- (BOOL)hidRemote:(HIDRemote *)hidRemote
|
||||
|
||||
@@ -293,7 +293,7 @@ static HIDRemote *sHIDRemote = nil;
|
||||
|
||||
}while(0);
|
||||
|
||||
// An error occured. Do necessary clean up.
|
||||
// An error occurred. Do necessary clean up.
|
||||
if (matchDict!=NULL)
|
||||
{
|
||||
CFRelease(matchDict);
|
||||
@@ -1422,7 +1422,7 @@ static HIDRemote *sHIDRemote = nil;
|
||||
[((NSObject <HIDRemoteDelegate> *)[self delegate]) hidRemote:self failedNewHardwareWithError:error];
|
||||
}
|
||||
|
||||
// An error occured or this device is not of interest .. cleanup ..
|
||||
// An error occurred or this device is not of interest .. cleanup ..
|
||||
if (serviceNotification!=0)
|
||||
{
|
||||
IOObjectRelease(serviceNotification);
|
||||
@@ -1615,7 +1615,7 @@ static HIDRemote *sHIDRemote = nil;
|
||||
switch (buttonCode)
|
||||
{
|
||||
case kHIDRemoteButtonCodeIDChanged:
|
||||
// Do nothing, this is handled seperately
|
||||
// Do nothing, this is handled separately
|
||||
break;
|
||||
|
||||
case kHIDRemoteButtonCodeUp:
|
||||
|
||||
+8
-21
@@ -28,15 +28,14 @@
|
||||
|
||||
// Emulate the parts of C11 stdatomic.h needed by mpv.
|
||||
// Still relies on gcc/clang atomic builtins.
|
||||
// The t field is a hack to make the non-atomic fallback macro mess work.
|
||||
|
||||
typedef struct { volatile unsigned long v, t; } atomic_ulong;
|
||||
typedef struct { volatile int v, t; } atomic_int;
|
||||
typedef struct { volatile unsigned int v, t; } atomic_uint;
|
||||
typedef struct { volatile _Bool v, t; } atomic_bool;
|
||||
typedef struct { volatile long long v, t; } atomic_llong;
|
||||
typedef struct { volatile uint_least32_t v, t; } atomic_uint_least32_t;
|
||||
typedef struct { volatile unsigned long long v, t; } atomic_ullong;
|
||||
typedef struct { volatile unsigned long v; } atomic_ulong;
|
||||
typedef struct { volatile int v; } atomic_int;
|
||||
typedef struct { volatile unsigned int v; } atomic_uint;
|
||||
typedef struct { volatile _Bool v; } atomic_bool;
|
||||
typedef struct { volatile long long v; } atomic_llong;
|
||||
typedef struct { volatile uint_least32_t v; } atomic_uint_least32_t;
|
||||
typedef struct { volatile unsigned long long v; } atomic_ullong;
|
||||
|
||||
#define ATOMIC_VAR_INIT(x) \
|
||||
{.v = (x)}
|
||||
@@ -82,19 +81,7 @@ typedef struct { volatile unsigned long long v, t; } atomic_ullong;
|
||||
ok_; })
|
||||
|
||||
#else
|
||||
|
||||
// This is extremely wrong. The build system actually disables code that has
|
||||
// a serious dependency on working atomics, so this is barely ok.
|
||||
#define atomic_load(p) ((p)->v)
|
||||
#define atomic_store(p, val) ((p)->v = (val))
|
||||
#define atomic_fetch_op_(a, b, op) \
|
||||
((a)->t = (a)->v, (a)->v = (a)->v op (b), (a)->t)
|
||||
#define atomic_fetch_add(a, b) atomic_fetch_op_(a, b, +)
|
||||
#define atomic_fetch_and(a, b) atomic_fetch_op_(a, b, &)
|
||||
#define atomic_fetch_or(a, b) atomic_fetch_op_(a, b, |)
|
||||
#define atomic_compare_exchange_strong(p, old, new) \
|
||||
((p)->v == *(old) ? ((p)->v = (new), 1) : (*(old) = (p)->v, 0))
|
||||
|
||||
# error "this should have been a configuration error, report a bug please"
|
||||
#endif /* no atomics */
|
||||
|
||||
#endif /* else HAVE_STDATOMIC */
|
||||
|
||||
@@ -85,6 +85,14 @@ int mp_make_wakeup_pipe(int pipes[2])
|
||||
}
|
||||
#endif
|
||||
|
||||
void mp_flush_wakeup_pipe(int pipe_end)
|
||||
{
|
||||
#ifndef __MINGW32__
|
||||
char buf[100];
|
||||
(void)read(pipe_end, buf, sizeof(buf));
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
bool mp_set_cloexec(int fd);
|
||||
int mp_make_cloexec_pipe(int pipes[2]);
|
||||
int mp_make_wakeup_pipe(int pipes[2]);
|
||||
void mp_flush_wakeup_pipe(int pipe_end);
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <wchar.h>
|
||||
|
||||
@@ -161,7 +161,7 @@ static void terminate_cocoa_application(void)
|
||||
|
||||
- (void)stopPlaybackAndRememberPosition
|
||||
{
|
||||
[self stopMPV:"quit_watch_later"];
|
||||
[self stopMPV:"quit-watch-later"];
|
||||
}
|
||||
|
||||
- (void)stopMPV:(char *)cmd
|
||||
|
||||
+25
-7
@@ -19,13 +19,31 @@
|
||||
|
||||
#include <winver.h>
|
||||
|
||||
1 VERSIONINFO
|
||||
FILEVERSION 2,0,0,0
|
||||
PRODUCTVERSION 2,0,0,0
|
||||
FILEOS VOS__WINDOWS32
|
||||
FILETYPE VFT_APP
|
||||
{
|
||||
}
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 2, 0, 0, 0
|
||||
PRODUCTVERSION 2, 0, 0, 0
|
||||
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
|
||||
FILEFLAGS 0
|
||||
FILEOS VOS__WINDOWS32
|
||||
FILETYPE VFT_APP
|
||||
FILESUBTYPE 0
|
||||
{
|
||||
BLOCK "StringFileInfo" {
|
||||
BLOCK "000004b0" {
|
||||
VALUE "Comments", "mpv is distributed under the terms of the GNU General Public License Version 2 or later."
|
||||
VALUE "CompanyName", "mpv"
|
||||
VALUE "FileDescription", "mpv"
|
||||
VALUE "FileVersion", "2.0.0.0"
|
||||
VALUE "LegalCopyright", "(C) 2000-2016 mpv/mplayer2/MPlayer"
|
||||
VALUE "OriginalFilename", "mpv.exe"
|
||||
VALUE "ProductName", "mpv"
|
||||
VALUE "ProductVersion", "2.0.0.0"
|
||||
}
|
||||
}
|
||||
BLOCK "VarFileInfo" {
|
||||
VALUE "Translation", 0, 1200
|
||||
}
|
||||
}
|
||||
|
||||
IDI_ICON1 ICON DISCARDABLE "etc/mpv-icon.ico"
|
||||
|
||||
|
||||
@@ -45,6 +45,10 @@ static void *run_subprocess(void *ptr)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void mp_devnull(void *ctx, char *data, size_t size)
|
||||
{
|
||||
}
|
||||
|
||||
void mp_subprocess_detached(struct mp_log *log, char **args)
|
||||
{
|
||||
struct subprocess_args *p = talloc_zero(NULL, struct subprocess_args);
|
||||
|
||||
@@ -24,6 +24,8 @@ struct mp_cancel;
|
||||
|
||||
typedef void (*subprocess_read_cb)(void *ctx, char *data, size_t size);
|
||||
|
||||
void mp_devnull(void *ctx, char *data, size_t size);
|
||||
|
||||
// Start a subprocess. Uses callbacks to read from stdout and stderr.
|
||||
int mp_subprocess(char **args, struct mp_cancel *cancel, void *ctx,
|
||||
subprocess_read_cb on_stdout, subprocess_read_cb on_stderr,
|
||||
|
||||
+120
-9
@@ -31,7 +31,6 @@
|
||||
#include "common/common.h"
|
||||
#include "osdep/timer.h"
|
||||
|
||||
#include "audio/mixer.h"
|
||||
#include "audio/audio.h"
|
||||
#include "audio/audio_buffer.h"
|
||||
#include "audio/decode/dec_audio.h"
|
||||
@@ -120,6 +119,69 @@ fail:
|
||||
mp_notify(mpctx, MP_EVENT_CHANGE_ALL, NULL);
|
||||
}
|
||||
|
||||
// Called when opts->softvol_volume or opts->softvol_mute were changed.
|
||||
void audio_update_volume(struct MPContext *mpctx)
|
||||
{
|
||||
struct MPOpts *opts = mpctx->opts;
|
||||
struct ao_chain *ao_c = mpctx->ao_chain;
|
||||
if (!ao_c || ao_c->af->initialized < 1)
|
||||
return;
|
||||
|
||||
float gain = MPMAX(opts->softvol_volume / 100.0, 0);
|
||||
if (opts->softvol_mute == 1)
|
||||
gain = 0.0;
|
||||
|
||||
if (!af_control_any_rev(ao_c->af, AF_CONTROL_SET_VOLUME, &gain)) {
|
||||
if (gain == 1.0)
|
||||
return;
|
||||
MP_VERBOSE(mpctx, "Inserting volume filter.\n");
|
||||
if (!(af_add(ao_c->af, "volume", "softvol", NULL)
|
||||
&& af_control_any_rev(ao_c->af, AF_CONTROL_SET_VOLUME, &gain)))
|
||||
MP_ERR(mpctx, "No volume control available.\n");
|
||||
}
|
||||
}
|
||||
|
||||
/* NOTE: Currently the balance code is seriously buggy: it always changes
|
||||
* the af_pan mapping between the first two input channels and first two
|
||||
* output channels to particular values. These values make sense for an
|
||||
* af_pan instance that was automatically inserted for balance control
|
||||
* only and is otherwise an identity transform, but if the filter was
|
||||
* there for another reason, then ignoring and overriding the original
|
||||
* values is completely wrong.
|
||||
*/
|
||||
void audio_update_balance(struct MPContext *mpctx)
|
||||
{
|
||||
struct MPOpts *opts = mpctx->opts;
|
||||
struct ao_chain *ao_c = mpctx->ao_chain;
|
||||
if (!ao_c || ao_c->af->initialized < 1)
|
||||
return;
|
||||
|
||||
float val = opts->balance;
|
||||
|
||||
if (af_control_any_rev(ao_c->af, AF_CONTROL_SET_PAN_BALANCE, &val))
|
||||
return;
|
||||
|
||||
if (val == 0)
|
||||
return;
|
||||
|
||||
struct af_instance *af_pan_balance;
|
||||
if (!(af_pan_balance = af_add(ao_c->af, "pan", "autopan", NULL))) {
|
||||
MP_ERR(mpctx, "No balance control available.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* make all other channels pass through since by default pan blocks all */
|
||||
for (int i = 2; i < AF_NCH; i++) {
|
||||
float level[AF_NCH] = {0};
|
||||
level[i] = 1.f;
|
||||
af_control_ext_t arg_ext = { .ch = i, .arg = level };
|
||||
af_pan_balance->control(af_pan_balance, AF_CONTROL_SET_PAN_LEVEL,
|
||||
&arg_ext);
|
||||
}
|
||||
|
||||
af_pan_balance->control(af_pan_balance, AF_CONTROL_SET_PAN_BALANCE, &val);
|
||||
}
|
||||
|
||||
static int recreate_audio_filters(struct MPContext *mpctx)
|
||||
{
|
||||
assert(mpctx->ao_chain);
|
||||
@@ -132,7 +194,11 @@ static int recreate_audio_filters(struct MPContext *mpctx)
|
||||
if (afs->initialized < 1 && af_init(afs) < 0)
|
||||
goto fail;
|
||||
|
||||
mixer_reinit_audio(mpctx->mixer, mpctx->ao, afs);
|
||||
if (mpctx->opts->softvol == SOFTVOL_NO)
|
||||
MP_ERR(mpctx, "--softvol=no is not supported anymore.\n");
|
||||
|
||||
audio_update_volume(mpctx);
|
||||
audio_update_balance(mpctx);
|
||||
|
||||
mp_notify(mpctx, MPV_EVENT_AUDIO_RECONFIG, NULL);
|
||||
|
||||
@@ -202,6 +268,7 @@ void reset_audio_state(struct MPContext *mpctx)
|
||||
mpctx->delay = 0;
|
||||
mpctx->audio_drop_throttle = 0;
|
||||
mpctx->audio_stat_start = 0;
|
||||
mpctx->audio_allow_second_chance_seek = false;
|
||||
}
|
||||
|
||||
void uninit_audio_out(struct MPContext *mpctx)
|
||||
@@ -210,7 +277,6 @@ void uninit_audio_out(struct MPContext *mpctx)
|
||||
// Note: with gapless_audio, stop_play is not correctly set
|
||||
if (mpctx->opts->gapless_audio || mpctx->stop_play == AT_END_OF_FILE)
|
||||
ao_drain(mpctx->ao);
|
||||
mixer_uninit_audio(mpctx->mixer);
|
||||
ao_uninit(mpctx->ao);
|
||||
|
||||
mp_notify(mpctx, MPV_EVENT_AUDIO_RECONFIG, NULL);
|
||||
@@ -243,7 +309,6 @@ static void ao_chain_uninit(struct ao_chain *ao_c)
|
||||
void uninit_audio_chain(struct MPContext *mpctx)
|
||||
{
|
||||
if (mpctx->ao_chain) {
|
||||
mixer_uninit_audio(mpctx->mixer);
|
||||
ao_chain_uninit(mpctx->ao_chain);
|
||||
mpctx->ao_chain = NULL;
|
||||
|
||||
@@ -289,7 +354,10 @@ static void reinit_audio_filters_and_output(struct MPContext *mpctx)
|
||||
} else if (af_fmt_is_pcm(in_format.format)) {
|
||||
afs->output.rate = opts->force_srate;
|
||||
mp_audio_set_format(&afs->output, opts->audio_output_format);
|
||||
mp_audio_set_channels(&afs->output, &opts->audio_output_channels);
|
||||
if (opts->audio_output_channels.num_chmaps == 1) {
|
||||
mp_audio_set_channels(&afs->output,
|
||||
&opts->audio_output_channels.chmaps[0]);
|
||||
}
|
||||
}
|
||||
|
||||
// filter input format: same as codec's output format:
|
||||
@@ -304,15 +372,25 @@ static void reinit_audio_filters_and_output(struct MPContext *mpctx)
|
||||
}
|
||||
|
||||
if (!mpctx->ao) {
|
||||
int ao_flags = 0;
|
||||
bool spdif_fallback = af_fmt_is_spdif(afs->output.format) &&
|
||||
ao_c->spdif_passthrough;
|
||||
bool ao_null_fallback = opts->ao_null_fallback && !spdif_fallback;
|
||||
|
||||
mp_chmap_remove_useless_channels(&afs->output.channels,
|
||||
&opts->audio_output_channels);
|
||||
if (opts->ao_null_fallback && !spdif_fallback)
|
||||
ao_flags |= AO_INIT_NULL_FALLBACK;
|
||||
|
||||
if (!opts->audio_output_channels.set || opts->audio_output_channels.auto_safe)
|
||||
ao_flags |= AO_INIT_SAFE_MULTICHANNEL_ONLY;
|
||||
|
||||
if (opts->audio_stream_silence)
|
||||
ao_flags |= AO_INIT_STREAM_SILENCE;
|
||||
|
||||
mp_chmap_sel_list(&afs->output.channels, opts->audio_output_channels.chmaps,
|
||||
opts->audio_output_channels.num_chmaps);
|
||||
|
||||
mp_audio_set_channels(&afs->output, &afs->output.channels);
|
||||
|
||||
mpctx->ao = ao_init_best(mpctx->global, ao_null_fallback, mpctx->input,
|
||||
mpctx->ao = ao_init_best(mpctx->global, ao_flags, mpctx->input,
|
||||
mpctx->encode_lavc_ctx, afs->output.rate,
|
||||
afs->output.format, afs->output.channels);
|
||||
ao_c->ao = mpctx->ao;
|
||||
@@ -363,6 +441,9 @@ static void reinit_audio_filters_and_output(struct MPContext *mpctx)
|
||||
mp_audio_config_to_str(&fmt));
|
||||
MP_VERBOSE(mpctx, "AO: Description: %s\n", ao_get_description(mpctx->ao));
|
||||
update_window_title(mpctx, true);
|
||||
|
||||
ao_c->ao_resume_time =
|
||||
opts->audio_wait_open > 0 ? mp_time_sec() + opts->audio_wait_open : 0;
|
||||
}
|
||||
|
||||
if (recreate_audio_filters(mpctx) < 0)
|
||||
@@ -596,6 +677,9 @@ static bool get_sync_samples(struct MPContext *mpctx, int *skip)
|
||||
sync_pts = mpctx->video_pts - opts->audio_delay;
|
||||
} else if (mpctx->hrseek_active) {
|
||||
sync_pts = mpctx->hrseek_pts;
|
||||
} else {
|
||||
// If audio-only is enabled mid-stream during playback, sync accordingly.
|
||||
sync_pts = mpctx->playback_pts;
|
||||
}
|
||||
if (sync_pts == MP_NOPTS_VALUE) {
|
||||
mpctx->audio_status = STATUS_FILLING;
|
||||
@@ -611,6 +695,27 @@ static bool get_sync_samples(struct MPContext *mpctx, int *skip)
|
||||
}
|
||||
ptsdiff = MPCLAMP(ptsdiff, -3600, 3600);
|
||||
|
||||
// Heuristic: if audio is "too far" ahead, and one of them is a separate
|
||||
// track, allow a refresh seek to the correct position to fix it.
|
||||
if (ptsdiff > 0.2 && mpctx->audio_allow_second_chance_seek && sync_to_video) {
|
||||
struct ao_chain *ao_c = mpctx->ao_chain;
|
||||
if (ao_c && ao_c->track && mpctx->vo_chain && mpctx->vo_chain->track &&
|
||||
ao_c->track->demuxer != mpctx->vo_chain->track->demuxer)
|
||||
{
|
||||
struct track *track = ao_c->track;
|
||||
double pts = mpctx->video_pts;
|
||||
if (pts != MP_NOPTS_VALUE)
|
||||
pts += get_track_seek_offset(mpctx, track);
|
||||
// (disable it first to make it take any effect)
|
||||
demuxer_select_track(track->demuxer, track->stream, pts, false);
|
||||
demuxer_select_track(track->demuxer, track->stream, pts, true);
|
||||
reset_audio_state(mpctx);
|
||||
MP_VERBOSE(mpctx, "retrying audio seek\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
mpctx->audio_allow_second_chance_seek = false;
|
||||
|
||||
int align = af_format_sample_alignment(out_format.format);
|
||||
*skip = (int)(-ptsdiff * play_samplerate) / align * align;
|
||||
return true;
|
||||
@@ -760,6 +865,12 @@ void fill_audio_out_buffers(struct MPContext *mpctx)
|
||||
return; // try again next iteration
|
||||
}
|
||||
|
||||
if (ao_c->ao_resume_time > mp_time_sec()) {
|
||||
double remaining = ao_c->ao_resume_time - mp_time_sec();
|
||||
mpctx->sleeptime = MPMIN(mpctx->sleeptime, remaining);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mpctx->vo_chain && ao_c->pts_reset) {
|
||||
MP_VERBOSE(mpctx, "Reset playback due to audio timestamp reset.\n");
|
||||
reset_playback_state(mpctx);
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "common/global.h"
|
||||
#include "common/msg.h"
|
||||
#include "common/msg_control.h"
|
||||
#include "common/global.h"
|
||||
#include "input/input.h"
|
||||
#include "input/cmd_list.h"
|
||||
#include "misc/ctype.h"
|
||||
@@ -62,9 +63,13 @@ struct mp_client_api {
|
||||
pthread_mutex_t lock;
|
||||
|
||||
// -- protected by lock
|
||||
|
||||
struct mpv_handle **clients;
|
||||
int num_clients;
|
||||
uint64_t event_masks; // combined events of all clients, or 0 if unknown
|
||||
|
||||
struct mp_custom_protocol *custom_protocols;
|
||||
int num_custom_protocols;
|
||||
};
|
||||
|
||||
struct observe_property {
|
||||
@@ -1592,6 +1597,7 @@ static const char *const err_table[] = {
|
||||
[-MPV_ERROR_UNKNOWN_FORMAT] = "unrecognized file format",
|
||||
[-MPV_ERROR_UNSUPPORTED] = "not supported",
|
||||
[-MPV_ERROR_NOT_IMPLEMENTED] = "operation not implemented",
|
||||
[-MPV_ERROR_GENERIC] = "something happened",
|
||||
};
|
||||
|
||||
const char *mpv_error_string(int error)
|
||||
@@ -1722,3 +1728,59 @@ void *mpv_get_sub_api(mpv_handle *ctx, mpv_sub_api sub_api)
|
||||
unlock_core(ctx);
|
||||
return res;
|
||||
}
|
||||
|
||||
struct mp_custom_protocol {
|
||||
char *protocol;
|
||||
void *user_data;
|
||||
mpv_stream_cb_open_ro_fn open_fn;
|
||||
};
|
||||
|
||||
int mpv_stream_cb_add_ro(mpv_handle *ctx, const char *protocol, void *user_data,
|
||||
mpv_stream_cb_open_ro_fn open_fn)
|
||||
{
|
||||
if (!open_fn)
|
||||
return MPV_ERROR_INVALID_PARAMETER;
|
||||
|
||||
struct mp_client_api *clients = ctx->clients;
|
||||
int r = 0;
|
||||
pthread_mutex_lock(&clients->lock);
|
||||
for (int n = 0; n < clients->num_custom_protocols; n++) {
|
||||
struct mp_custom_protocol *proto = &clients->custom_protocols[n];
|
||||
if (strcmp(proto->protocol, protocol) == 0) {
|
||||
r = MPV_ERROR_INVALID_PARAMETER;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (stream_has_proto(protocol))
|
||||
r = MPV_ERROR_INVALID_PARAMETER;
|
||||
if (r >= 0) {
|
||||
struct mp_custom_protocol proto = {
|
||||
.protocol = talloc_strdup(clients, protocol),
|
||||
.user_data = user_data,
|
||||
.open_fn = open_fn,
|
||||
};
|
||||
MP_TARRAY_APPEND(clients, clients->custom_protocols,
|
||||
clients->num_custom_protocols, proto);
|
||||
}
|
||||
pthread_mutex_unlock(&clients->lock);
|
||||
return r;
|
||||
}
|
||||
|
||||
bool mp_streamcb_lookup(struct mpv_global *g, const char *protocol,
|
||||
void **out_user_data, mpv_stream_cb_open_ro_fn *out_fn)
|
||||
{
|
||||
struct mp_client_api *clients = g->client_api;
|
||||
bool found = false;
|
||||
pthread_mutex_lock(&clients->lock);
|
||||
for (int n = 0; n < clients->num_custom_protocols; n++) {
|
||||
struct mp_custom_protocol *proto = &clients->custom_protocols[n];
|
||||
if (strcmp(proto->protocol, protocol) == 0) {
|
||||
*out_user_data = proto->user_data;
|
||||
*out_fn = proto->open_fn;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&clients->lock);
|
||||
return found;
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "libmpv/client.h"
|
||||
#include "libmpv/stream_cb.h"
|
||||
|
||||
struct MPContext;
|
||||
struct mpv_handle;
|
||||
@@ -46,4 +47,7 @@ struct mpv_opengl_cb_context *mp_opengl_create(struct mpv_global *g,
|
||||
struct mp_client_api *client_api);
|
||||
void kill_video(struct mp_client_api *client_api);
|
||||
|
||||
bool mp_streamcb_lookup(struct mpv_global *g, const char *protocol,
|
||||
void **out_user_data, mpv_stream_cb_open_ro_fn *out_fn);
|
||||
|
||||
#endif
|
||||
+235
-194
@@ -54,12 +54,12 @@
|
||||
#include "video/decode/vd.h"
|
||||
#include "video/out/vo.h"
|
||||
#include "video/csputils.h"
|
||||
#include "audio/mixer.h"
|
||||
#include "audio/audio_buffer.h"
|
||||
#include "audio/out/ao.h"
|
||||
#include "audio/filter/af.h"
|
||||
#include "video/decode/dec_video.h"
|
||||
#include "audio/decode/dec_audio.h"
|
||||
#include "video/out/bitmap_packer.h"
|
||||
#include "options/path.h"
|
||||
#include "screenshot.h"
|
||||
|
||||
@@ -88,7 +88,8 @@ struct command_ctx {
|
||||
// One of these is in use by the OSD; the other one exists so that the
|
||||
// bitmap list can be manipulated without additional synchronization.
|
||||
struct sub_bitmaps overlay_osd[2];
|
||||
struct sub_bitmaps *overlay_osd_current;
|
||||
int overlay_osd_current;
|
||||
struct bitmap_packer *overlay_packer;
|
||||
|
||||
struct hook_handler **hooks;
|
||||
int num_hooks;
|
||||
@@ -98,9 +99,8 @@ struct command_ctx {
|
||||
};
|
||||
|
||||
struct overlay {
|
||||
void *map_start;
|
||||
size_t map_size;
|
||||
struct sub_bitmap osd;
|
||||
struct mp_image *source;
|
||||
int x, y;
|
||||
};
|
||||
|
||||
struct hook_handler {
|
||||
@@ -219,7 +219,7 @@ static void mp_hook_add(struct MPContext *mpctx, char *client, char *name,
|
||||
qsort(cmd->hooks, cmd->num_hooks, sizeof(cmd->hooks[0]), compare_hook);
|
||||
}
|
||||
|
||||
// Call before a seek, in order to allow revert_seek to undo the seek.
|
||||
// Call before a seek, in order to allow revert-seek to undo the seek.
|
||||
static void mark_seek(struct MPContext *mpctx)
|
||||
{
|
||||
struct command_ctx *cmd = mpctx->command_ctx;
|
||||
@@ -346,7 +346,19 @@ static int mp_property_filename(void *ctx, struct m_property *prop,
|
||||
if (mp_is_url(bstr0(filename)))
|
||||
mp_url_unescape_inplace(filename);
|
||||
char *f = (char *)mp_basename(filename);
|
||||
int r = m_property_strdup_ro(action, arg, f[0] ? f : filename);
|
||||
if (!f[0])
|
||||
f = filename;
|
||||
if (action == M_PROPERTY_KEY_ACTION) {
|
||||
struct m_property_action_arg *ka = arg;
|
||||
if (strcmp(ka->key, "no-ext") == 0) {
|
||||
action = ka->action;
|
||||
arg = ka->arg;
|
||||
bstr root;
|
||||
if (mp_splitext(f, &root))
|
||||
f = bstrto0(filename, root);
|
||||
}
|
||||
}
|
||||
int r = m_property_strdup_ro(action, arg, f);
|
||||
talloc_free(filename);
|
||||
return r;
|
||||
}
|
||||
@@ -1560,7 +1572,8 @@ static int mp_property_mixer_active(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
return m_property_flag_ro(action, arg, mixer_audio_initialized(mpctx->mixer));
|
||||
struct ao_chain *ao_c = mpctx->ao_chain;
|
||||
return m_property_flag_ro(action, arg, ao_c && ao_c->af->initialized > 0);
|
||||
}
|
||||
|
||||
/// Volume (RW)
|
||||
@@ -1568,44 +1581,29 @@ static int mp_property_volume(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
struct MPOpts *opts = mpctx->opts;
|
||||
|
||||
switch (action) {
|
||||
case M_PROPERTY_GET:
|
||||
mixer_getbothvolume(mpctx->mixer, arg);
|
||||
return M_PROPERTY_OK;
|
||||
case M_PROPERTY_GET_TYPE:
|
||||
*(struct m_option *)arg = (struct m_option){
|
||||
.type = CONF_TYPE_FLOAT,
|
||||
.flags = M_OPT_RANGE,
|
||||
.min = 0,
|
||||
.max = mixer_getmaxvolume(mpctx->mixer),
|
||||
.max = opts->softvol_max,
|
||||
};
|
||||
return M_PROPERTY_OK;
|
||||
case M_PROPERTY_GET_NEUTRAL:
|
||||
*(float *)arg = 100;
|
||||
return M_PROPERTY_OK;
|
||||
case M_PROPERTY_PRINT: {
|
||||
float val;
|
||||
mixer_getbothvolume(mpctx->mixer, &val);
|
||||
*(char **)arg = talloc_asprintf(NULL, "%i", (int)val);
|
||||
case M_PROPERTY_PRINT:
|
||||
*(char **)arg = talloc_asprintf(NULL, "%i", (int)opts->softvol_volume);
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
case M_PROPERTY_SET:
|
||||
mixer_setvolume(mpctx->mixer, *(float *) arg, *(float *) arg);
|
||||
return M_PROPERTY_OK;
|
||||
case M_PROPERTY_SWITCH: {
|
||||
struct m_property_switch_arg *sarg = arg;
|
||||
mixer_addvolume(mpctx->mixer, sarg->inc);
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
}
|
||||
return M_PROPERTY_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
static int mp_property_volume_max(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
return m_property_float_ro(action, arg, mixer_getmaxvolume(mpctx->mixer));
|
||||
int r = mp_property_generic_option(mpctx, prop, action, arg);
|
||||
if (action == M_PROPERTY_SET)
|
||||
audio_update_volume(mpctx);
|
||||
return r;
|
||||
}
|
||||
|
||||
/// Mute (RW)
|
||||
@@ -1613,13 +1611,83 @@ static int mp_property_mute(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
|
||||
if (action == M_PROPERTY_GET_TYPE) {
|
||||
*(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_FLAG};
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
|
||||
int r = mp_property_generic_option(mpctx, prop, action, arg);
|
||||
if (action == M_PROPERTY_SET)
|
||||
audio_update_volume(mpctx);
|
||||
return r;
|
||||
}
|
||||
|
||||
static int mp_property_ao_volume(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
struct ao *ao = mpctx->ao;
|
||||
if (!ao)
|
||||
return M_PROPERTY_NOT_IMPLEMENTED;
|
||||
|
||||
switch (action) {
|
||||
case M_PROPERTY_SET:
|
||||
mixer_setmute(mpctx->mixer, *(int *) arg);
|
||||
case M_PROPERTY_SET: {
|
||||
float value = *(float *)arg;
|
||||
ao_control_vol_t vol = {value, value};
|
||||
if (ao_control(ao, AOCONTROL_SET_VOLUME, &vol) != CONTROL_OK)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
return M_PROPERTY_OK;
|
||||
case M_PROPERTY_GET:
|
||||
*(int *)arg = mixer_getmute(mpctx->mixer);
|
||||
}
|
||||
case M_PROPERTY_GET: {
|
||||
ao_control_vol_t vol = {0};
|
||||
if (ao_control(ao, AOCONTROL_GET_VOLUME, &vol) != CONTROL_OK)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
*(float *)arg = (vol.left + vol.right) / 2.0f;
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
case M_PROPERTY_GET_TYPE:
|
||||
*(struct m_option *)arg = (struct m_option){
|
||||
.type = CONF_TYPE_FLOAT,
|
||||
.flags = M_OPT_RANGE,
|
||||
.min = 0,
|
||||
.max = 100,
|
||||
};
|
||||
return M_PROPERTY_OK;
|
||||
case M_PROPERTY_PRINT: {
|
||||
ao_control_vol_t vol = {0};
|
||||
if (ao_control(ao, AOCONTROL_GET_VOLUME, &vol) != CONTROL_OK)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
*(char **)arg = talloc_asprintf(NULL, "%.f", (vol.left + vol.right) / 2.0f);
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
}
|
||||
return M_PROPERTY_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
|
||||
static int mp_property_ao_mute(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
struct ao *ao = mpctx->ao;
|
||||
if (!ao)
|
||||
return M_PROPERTY_NOT_IMPLEMENTED;
|
||||
|
||||
switch (action) {
|
||||
case M_PROPERTY_SET: {
|
||||
bool value = *(int *)arg;
|
||||
if (ao_control(ao, AOCONTROL_SET_MUTE, &value) != CONTROL_OK)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
case M_PROPERTY_GET: {
|
||||
bool value = false;
|
||||
if (ao_control(ao, AOCONTROL_GET_MUTE, &value) != CONTROL_OK)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
*(int *)arg = value;
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
case M_PROPERTY_GET_TYPE:
|
||||
*(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_FLAG};
|
||||
return M_PROPERTY_OK;
|
||||
@@ -1627,22 +1695,6 @@ static int mp_property_mute(void *ctx, struct m_property *prop,
|
||||
return M_PROPERTY_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
static int mp_property_volrestore(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
switch (action) {
|
||||
case M_PROPERTY_GET: {
|
||||
char *s = mixer_get_volume_restore_data(mpctx->mixer);
|
||||
*(char **)arg = s;
|
||||
return s ? M_PROPERTY_OK : M_PROPERTY_UNAVAILABLE;
|
||||
}
|
||||
case M_PROPERTY_SET:
|
||||
return M_PROPERTY_NOT_IMPLEMENTED;
|
||||
}
|
||||
return mp_property_generic_option(mpctx, prop, action, arg);
|
||||
}
|
||||
|
||||
static int get_device_entry(int item, int action, void *arg, void *ctx)
|
||||
{
|
||||
struct ao_device_list *list = ctx;
|
||||
@@ -1801,23 +1853,10 @@ static int mp_property_balance(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
float bal;
|
||||
|
||||
switch (action) {
|
||||
case M_PROPERTY_GET:
|
||||
mixer_getbalance(mpctx->mixer, arg);
|
||||
return M_PROPERTY_OK;
|
||||
case M_PROPERTY_GET_TYPE:
|
||||
*(struct m_option *)arg = (struct m_option){
|
||||
.type = CONF_TYPE_FLOAT,
|
||||
.flags = M_OPT_RANGE,
|
||||
.min = -1,
|
||||
.max = 1,
|
||||
};
|
||||
return M_PROPERTY_OK;
|
||||
case M_PROPERTY_PRINT: {
|
||||
if (action == M_PROPERTY_PRINT) {
|
||||
char **str = arg;
|
||||
mixer_getbalance(mpctx->mixer, &bal);
|
||||
float bal = mpctx->opts->balance;
|
||||
if (bal == 0.f)
|
||||
*str = talloc_strdup(NULL, "center");
|
||||
else if (bal == -1.f)
|
||||
@@ -1831,11 +1870,11 @@ static int mp_property_balance(void *ctx, struct m_property *prop,
|
||||
}
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
case M_PROPERTY_SET:
|
||||
mixer_setbalance(mpctx->mixer, *(float *)arg);
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
return M_PROPERTY_NOT_IMPLEMENTED;
|
||||
|
||||
int r = mp_property_generic_option(mpctx, prop, action, arg);
|
||||
if (action == M_PROPERTY_SET)
|
||||
audio_update_balance(mpctx);
|
||||
return r;
|
||||
}
|
||||
|
||||
static struct track* track_next(struct MPContext *mpctx, enum stream_type type,
|
||||
@@ -1971,6 +2010,10 @@ static int get_track_entry(int item, int action, void *arg, void *ctx)
|
||||
if (track->d_audio)
|
||||
decoder_desc = track->d_audio->decoder_desc;
|
||||
|
||||
bool has_rg = track->stream->codec->replaygain_data;
|
||||
struct replaygain_data rg = has_rg ? *track->stream->codec->replaygain_data
|
||||
: (struct replaygain_data){0};
|
||||
|
||||
struct m_sub_property props[] = {
|
||||
{"id", SUB_PROP_INT(track->user_tid)},
|
||||
{"type", SUB_PROP_STR(stream_type_name(track->type)),
|
||||
@@ -2004,6 +2047,14 @@ static int get_track_entry(int item, int action, void *arg, void *ctx)
|
||||
{"demux-samplerate", SUB_PROP_INT(p.samplerate),
|
||||
.unavailable = !p.samplerate},
|
||||
{"demux-fps", SUB_PROP_DOUBLE(p.fps), .unavailable = p.fps <= 0},
|
||||
{"replaygain-track-peak", SUB_PROP_FLOAT(rg.track_peak),
|
||||
.unavailable = !has_rg},
|
||||
{"replaygain-track-gain", SUB_PROP_FLOAT(rg.track_gain),
|
||||
.unavailable = !has_rg},
|
||||
{"replaygain-album-peak", SUB_PROP_FLOAT(rg.album_peak),
|
||||
.unavailable = !has_rg},
|
||||
{"replaygain-album-gain", SUB_PROP_FLOAT(rg.album_gain),
|
||||
.unavailable = !has_rg},
|
||||
{0}
|
||||
};
|
||||
|
||||
@@ -2257,88 +2308,6 @@ static int mp_property_detected_hwdec(void *ctx, struct m_property *prop,
|
||||
return M_PROPERTY_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
#define VF_DEINTERLACE_LABEL "deinterlace"
|
||||
|
||||
static bool probe_deint_filter(struct MPContext *mpctx, const char *filt)
|
||||
{
|
||||
char filter[80];
|
||||
// add a label so that removing the filter is easier
|
||||
snprintf(filter, sizeof(filter), "@%s:%s", VF_DEINTERLACE_LABEL, filt);
|
||||
return edit_filters(mpctx, mp_null_log, STREAM_VIDEO, "pre", filter) >= 0;
|
||||
}
|
||||
|
||||
static bool check_output_format(struct MPContext *mpctx, int imgfmt)
|
||||
{
|
||||
struct vo_chain *vo_c = mpctx->vo_chain;
|
||||
if (!vo_c)
|
||||
return false;
|
||||
return vo_c->vf->allowed_output_formats[imgfmt - IMGFMT_START];
|
||||
}
|
||||
|
||||
static int probe_deint_filters(struct MPContext *mpctx)
|
||||
{
|
||||
if (check_output_format(mpctx, IMGFMT_VDPAU)) {
|
||||
char filter[80] = "vdpaupp:deint=yes";
|
||||
int pref = 0;
|
||||
vo_control(mpctx->video_out, VOCTRL_GET_PREF_DEINT, &pref);
|
||||
pref = pref < 0 ? -pref : pref;
|
||||
if (pref > 0 && pref <= 4) {
|
||||
const char *types[] =
|
||||
{"", "first-field", "bob", "temporal", "temporal-spatial"};
|
||||
mp_snprintf_cat(filter, sizeof(filter), ":deint-mode=%s",
|
||||
types[pref]);
|
||||
}
|
||||
|
||||
probe_deint_filter(mpctx, filter);
|
||||
return 0;
|
||||
}
|
||||
if (check_output_format(mpctx, IMGFMT_VAAPI) &&
|
||||
probe_deint_filter(mpctx, "vavpp"))
|
||||
return 0;
|
||||
if ((check_output_format(mpctx, IMGFMT_D3D11VA) ||
|
||||
check_output_format(mpctx, IMGFMT_D3D11NV12)) &&
|
||||
probe_deint_filter(mpctx, "d3d11vpp"))
|
||||
return 0;
|
||||
if (probe_deint_filter(mpctx, "yadif"))
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int get_deinterlacing(struct MPContext *mpctx)
|
||||
{
|
||||
struct vo_chain *vo_c = mpctx->vo_chain;
|
||||
int enabled = 0;
|
||||
if (video_vf_vo_control(vo_c, VFCTRL_GET_DEINTERLACE, &enabled) != CONTROL_OK)
|
||||
enabled = -1;
|
||||
if (enabled < 0) {
|
||||
// vf_lavfi doesn't support VFCTRL_GET_DEINTERLACE
|
||||
if (vf_find_by_label(vo_c->vf, VF_DEINTERLACE_LABEL))
|
||||
enabled = 1;
|
||||
}
|
||||
return enabled;
|
||||
}
|
||||
|
||||
void remove_deint_filter(struct MPContext *mpctx)
|
||||
{
|
||||
edit_filters(mpctx, mp_null_log, STREAM_VIDEO, "del", "@" VF_DEINTERLACE_LABEL);
|
||||
}
|
||||
|
||||
void set_deinterlacing(struct MPContext *mpctx, bool enable)
|
||||
{
|
||||
struct vo_chain *vo_c = mpctx->vo_chain;
|
||||
if (vf_find_by_label(vo_c->vf, VF_DEINTERLACE_LABEL)) {
|
||||
if (!enable)
|
||||
remove_deint_filter(mpctx);
|
||||
} else {
|
||||
if ((get_deinterlacing(mpctx) > 0) != enable) {
|
||||
int arg = enable;
|
||||
if (video_vf_vo_control(vo_c, VFCTRL_SET_DEINTERLACE, &arg) != CONTROL_OK)
|
||||
probe_deint_filters(mpctx);
|
||||
}
|
||||
}
|
||||
mpctx->opts->deinterlace = get_deinterlacing(mpctx) > 0;
|
||||
}
|
||||
|
||||
static int mp_property_deinterlace(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
@@ -2568,13 +2537,15 @@ static int property_imgparams(struct mp_image_params p, int action, void *arg)
|
||||
{"aspect", SUB_PROP_FLOAT(d_w / (double)d_h)},
|
||||
{"par", SUB_PROP_FLOAT(p.p_w / (double)p.p_h)},
|
||||
{"colormatrix",
|
||||
SUB_PROP_STR(m_opt_choice_str(mp_csp_names, p.colorspace))},
|
||||
SUB_PROP_STR(m_opt_choice_str(mp_csp_names, p.color.space))},
|
||||
{"colorlevels",
|
||||
SUB_PROP_STR(m_opt_choice_str(mp_csp_levels_names, p.colorlevels))},
|
||||
SUB_PROP_STR(m_opt_choice_str(mp_csp_levels_names, p.color.levels))},
|
||||
{"primaries",
|
||||
SUB_PROP_STR(m_opt_choice_str(mp_csp_prim_names, p.primaries))},
|
||||
SUB_PROP_STR(m_opt_choice_str(mp_csp_prim_names, p.color.primaries))},
|
||||
{"gamma",
|
||||
SUB_PROP_STR(m_opt_choice_str(mp_csp_trc_names, p.gamma))},
|
||||
SUB_PROP_STR(m_opt_choice_str(mp_csp_trc_names, p.color.gamma))},
|
||||
{"nom-peak", SUB_PROP_FLOAT(p.color.nom_peak)},
|
||||
{"sig-peak", SUB_PROP_FLOAT(p.color.sig_peak)},
|
||||
{"chroma-location",
|
||||
SUB_PROP_STR(m_opt_choice_str(mp_chroma_names, p.chroma_location))},
|
||||
{"stereo-in",
|
||||
@@ -3751,8 +3722,10 @@ static const struct m_property mp_properties[] = {
|
||||
// Audio
|
||||
{"mixer-active", mp_property_mixer_active},
|
||||
{"volume", mp_property_volume},
|
||||
{"volume-max", mp_property_volume_max},
|
||||
{"volume-max", mp_property_generic_option},
|
||||
{"mute", mp_property_mute},
|
||||
{"ao-volume", mp_property_ao_volume},
|
||||
{"ao-mute", mp_property_ao_mute},
|
||||
{"audio-delay", mp_property_audio_delay},
|
||||
{"audio-codec-name", mp_property_audio_codec_name},
|
||||
{"audio-codec", mp_property_audio_codec},
|
||||
@@ -3762,7 +3735,6 @@ static const struct m_property mp_properties[] = {
|
||||
M_PROPERTY_DEPRECATED_ALIAS("audio-channels", "audio-params/channel-count"),
|
||||
{"aid", mp_property_audio},
|
||||
{"balance", mp_property_balance},
|
||||
{"volume-restore-data", mp_property_volrestore},
|
||||
{"audio-device", mp_property_audio_device},
|
||||
{"audio-device-list", mp_property_audio_devices},
|
||||
{"current-ao", mp_property_ao},
|
||||
@@ -3940,7 +3912,7 @@ static const char *const *const mp_event_property_change[] = {
|
||||
"colormatrix-output-range", "colormatrix-primaries", "video-aspect"),
|
||||
E(MPV_EVENT_AUDIO_RECONFIG, "audio-format", "audio-codec", "audio-bitrate",
|
||||
"samplerate", "channels", "audio", "volume", "mute", "balance",
|
||||
"volume-restore-data", "current-ao", "audio-codec-name", "audio-params",
|
||||
"current-ao", "audio-codec-name", "audio-params",
|
||||
"audio-out-params", "volume-max", "mixer-active"),
|
||||
E(MPV_EVENT_SEEK, "seeking", "core-idle", "eof-reached"),
|
||||
E(MPV_EVENT_PLAYBACK_RESTART, "seeking", "core-idle", "eof-reached"),
|
||||
@@ -4104,7 +4076,11 @@ static const struct property_osd_display {
|
||||
{ "volume", "Volume",
|
||||
.msg = "Volume: ${?volume:${volume}% ${?mute==yes:(Muted)}}${!volume:${volume}}",
|
||||
.osd_progbar = OSD_VOLUME },
|
||||
{ "ao-volume", "AO Volume",
|
||||
.msg = "AO Volume: ${?ao-volume:${ao-volume}% ${?ao-mute==yes:(Muted)}}${!ao-volume:${ao-volume}}",
|
||||
.osd_progbar = OSD_VOLUME },
|
||||
{ "mute", "Mute" },
|
||||
{ "ao-mute", "AO Mute" },
|
||||
{ "audio-delay", "A-V delay" },
|
||||
{ "audio", "Audio" },
|
||||
{ "balance", "Balance", .osd_progbar = OSD_BALANCE },
|
||||
@@ -4314,20 +4290,85 @@ static int edit_filters_osd(struct MPContext *mpctx, enum stream_type mediatype,
|
||||
static void recreate_overlays(struct MPContext *mpctx)
|
||||
{
|
||||
struct command_ctx *cmd = mpctx->command_ctx;
|
||||
struct sub_bitmaps *new = &cmd->overlay_osd[0];
|
||||
if (new == cmd->overlay_osd_current)
|
||||
new += 1; // pick the unused one
|
||||
int overlay_next = !cmd->overlay_osd_current;
|
||||
struct sub_bitmaps *new = &cmd->overlay_osd[overlay_next];
|
||||
new->format = SUBBITMAP_RGBA;
|
||||
new->change_id = 1;
|
||||
// overlay array can have unused entries, but parts list must be "packed"
|
||||
|
||||
bool valid = false;
|
||||
|
||||
new->num_parts = 0;
|
||||
for (int n = 0; n < cmd->num_overlays; n++) {
|
||||
struct overlay *o = &cmd->overlays[n];
|
||||
if (o->osd.bitmap)
|
||||
MP_TARRAY_APPEND(cmd, new->parts, new->num_parts, o->osd);
|
||||
if (o->source) {
|
||||
struct mp_image *s = o->source;
|
||||
struct sub_bitmap b = {
|
||||
.bitmap = s->planes[0],
|
||||
.stride = s->stride[0],
|
||||
.w = s->w, .dw = s->w,
|
||||
.h = s->h, .dh = s->h,
|
||||
.x = o->x,
|
||||
.y = o->y,
|
||||
};
|
||||
MP_TARRAY_APPEND(cmd, new->parts, new->num_parts, b);
|
||||
}
|
||||
}
|
||||
cmd->overlay_osd_current = new;
|
||||
osd_set_external2(mpctx->osd, cmd->overlay_osd_current);
|
||||
|
||||
if (!cmd->overlay_packer)
|
||||
cmd->overlay_packer = talloc_zero(cmd, struct bitmap_packer);
|
||||
|
||||
cmd->overlay_packer->padding = 1; // assume bilinear scaling
|
||||
packer_set_size(cmd->overlay_packer, new->num_parts);
|
||||
|
||||
for (int n = 0; n < new->num_parts; n++)
|
||||
cmd->overlay_packer->in[n] = (struct pos){new->parts[n].w, new->parts[n].h};
|
||||
|
||||
if (packer_pack(cmd->overlay_packer) < 0 || new->num_parts == 0)
|
||||
goto done;
|
||||
|
||||
struct pos bb[2];
|
||||
packer_get_bb(cmd->overlay_packer, bb);
|
||||
|
||||
new->packed_w = bb[1].x;
|
||||
new->packed_h = bb[1].y;
|
||||
|
||||
if (!new->packed || new->packed->w < new->packed_w ||
|
||||
new->packed->h < new->packed_h)
|
||||
{
|
||||
talloc_free(new->packed);
|
||||
new->packed = mp_image_alloc(IMGFMT_BGRA, cmd->overlay_packer->w,
|
||||
cmd->overlay_packer->h);
|
||||
if (!new->packed)
|
||||
goto done;
|
||||
}
|
||||
|
||||
// clear padding
|
||||
mp_image_clear(new->packed, 0, 0, new->packed->w, new->packed->h);
|
||||
|
||||
for (int n = 0; n < new->num_parts; n++) {
|
||||
struct sub_bitmap *b = &new->parts[n];
|
||||
struct pos pos = cmd->overlay_packer->result[n];
|
||||
|
||||
int stride = new->packed->stride[0];
|
||||
void *pdata = (uint8_t *)new->packed->planes[0] + pos.y * stride + pos.x * 4;
|
||||
memcpy_pic(pdata, b->bitmap, b->w * 4, b->h, stride, b->stride);
|
||||
|
||||
b->bitmap = pdata;
|
||||
b->stride = stride;
|
||||
|
||||
b->src_x = pos.x;
|
||||
b->src_y = pos.y;
|
||||
}
|
||||
|
||||
valid = true;
|
||||
done:
|
||||
if (!valid) {
|
||||
new->format = SUBBITMAP_EMPTY;
|
||||
new->num_parts = 0;
|
||||
}
|
||||
|
||||
osd_set_external2(mpctx->osd, new);
|
||||
cmd->overlay_osd_current = overlay_next;
|
||||
}
|
||||
|
||||
// Set overlay with the given ID to the contents as described by "new".
|
||||
@@ -4342,17 +4383,11 @@ static void replace_overlay(struct MPContext *mpctx, int id, struct overlay *new
|
||||
}
|
||||
|
||||
struct overlay *ptr = &cmd->overlays[id];
|
||||
struct overlay old = *ptr;
|
||||
|
||||
if (!ptr->osd.bitmap && !new->osd.bitmap)
|
||||
return; // don't need to recreate or unmap
|
||||
|
||||
talloc_free(ptr->source);
|
||||
*ptr = *new;
|
||||
recreate_overlays(mpctx);
|
||||
|
||||
// Do this afterwards, so we never unmap while the OSD is using it.
|
||||
if (old.map_start && old.map_size)
|
||||
munmap(old.map_start, old.map_size);
|
||||
recreate_overlays(mpctx);
|
||||
}
|
||||
|
||||
static int overlay_add(struct MPContext *mpctx, int id, int x, int y,
|
||||
@@ -4361,25 +4396,24 @@ static int overlay_add(struct MPContext *mpctx, int id, int x, int y,
|
||||
{
|
||||
int r = -1;
|
||||
if (strcmp(fmt, "bgra") != 0) {
|
||||
MP_ERR(mpctx, "overlay_add: unsupported OSD format '%s'\n", fmt);
|
||||
MP_ERR(mpctx, "overlay-add: unsupported OSD format '%s'\n", fmt);
|
||||
goto error;
|
||||
}
|
||||
if (id < 0 || id >= 64) { // arbitrary upper limit
|
||||
MP_ERR(mpctx, "overlay_add: invalid id %d\n", id);
|
||||
MP_ERR(mpctx, "overlay-add: invalid id %d\n", id);
|
||||
goto error;
|
||||
}
|
||||
if (w < 0 || h < 0 || stride < w * 4 || (stride % 4)) {
|
||||
MP_ERR(mpctx, "overlay_add: inconsistent parameters\n");
|
||||
if (w <= 0 || h <= 0 || stride < w * 4 || (stride % 4)) {
|
||||
MP_ERR(mpctx, "overlay-add: inconsistent parameters\n");
|
||||
goto error;
|
||||
}
|
||||
struct overlay overlay = {
|
||||
.osd = {
|
||||
.stride = stride,
|
||||
.x = x, .y = y,
|
||||
.w = w, .h = h,
|
||||
.dw = w, .dh = h,
|
||||
},
|
||||
.source = mp_image_alloc(IMGFMT_BGRA, w, h),
|
||||
.x = x,
|
||||
.y = y,
|
||||
};
|
||||
if (!overlay.source)
|
||||
goto error;
|
||||
int fd = -1;
|
||||
bool close_fd = true;
|
||||
void *p = NULL;
|
||||
@@ -4398,21 +4432,25 @@ static int overlay_add(struct MPContext *mpctx, int id, int x, int y,
|
||||
} else {
|
||||
fd = open(file, O_RDONLY | O_BINARY | O_CLOEXEC);
|
||||
}
|
||||
int map_size = 0;
|
||||
if (fd >= 0) {
|
||||
overlay.map_size = offset + h * stride;
|
||||
void *m = mmap(NULL, overlay.map_size, PROT_READ, MAP_SHARED, fd, 0);
|
||||
map_size = offset + h * stride;
|
||||
void *m = mmap(NULL, map_size, PROT_READ, MAP_SHARED, fd, 0);
|
||||
if (close_fd)
|
||||
close(fd);
|
||||
if (m && m != MAP_FAILED) {
|
||||
overlay.map_start = m;
|
||||
if (m && m != MAP_FAILED)
|
||||
p = m;
|
||||
}
|
||||
}
|
||||
if (!p) {
|
||||
MP_ERR(mpctx, "overlay_add: could not open or map '%s'\n", file);
|
||||
MP_ERR(mpctx, "overlay-add: could not open or map '%s'\n", file);
|
||||
talloc_free(overlay.source);
|
||||
goto error;
|
||||
}
|
||||
overlay.osd.bitmap = (char *)p + offset;
|
||||
memcpy_pic(overlay.source->planes[0], (char *)p + offset, w * 4, h,
|
||||
overlay.source->stride[0], stride);
|
||||
if (map_size)
|
||||
munmap(p, map_size);
|
||||
|
||||
replace_overlay(mpctx, id, &overlay);
|
||||
r = 0;
|
||||
error:
|
||||
@@ -4434,6 +4472,8 @@ static void overlay_uninit(struct MPContext *mpctx)
|
||||
for (int id = 0; id < cmd->num_overlays; id++)
|
||||
overlay_remove(mpctx, id);
|
||||
osd_set_external2(mpctx->osd, NULL);
|
||||
for (int n = 0; n < 2; n++)
|
||||
mp_image_unrefp(&cmd->overlay_osd[n].packed);
|
||||
}
|
||||
|
||||
struct cycle_counter {
|
||||
@@ -4958,6 +4998,7 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
|
||||
mpctx->add_osd_seek_info |=
|
||||
(msg_osd ? OSD_SEEK_INFO_TEXT : 0) |
|
||||
(bar_osd ? OSD_SEEK_INFO_BAR : 0);
|
||||
mpctx->osd_force_update = true;
|
||||
break;
|
||||
|
||||
case MP_CMD_TV_LAST_CHANNEL: {
|
||||
|
||||
@@ -59,7 +59,4 @@ void mp_hook_run(struct MPContext *mpctx, char *client, char *type);
|
||||
|
||||
void handle_ab_loop(struct MPContext *mpctx);
|
||||
|
||||
void remove_deint_filter(struct MPContext *mpctx);
|
||||
void set_deinterlacing(struct MPContext *mpctx, bool enable);
|
||||
|
||||
#endif /* MPLAYER_COMMAND_H */
|
||||
@@ -208,7 +208,8 @@ static const char *const backup_properties[] = {
|
||||
"options/speed",
|
||||
"options/edition",
|
||||
"options/pause",
|
||||
"volume-restore-data",
|
||||
"volume",
|
||||
"mute",
|
||||
"options/audio-delay",
|
||||
//"balance",
|
||||
"options/fullscreen",
|
||||
|
||||
+17
-1
@@ -185,6 +185,7 @@ struct ao_chain {
|
||||
struct af_stream *af;
|
||||
struct ao *ao;
|
||||
struct mp_audio_buffer *ao_buffer;
|
||||
double ao_resume_time;
|
||||
|
||||
// 1-element input frame queue.
|
||||
struct mp_audio *input_frame;
|
||||
@@ -287,7 +288,6 @@ typedef struct MPContext {
|
||||
|
||||
struct lavfi *lavfi;
|
||||
|
||||
struct mixer *mixer;
|
||||
struct ao *ao;
|
||||
struct mp_audio *ao_decoder_fmt; // for weak gapless audio check
|
||||
struct ao_chain *ao_chain;
|
||||
@@ -383,6 +383,10 @@ typedef struct MPContext {
|
||||
bool immediate; // disable seek delay logic
|
||||
} seek;
|
||||
|
||||
// Allow audio to issue a second seek if audio is too far ahead (for non-hr
|
||||
// seeks with external audio tracks).
|
||||
bool audio_allow_second_chance_seek;
|
||||
|
||||
/* Heuristic for relative chapter seeks: keep track which chapter
|
||||
* the user wanted to go to, even if we aren't exactly within the
|
||||
* boundaries of that chapter due to an inaccurate seek. */
|
||||
@@ -429,6 +433,8 @@ void uninit_audio_out(struct MPContext *mpctx);
|
||||
void uninit_audio_chain(struct MPContext *mpctx);
|
||||
int init_audio_decoder(struct MPContext *mpctx, struct track *track);
|
||||
void reinit_audio_chain_src(struct MPContext *mpctx, struct lavfi_pad *src);
|
||||
void audio_update_volume(struct MPContext *mpctx);
|
||||
void audio_update_balance(struct MPContext *mpctx);
|
||||
|
||||
// configfiles.c
|
||||
void mp_parse_cfgfiles(struct MPContext *mpctx);
|
||||
@@ -485,6 +491,7 @@ 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);
|
||||
struct mpv_global *create_sub_global(struct MPContext *mpctx);
|
||||
double get_track_seek_offset(struct MPContext *mpctx, struct track *track);
|
||||
|
||||
// osd.c
|
||||
void set_osd_bar(struct MPContext *mpctx, int type,
|
||||
@@ -555,5 +562,14 @@ void uninit_video_out(struct MPContext *mpctx);
|
||||
void uninit_video_chain(struct MPContext *mpctx);
|
||||
double calc_average_frame_duration(struct MPContext *mpctx);
|
||||
int init_video_decoder(struct MPContext *mpctx, struct track *track);
|
||||
int get_deinterlacing(struct MPContext *mpctx);
|
||||
void set_deinterlacing(struct MPContext *mpctx, bool enable);
|
||||
|
||||
// Values of MPOpts.softvol
|
||||
enum {
|
||||
SOFTVOL_NO = 0,
|
||||
SOFTVOL_YES = 1,
|
||||
SOFTVOL_AUTO = 2,
|
||||
};
|
||||
|
||||
#endif /* MPLAYER_MP_CORE_H */
|
||||
+11
-24
@@ -42,7 +42,6 @@
|
||||
#include "common/encode.h"
|
||||
#include "input/input.h"
|
||||
|
||||
#include "audio/mixer.h"
|
||||
#include "audio/audio.h"
|
||||
#include "audio/audio_buffer.h"
|
||||
#include "audio/decode/dec_audio.h"
|
||||
@@ -195,23 +194,10 @@ void reselect_demux_stream(struct MPContext *mpctx, struct track *track)
|
||||
{
|
||||
if (!track->stream)
|
||||
return;
|
||||
demuxer_select_track(track->demuxer, track->stream, track->selected);
|
||||
// External files may need an explicit seek to the correct position, if
|
||||
// they were not implicitly advanced during playback.
|
||||
if (track->selected && track->demuxer != mpctx->demuxer) {
|
||||
bool position_ok = false;
|
||||
for (int n = 0; n < demux_get_num_stream(track->demuxer); n++) {
|
||||
struct sh_stream *stream = demux_get_stream(track->demuxer, n);
|
||||
if (stream != track->stream && stream->type != STREAM_SUB)
|
||||
position_ok |= demux_stream_is_selected(stream);
|
||||
}
|
||||
if (!position_ok) {
|
||||
double pts = get_current_time(mpctx);
|
||||
if (pts == MP_NOPTS_VALUE)
|
||||
pts = 0;
|
||||
demux_seek(track->demuxer, pts, 0);
|
||||
}
|
||||
}
|
||||
double pts = get_current_time(mpctx);
|
||||
if (pts != MP_NOPTS_VALUE)
|
||||
pts += get_track_seek_offset(mpctx, track);
|
||||
demuxer_select_track(track->demuxer, track->stream, pts, track->selected);
|
||||
}
|
||||
|
||||
// Called from the demuxer thread if a new packet is available.
|
||||
@@ -266,7 +252,7 @@ static struct track *add_stream_track(struct MPContext *mpctx,
|
||||
};
|
||||
MP_TARRAY_APPEND(mpctx, mpctx->tracks, mpctx->num_tracks, track);
|
||||
|
||||
demuxer_select_track(track->demuxer, stream, false);
|
||||
demuxer_select_track(track->demuxer, stream, MP_NOPTS_VALUE, false);
|
||||
|
||||
mp_notify(mpctx, MPV_EVENT_TRACKS_CHANGED, NULL);
|
||||
|
||||
@@ -310,6 +296,8 @@ static int match_lang(char **langs, char *lang)
|
||||
static bool compare_track(struct track *t1, struct track *t2, char **langs,
|
||||
struct MPOpts *opts)
|
||||
{
|
||||
if (!opts->autoload_files && t1->is_external != t2->is_external)
|
||||
return !t1->is_external;
|
||||
bool ext1 = t1->is_external && !t1->no_default;
|
||||
bool ext2 = t2->is_external && !t2->no_default;
|
||||
if (ext1 != ext2)
|
||||
@@ -368,6 +356,8 @@ struct track *select_default_track(struct MPContext *mpctx, int order,
|
||||
pick = NULL;
|
||||
if (pick && pick->attached_picture && !mpctx->opts->audio_display)
|
||||
pick = NULL;
|
||||
if (pick && !opts->autoload_files && pick->is_external)
|
||||
pick = NULL;
|
||||
return pick;
|
||||
}
|
||||
|
||||
@@ -467,9 +457,6 @@ void mp_switch_track_n(struct MPContext *mpctx, int order, enum stream_type type
|
||||
reselect_demux_stream(mpctx, current);
|
||||
}
|
||||
|
||||
if (track && track->demuxer == mpctx->demuxer)
|
||||
demux_set_enable_refresh_seeks(mpctx->demuxer, true);
|
||||
|
||||
mpctx->current_track[order][type] = track;
|
||||
|
||||
if (track) {
|
||||
@@ -477,8 +464,6 @@ void mp_switch_track_n(struct MPContext *mpctx, int order, enum stream_type type
|
||||
reselect_demux_stream(mpctx, track);
|
||||
}
|
||||
|
||||
demux_set_enable_refresh_seeks(mpctx->demuxer, false);
|
||||
|
||||
if (type == STREAM_VIDEO && order == 0) {
|
||||
reinit_video_chain(mpctx);
|
||||
} else if (type == STREAM_AUDIO && order == 0) {
|
||||
@@ -624,6 +609,8 @@ void autoload_external_files(struct MPContext *mpctx)
|
||||
{
|
||||
if (mpctx->opts->sub_auto < 0 && mpctx->opts->audiofile_auto < 0)
|
||||
return;
|
||||
if (!mpctx->opts->autoload_files)
|
||||
return;
|
||||
|
||||
void *tmp = talloc_new(NULL);
|
||||
char *base_filename = mpctx->filename;
|
||||
|
||||
@@ -42,8 +42,8 @@ function mp.input_disable_section(section)
|
||||
mp.commandv("disable-section", section)
|
||||
end
|
||||
|
||||
-- For dispatching script_binding. This is sent as:
|
||||
-- script_message_to $script_name $binding_name $keystate
|
||||
-- For dispatching script-binding. This is sent as:
|
||||
-- script-message-to $script_name $binding_name $keystate
|
||||
-- The array is indexed by $binding_name, and has functions like this as value:
|
||||
-- fn($binding_name, $keystate)
|
||||
local dispatch_key_bindings = {}
|
||||
@@ -412,7 +412,7 @@ mp.register_event("shutdown", function() mp.keep_running = false end)
|
||||
mp.register_event("client-message", message_dispatch)
|
||||
mp.register_event("property-change", property_change)
|
||||
|
||||
-- sent by "script_binding"
|
||||
-- sent by "script-binding"
|
||||
mp.register_script_message("key-binding", dispatch_key_binding)
|
||||
|
||||
mp.msg = {
|
||||
|
||||
+4
-4
@@ -1383,7 +1383,7 @@ function osc_init()
|
||||
ne.content = "\238\132\144"
|
||||
ne.visible = have_pl
|
||||
ne.eventresponder["mouse_btn0_up"] =
|
||||
function () mp.commandv("playlist_prev", "weak") end
|
||||
function () mp.commandv("playlist-prev", "weak") end
|
||||
ne.eventresponder["shift+mouse_btn0_up"] =
|
||||
function () show_message(mp.get_property_osd("playlist"), 3) end
|
||||
|
||||
@@ -1393,7 +1393,7 @@ function osc_init()
|
||||
ne.content = "\238\132\129"
|
||||
ne.visible = have_pl
|
||||
ne.eventresponder["mouse_btn0_up"] =
|
||||
function () mp.commandv("playlist_next", "weak") end
|
||||
function () mp.commandv("playlist-next", "weak") end
|
||||
ne.eventresponder["shift+mouse_btn0_up"] =
|
||||
function () show_message(mp.get_property_osd("playlist"), 3) end
|
||||
|
||||
@@ -1421,7 +1421,7 @@ function osc_init()
|
||||
ne.eventresponder["mouse_btn0_down"] =
|
||||
function () mp.commandv("seek", -5, "relative", "keyframes") end
|
||||
ne.eventresponder["shift+mouse_btn0_down"] =
|
||||
function () mp.commandv("frame_back_step") end
|
||||
function () mp.commandv("frame-back-step") end
|
||||
ne.eventresponder["mouse_btn2_down"] =
|
||||
function () mp.commandv("seek", -30, "relative", "keyframes") end
|
||||
|
||||
@@ -1433,7 +1433,7 @@ function osc_init()
|
||||
ne.eventresponder["mouse_btn0_down"] =
|
||||
function () mp.commandv("seek", 10, "relative", "keyframes") end
|
||||
ne.eventresponder["shift+mouse_btn0_down"] =
|
||||
function () mp.commandv("frame_step") end
|
||||
function () mp.commandv("frame-step") end
|
||||
ne.eventresponder["mouse_btn2_down"] =
|
||||
function () mp.commandv("seek", 60, "relative", "keyframes") end
|
||||
|
||||
|
||||
@@ -222,6 +222,10 @@ mp.add_hook("on_load", 10, function ()
|
||||
|
||||
-- DASH?
|
||||
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
|
||||
end
|
||||
|
||||
-- video url
|
||||
streamurl = json["requested_formats"][1].url
|
||||
|
||||
Loaded 100 of 198 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user