Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c4e4d9cbba | ||
|
|
fa53c3fc7b | ||
|
|
c61f76829f | ||
|
|
30e77e0fc5 | ||
|
|
328a0f9c80 | ||
|
|
4f2104961e | ||
|
|
c2ef2bcb90 | ||
|
|
86229e8714 | ||
|
|
295fdeb086 | ||
|
|
909c4d7711 | ||
|
|
0f83f1e4b3 | ||
|
|
9c83e8483e | ||
|
|
e0e58e18c0 | ||
|
|
6f9eb960c7 | ||
|
|
a4b21b5a13 | ||
|
|
799458a5b1 | ||
|
|
c774f2c3bb | ||
|
|
9e6091a5b1 | ||
|
|
74c2146092 | ||
|
|
7af2cb08e2 | ||
|
|
155ecb2c14 | ||
|
|
2a2dc4d73c | ||
|
|
d855be5ec1 | ||
|
|
141728832b | ||
|
|
a8b364cad3 | ||
|
|
c282cd679c | ||
|
|
1f815558b8 | ||
|
|
a0de98871b | ||
|
|
3711327430 | ||
|
|
7d523cd47a | ||
|
|
224abe9e23 | ||
|
|
c6d54cccb5 | ||
|
|
081368dabf | ||
|
|
3b5b2144c3 | ||
|
|
a80166f22a | ||
|
|
126b7c4739 | ||
|
|
f8b842cdf3 | ||
|
|
ad9ea546d3 | ||
|
|
c63d929261 | ||
|
|
595c4cf872 | ||
|
|
4ebed102f0 | ||
|
|
371b52e377 | ||
|
|
d8152b7003 | ||
|
|
6a9cdfa771 | ||
|
|
399e746275 | ||
|
|
929c4a5529 | ||
|
|
38903486fb | ||
|
|
c56b237b3c | ||
|
|
63e401ccc9 | ||
|
|
35cc1830a3 | ||
|
|
f4a9c9424f | ||
|
|
d9da0c9ce9 | ||
|
|
5f3f36a800 | ||
|
|
12c316d95e | ||
|
|
cf997ff691 | ||
|
|
1560f74dde | ||
|
|
9b9d453748 | ||
|
|
cb9b59858d | ||
|
|
3f85092ffe | ||
|
|
f4634e404f | ||
|
|
dbc00297cc | ||
|
|
1e14d65db8 | ||
|
|
57829ed95a | ||
|
|
2f69e8f93c | ||
|
|
6b063b3767 | ||
|
|
a5191c1479 | ||
|
|
cfc5a65494 | ||
|
|
307b8925ee | ||
|
|
2090a35c57 | ||
|
|
2746910515 | ||
|
|
40dce4048f | ||
|
|
465bd38dc6 | ||
|
|
5992ff785c | ||
|
|
b6d8694062 | ||
|
|
7b8ab2005e | ||
|
|
4912fef753 | ||
|
|
c9c9919b2d | ||
|
|
b1c76e6a59 | ||
|
|
58c91c02e2 | ||
|
|
5c1c8b5fab | ||
|
|
85f54ce5d0 | ||
|
|
c473688c5f | ||
|
|
413bd22527 | ||
|
|
ccf8c42a42 | ||
|
|
4759f61e73 | ||
|
|
1a346ce11b | ||
|
|
891a9727c6 | ||
|
|
2e133e9dde | ||
|
|
f5c19f29af | ||
|
|
ca048e8d6d | ||
|
|
a2f4391ca1 | ||
|
|
4971ad9e17 | ||
|
|
3d94938246 | ||
|
|
d627c579ea | ||
|
|
752dd2ef76 | ||
|
|
562e42f826 | ||
|
|
ad3d8ce5c6 | ||
|
|
777bffd307 | ||
|
|
7f839323cb | ||
|
|
cb4b28e336 | ||
|
|
35be53108e | ||
|
|
9f4d44f8c7 | ||
|
|
d60940a080 | ||
|
|
dab7602a89 | ||
|
|
37210f0cbc | ||
|
|
f24b8cbd47 | ||
|
|
b706198207 | ||
|
|
e213e20c2d | ||
|
|
2ade5c8477 | ||
|
|
15a191f920 | ||
|
|
899822c001 | ||
|
|
a9db58cc3b | ||
|
|
21c4c50ac6 | ||
|
|
86493730b2 | ||
|
|
87e635e843 | ||
|
|
b0759abe9c | ||
|
|
ebd4bf9792 | ||
|
|
ab4c857ff6 | ||
|
|
e3f4491a24 | ||
|
|
12ee2e64be | ||
|
|
ead565afb3 | ||
|
|
9f80936ddc | ||
|
|
6a8dad4c3a | ||
|
|
3d710630a4 | ||
|
|
f83e28d893 | ||
|
|
3f66dd82b1 | ||
|
|
c4c95f0dde | ||
|
|
60c6307a97 | ||
|
|
99f237beb9 | ||
|
|
6cf54d6bc0 | ||
|
|
c6ce2c6b43 | ||
|
|
7f92344138 | ||
|
|
d56a1112d2 | ||
|
|
b1292c044a | ||
|
|
18fb4bff6f | ||
|
|
7e60fb2c97 | ||
|
|
8c8af64820 | ||
|
|
3ee6992656 | ||
|
|
9a7f332571 | ||
|
|
fadf87f2a7 | ||
|
|
62c7a372ec | ||
|
|
0692da3007 | ||
|
|
0fcb96139d | ||
|
|
5f6b5feac6 | ||
|
|
e2a4fde6de | ||
|
|
01401b5f5b | ||
|
|
f794eca255 | ||
|
|
ad2a461a09 | ||
|
|
2e931b34f4 | ||
|
|
ade8b6214d | ||
|
|
78e957a2bd | ||
|
|
9eb75631c7 | ||
|
|
0c8a419775 | ||
|
|
885c6e5b07 | ||
|
|
44a7649e79 | ||
|
|
fded4de8a7 | ||
|
|
f20575f3dd | ||
|
|
1936cfdb09 | ||
|
|
84c2dc882d | ||
|
|
d72718ca65 | ||
|
|
8a73b02952 | ||
|
|
b4174ab4e9 | ||
|
|
ce5f0f1a7a | ||
|
|
e02146b0c0 | ||
|
|
804468b1ac | ||
|
|
d8455f34c1 | ||
|
|
b0877e5454 | ||
|
|
c926927c59 | ||
|
|
9af1ff9817 | ||
|
|
ca34fd569d | ||
|
|
4bb2da8204 | ||
|
|
6d7eb8346a | ||
|
|
cd99121185 | ||
|
|
392159337f | ||
|
|
fffd7e7a3a | ||
|
|
59781d3cb3 | ||
|
|
4a6cb85993 | ||
|
|
74a3d29f21 | ||
|
|
07acf5cb9a | ||
|
|
8e1e814166 | ||
|
|
da21670880 | ||
|
|
2cac69f221 | ||
|
|
99cca56a99 | ||
|
|
676457256a | ||
|
|
7368d8f353 | ||
|
|
2c9e0c0a29 | ||
|
|
8d3259a391 | ||
|
|
faf561c4ec | ||
|
|
8c54b56f43 | ||
|
|
a44ffb729b | ||
|
|
947a791992 | ||
|
|
42f82c5ccf | ||
|
|
1a3a52ea25 | ||
|
|
77d5aa56b3 | ||
|
|
1f795b6172 | ||
|
|
22a06b0e81 | ||
|
|
f551da2cfb | ||
|
|
ac06ba0aec | ||
|
|
99dc774dbb | ||
|
|
83d44a4f6a | ||
|
|
6609f68691 | ||
|
|
e2beeffb92 | ||
|
|
704c24776a | ||
|
|
cc32aa436e | ||
|
|
57f149e26a | ||
|
|
a77fec7547 | ||
|
|
18c8d60b54 | ||
|
|
1eb4e90161 | ||
|
|
f3e515c790 | ||
|
|
0547b2a637 | ||
|
|
906448243c | ||
|
|
1ea7b386a9 | ||
|
|
a82f2e2792 | ||
|
|
e0231926df | ||
|
|
2c20c6f892 | ||
|
|
bde22281f7 | ||
|
|
697a7b7d19 | ||
|
|
422502f524 | ||
|
|
34cbc9057c | ||
|
|
36a2dd5ace | ||
|
|
6cec24e980 | ||
|
|
9b2b95ee52 | ||
|
|
47d972102e | ||
|
|
0cf7316c47 | ||
|
|
3a6572d206 | ||
|
|
8ab8fa8960 | ||
|
|
295622bab1 | ||
|
|
8def7ccb4f | ||
|
|
f88a86a936 | ||
|
|
1783e8cade | ||
|
|
c7d6b21789 | ||
|
|
8d4e6498c3 | ||
|
|
9984f4fffd | ||
|
|
75a62c7a64 | ||
|
|
73eaadf6c3 | ||
|
|
292f6f1904 | ||
|
|
5a2947c9d0 | ||
|
|
fdea35a71b | ||
|
|
8f6eb196cd | ||
|
|
3b6e063599 | ||
|
|
015218f8c8 | ||
|
|
00d1d6d807 | ||
|
|
49c3cbfb4e | ||
|
|
925becfb15 | ||
|
|
2cebc52065 | ||
|
|
34e504bf93 | ||
|
|
f0649ce841 | ||
|
|
65af6a55b7 | ||
|
|
1326d3f0b8 | ||
|
|
b062b54626 | ||
|
|
9bee56cc67 | ||
|
|
9fb0180eb2 | ||
|
|
4f8af1a518 | ||
|
|
62b6048468 | ||
|
|
b6363c8100 | ||
|
|
db4cec8a6c | ||
|
|
4de6f4f616 | ||
|
|
7c53bb4bb1 | ||
|
|
115709e3c1 | ||
|
|
1e8dbf8212 | ||
|
|
1bfaab13ca | ||
|
|
083235d91f | ||
|
|
bc4e115b30 | ||
|
|
807763c85e | ||
|
|
69fa6c4b73 | ||
|
|
3141381c2e | ||
|
|
8f847437a9 | ||
|
|
487fcea6f4 | ||
|
|
63a31db0a3 | ||
|
|
575edecccb | ||
|
|
14adda8f6b | ||
|
|
7cacff6d61 | ||
|
|
4ced125239 | ||
|
|
ac94c81134 | ||
|
|
9501050ddc | ||
|
|
c688880f9e | ||
|
|
47ec404a52 | ||
|
|
18b15a436f | ||
|
|
6583ad6970 | ||
|
|
863701391c | ||
|
|
3803c14279 | ||
|
|
39126c3828 | ||
|
|
b9cfc3622c | ||
|
|
1b0bf6507e | ||
|
|
7ca6aa4ad0 | ||
|
|
b82a98722a | ||
|
|
3d6d7012ff | ||
|
|
3051ff9f25 | ||
|
|
9bf66294f8 | ||
|
|
577378b827 | ||
|
|
1679222e18 | ||
|
|
74270cd1db | ||
|
|
718bf3b39e | ||
|
|
5ecb381d78 | ||
|
|
17f1332955 | ||
|
|
f4a1dfdffb | ||
|
|
d3972125a1 | ||
|
|
71f504a43d | ||
|
|
bc1370041d | ||
|
|
4f74869cd8 | ||
|
|
8d8b36df1b | ||
|
|
2da6c95208 |
No files matched your search
@@ -25,6 +25,7 @@ API changes
|
||||
|
||||
::
|
||||
|
||||
--- mpv 0.7.0 is released ---
|
||||
1.10 - deprecate/disable everything directly related to script_dispatch
|
||||
(most likely affects nobody)
|
||||
1.9 - add enum mpv_end_file_reason for mpv_event_end_file.reason
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
All examples in this directory and its sub-directories are licensed
|
||||
under one of the following licenses:
|
||||
|
||||
WTFPL, ISC, Ms-PL, AGPLv3, BSD (any)
|
||||
|
||||
Pick any license of your liking, and disregard the others.
|
||||
|
||||
(The full text of each license is available on this website:
|
||||
http://opensource.org/licenses/alphabetical )
|
||||
|
||||
Additionally, you may consider the example code to be public domain.
|
||||
You are free to use any of the example code without further
|
||||
requirements or need for attribution.
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
// License: pick one of: public domain, WTFPL, ISC, Ms-PL, AGPLv3
|
||||
|
||||
// This example can be built with: qmake && make
|
||||
|
||||
#include <clocale>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QFileDialog>
|
||||
@@ -57,7 +56,7 @@ MainWindow::MainWindow(QWidget *parent) :
|
||||
|
||||
mpv = mpv_create();
|
||||
if (!mpv)
|
||||
throw "can't create mpv instance";
|
||||
throw std::runtime_error("can't create mpv instance");
|
||||
|
||||
// Create a video child window. Force Qt to create a native window, and
|
||||
// pass the window ID to the mpv wid option. Works on: X11, win32, Cocoa
|
||||
@@ -94,7 +93,7 @@ MainWindow::MainWindow(QWidget *parent) :
|
||||
mpv_set_wakeup_callback(mpv, wakeup, this);
|
||||
|
||||
if (mpv_initialize(mpv) < 0)
|
||||
throw "mpv failed to initialize";
|
||||
throw std::runtime_error("mpv failed to initialize");
|
||||
}
|
||||
|
||||
void MainWindow::handle_mpv_event(mpv_event *event)
|
||||
|
||||
+9
-4
@@ -28,8 +28,8 @@ normal driver parameters.
|
||||
.. admonition:: Examples
|
||||
|
||||
- ``--ao=alsa,oss,`` Try the ALSA driver, then the OSS driver, then others.
|
||||
- ``--ao=alsa:no-block:device=[hw:0,3]`` Sets noblock-mode and the
|
||||
device-name as first card, fourth device.
|
||||
- ``--ao=alsa:resample=yes:device=[plughw:0,3]`` Lets ALSA resample and
|
||||
sets the device-name as first card, fourth device.
|
||||
|
||||
Available audio output drivers are:
|
||||
|
||||
@@ -147,10 +147,15 @@ Available audio output drivers are:
|
||||
changes. Default: 250.
|
||||
|
||||
``latency-hacks=<yes|no>``
|
||||
Enable hacks to workaround PulseAudio timing bugs (default: yes). If
|
||||
Enable hacks to workaround PulseAudio timing bugs (default: no). If
|
||||
enabled, mpv will do elaborate latency calculations on its own. If
|
||||
disabled, it will use PulseAudio automatically updated timing
|
||||
information. Disabling this might help with e.g. networked audio.
|
||||
information. Disabling this might help with e.g. networked audio or
|
||||
some plugins, while enabling it might help in some unknown situations
|
||||
(it used to be required to get good behavior on old PulseAudio versions).
|
||||
|
||||
If you have stuttering video when using pulse, try to enable this
|
||||
option. (Or alternatively, try to update PulseAudio.)
|
||||
|
||||
``portaudio``
|
||||
PortAudio audio output driver. This works on all platforms, and has
|
||||
|
||||
@@ -113,8 +113,8 @@ Miscellaneous
|
||||
* Matroska edition switching at runtime.
|
||||
* Support for playing URLs of popular streaming sites directly.
|
||||
(e.g. ``mpv https://www.youtube.com/watch?v=...``).
|
||||
Requires a recent version of ``youtube-dl`` to be installed, and
|
||||
``ytdl=yes`` in the mpv config file.
|
||||
Requires a recent version of ``youtube-dl`` to be installed. Can be
|
||||
disabled with ``ytdl=no`` in the mpv config file.
|
||||
* Support for precise scrolling which scales the parameter of commands. If the
|
||||
input doesn't support precise scrolling the scale factor stays 1.
|
||||
* Allow changing/adjusting video filters at runtime. (This is also used to make
|
||||
|
||||
+15
-5
@@ -1,5 +1,13 @@
|
||||
INPUT.CONF
|
||||
==========
|
||||
COMMAND INTERFACE
|
||||
=================
|
||||
|
||||
The mpv core can be controlled with commands and properties. A number of ways
|
||||
to interact with the player use them: key bindings (``input.conf``), OSD
|
||||
(showing information with properties), JSON IPC, the client API (``libmpv``),
|
||||
and the classic slave mode.
|
||||
|
||||
input.conf
|
||||
----------
|
||||
|
||||
The input.conf file consists of a list of key bindings, for example::
|
||||
|
||||
@@ -232,7 +240,6 @@ List of Input Commands
|
||||
will have after moving.)
|
||||
|
||||
``run "command" "arg1" "arg2" ...``
|
||||
(Unix only)
|
||||
Run the given command. Unlike in MPlayer/mplayer2 and earlier versions of
|
||||
mpv (0.2.x and older), this doesn't call the shell. Instead, the command
|
||||
is run directly, with each argument passed separately. Each argument is
|
||||
@@ -814,7 +821,7 @@ Property list
|
||||
"title" MPV_FORMAT_STRING
|
||||
"default" MPV_FORMAT_FLAG
|
||||
|
||||
``ab-loop-a``, ``ab-loop-b`` (TW)
|
||||
``ab-loop-a``, ``ab-loop-b`` (RW)
|
||||
Set/get A-B loop points. See corresponding options and ``ab_loop`` command.
|
||||
The special value ``no`` on either of these properties disables looping.
|
||||
|
||||
@@ -1421,13 +1428,16 @@ Property list
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
--osd-status-msg='This is ${osd-ass-cc/0}{\\b1}bold text'
|
||||
- ``--osd-status-msg='This is ${osd-ass-cc/0}{\\b1}bold text'``
|
||||
- ``show_text "This is ${osd-ass-cc/0}{\b1}bold text"``
|
||||
|
||||
Any ASS override tags as understood by libass can be used.
|
||||
|
||||
Note that you need to escape the ``\`` character, because the string is
|
||||
processed for C escape sequences before passing it to the OSD code.
|
||||
|
||||
A list of tags can be found here: http://docs.aegisub.org/latest/ASS_Tags/
|
||||
|
||||
``vo-configured``
|
||||
Return whether the VO is configured right now. Usually this corresponds to
|
||||
whether the video window is visible. If the ``--force-window`` option is
|
||||
|
||||
+10
-7
@@ -22,10 +22,12 @@ A script which leaves fullscreen mode when the player is paused:
|
||||
|
||||
::
|
||||
|
||||
function on_pause()
|
||||
mp.set_property("fullscreen", "no")
|
||||
function on_pause_change(name, value)
|
||||
if value == true then
|
||||
mp.set_property("fullscreen", "no")
|
||||
end
|
||||
end
|
||||
mp.register_event("pause", on_pause)
|
||||
mp.observe_property("pause", "bool", on_pause_change)
|
||||
|
||||
This script provides a pretty weird feature, but Lua scripting was made to
|
||||
allow users implement features which are not going to be added to the mpv core.
|
||||
@@ -573,7 +575,7 @@ strictly part of the guaranteed API.
|
||||
Runs an external process and waits until it exits. Returns process status
|
||||
and the captured output.
|
||||
|
||||
The paramater ``t`` is a table. The function reads the following entries:
|
||||
The parameter ``t`` is a table. The function reads the following entries:
|
||||
|
||||
``args``
|
||||
Array of strings. The first array entry is the executable. This
|
||||
@@ -623,6 +625,10 @@ strictly part of the guaranteed API.
|
||||
trailing text is returned as 3rd return value. (The 3rd return value is
|
||||
always there, but with ``trail`` set, no error is raised.)
|
||||
|
||||
``utils.to_string(v)``
|
||||
Turn the given value into a string. Formats tables and their contents. This
|
||||
doesn't do anything special; it is only needed because Lua is terrible.
|
||||
|
||||
Events
|
||||
------
|
||||
|
||||
@@ -716,9 +722,6 @@ List of events
|
||||
``command-reply``
|
||||
Undocumented (not useful for Lua scripts).
|
||||
|
||||
``script-input-dispatch``
|
||||
Undocumented (used internally).
|
||||
|
||||
``client-message``
|
||||
Undocumented (used internally).
|
||||
|
||||
|
||||
+85
-15
@@ -235,11 +235,43 @@ is the same as ``--no-fs``.
|
||||
If an option is marked as *(XXX only)*, it will only work in combination with
|
||||
the *XXX* option or if *XXX* is compiled in.
|
||||
|
||||
.. note::
|
||||
Escaping spaces and other special characters
|
||||
--------------------------------------------
|
||||
|
||||
The suboption parser (used for example for ``--ao=pcm`` suboptions)
|
||||
supports a special kind of string-escaping intended for use with external
|
||||
GUIs.
|
||||
Keep in mind that the shell will partially parse and mangle the arguments you
|
||||
pass to mpv. For example, you might need to quote or escape options and
|
||||
filenames:
|
||||
|
||||
``mpv "filename with spaces.mkv" --title="window title"``
|
||||
|
||||
It gets more complicated if the suboption parser is involved. The suboption
|
||||
parser puts several options into a single string, and passes them to a
|
||||
component at once, instead of using multiple options on the level of the
|
||||
command line.
|
||||
|
||||
The suboption parser can quote strings with ``"``, ``'``, and ``[...]``.
|
||||
Additionally, there is a special form of quoting with ``%n%`` described below.
|
||||
|
||||
For example, the ``opengl`` VO can take multiple options:
|
||||
|
||||
``mpv test.mkv --vo=opengl:lscale=lanczos2:icc-profile=file.icc,xv``
|
||||
|
||||
This passed ``lscale=lanczos2`` and ``icc-profile=file.icc`` to ``opengl``,
|
||||
and also specifies ``xv`` as fallback VO. If the icc-profile path contains
|
||||
spaces or characters like ``,`` or ``:``, you need to quote them:
|
||||
|
||||
``mpv '--vo=opengl:icc-profile="file with spaces.icc",xv'``
|
||||
|
||||
Shells may actually strip some quotes from the string passed to the commandline,
|
||||
so the example quotes the string twice, ensuring that mpv recieves the ``"``
|
||||
quotes.
|
||||
|
||||
The ``[...]`` from of quotes wraps everything between ``[`` and ``]``. It's
|
||||
useful with shells that don't interpret these characters in the middle of
|
||||
an argument (like bash).
|
||||
|
||||
A special kind of string-escaping intended for use with external scripts and
|
||||
programs is started with ``%``.
|
||||
|
||||
It has the following format::
|
||||
|
||||
@@ -253,6 +285,15 @@ It has the following format::
|
||||
|
||||
``mpv --ao=pcm:file=%`expr length "$NAME"`%"$NAME" test.avi``
|
||||
|
||||
Suboptions passed to the client API are also subject to escaping. Using
|
||||
``mpv_set_option_string()`` is exactly like passing ``--name=data`` to the
|
||||
command line (but without shell processing of the string). Some options
|
||||
support passing values in a more structured way instead of flat strings, and
|
||||
can avoid the suboption parsing mess. For example, ``--vf`` supports
|
||||
``MPV_FORMAT_NODE``, which let's you pass suboptions as a nested data structure
|
||||
of maps and arrays. (``--vo`` supports this in the same way, although this
|
||||
fact is undocumented.)
|
||||
|
||||
Paths
|
||||
-----
|
||||
|
||||
@@ -340,6 +381,17 @@ setting them to *no*. Even suboptions can be specified in this way.
|
||||
# Use quotes for text that can contain spaces:
|
||||
status-msg="Time: ${time-pos}"
|
||||
|
||||
Escaping spaces and special characters
|
||||
--------------------------------------
|
||||
|
||||
This is done like with command line options. The shell is not involved here,
|
||||
but option values still need to be quoted as a whole if it contains certain
|
||||
characters like spaces. A config entry can be quoted with ``"`` and ``'``,
|
||||
as well as with the ``%n%`` syntax mentioned before. This is like passing
|
||||
the exact contents of the quoted string as command line option. C-style escapes
|
||||
are currently _not_ interpreted on this level, although some options to this
|
||||
manually. (This is a mess and should probably be changed at some point.)
|
||||
|
||||
Putting Command Line Options into the Configuration File
|
||||
--------------------------------------------------------
|
||||
|
||||
@@ -436,7 +488,7 @@ listed.
|
||||
|
||||
- ``AV:`` or ``V:`` (video only) or ``A:`` (audio only)
|
||||
- The current time position in ``HH:MM:SS`` format (``playback-time`` property)
|
||||
- The total file duration (or ``00:00:00`` if unknown) (``length`` property)
|
||||
- The total file duration (absent if unknown) (``length`` property)
|
||||
- Playback speed, e.g. `` x2.0``. Only visible if the speed is not normal. This
|
||||
is the user-requested speed, and not the actual speed (usually they should
|
||||
be the same, unless playback is too slow). (``speed`` property.)
|
||||
@@ -487,7 +539,7 @@ PROTOCOLS
|
||||
``bd://[title][/device]`` ``--bluray-device=PATH``
|
||||
Play a Blu-Ray disc. Currently, this does not accept ISO files. Instead,
|
||||
you must mount the ISO file as filesystem, and point ``--bluray-device``
|
||||
to the mounted directly.
|
||||
to the mounted directory directly.
|
||||
|
||||
``bdnav://[title][/device]``
|
||||
Play a Blu-Ray disc, with navigation features enabled. This feature is
|
||||
@@ -718,6 +770,26 @@ FILES
|
||||
and files with no ``.lua`` extension are ignored. The ``--load-scripts=no``
|
||||
option disables loading these files.
|
||||
|
||||
``~/.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.
|
||||
|
||||
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
|
||||
all) settings that were changed during playback. The filenames are hashed
|
||||
from the full paths of the media files. It's in general not possible to
|
||||
extract the media filename from this hash. However, you can set the
|
||||
``--write-filename-in-watch-later-config`` option, and the player will
|
||||
add the media filename to the contents of the resume config file.
|
||||
|
||||
``~/.config/mpv/lua-settings/osc.conf``
|
||||
This is loaded by the OSC script. See the `ON SCREEN CONTROLLER`_ docs
|
||||
for details.
|
||||
|
||||
Other files in this directory are specific to the corresponding scripts
|
||||
as well, and the mpv core doesn't touch them.
|
||||
|
||||
Note that the environment variables ``$XDG_CONFIG_HOME`` and ``$MPV_HOME`` can
|
||||
override the standard directory ``~/.config/mpv/``.
|
||||
|
||||
@@ -728,18 +800,16 @@ FILES ON WINDOWS
|
||||
================
|
||||
|
||||
On win32 (if compiled with MinGW, but not Cygwin), the default config file
|
||||
locations are different:
|
||||
locations are different. They are generally located under ``%APPDATA%/mpv/``.
|
||||
For example, the path to mpv.conf is ``%APPDATA%/mpv/mpv.conf``, which maps to
|
||||
a system and user-specific path, for example
|
||||
|
||||
``%APPDATA%/mpv/mpv.conf``
|
||||
Preferred mpv config file. This maps to a system and user-specific path,
|
||||
for example ``C:\users\USERNAME\Application Data\mpv\mpv.conf``. You can
|
||||
find the exact path by running ``echo %APPDATA%\mpv\mpv.conf`` in cmd.exe.
|
||||
``C:\users\USERNAME\Application Data\mpv\mpv.conf``
|
||||
|
||||
``%APPDATA%/mpv/input.conf``
|
||||
key bindings (see `INPUT.CONF`_ section)
|
||||
You can find the exact path by running ``echo %APPDATA%\mpv\mpv.conf`` in cmd.exe.
|
||||
|
||||
``%APPDATA%/mpv/lua/``
|
||||
equivalent of ``~/.config/mpv/lua/`` on Unix.
|
||||
Other config files (such as ``input.conf``) are in the same directory. See the
|
||||
`FILES`_ section above.
|
||||
|
||||
The environment variable ``$MPV_HOME`` completely overrides these, like on
|
||||
UNIX.
|
||||
|
||||
+20
-24
@@ -51,8 +51,8 @@ Track Selection
|
||||
other software using FFmpeg (consider ``ffprobe``).
|
||||
|
||||
Note that with external tracks (added with ``--sub-file`` and similar
|
||||
options) will have duplicate IDs. In that case, the first stream in order
|
||||
is selected.
|
||||
options), there will be streams with duplicate IDs. In this case, the
|
||||
first stream in order is selected.
|
||||
|
||||
``--edition=<ID|auto>``
|
||||
(Matroska files only)
|
||||
@@ -124,8 +124,8 @@ Playback Control
|
||||
chapter to end playing at. Also see ``--start``.
|
||||
|
||||
``--playlist=<filename>``
|
||||
Play files according to a playlist file (Supports some common formats.If
|
||||
no format is detected, t will be treated as list of files, separated by
|
||||
Play files according to a playlist file (Supports some common formats. If
|
||||
no format is detected, it will be treated as list of files, separated by
|
||||
newline characters. Note that XML playlist formats are not supported.)
|
||||
|
||||
You can play playlists directly and without this option, however, this
|
||||
@@ -360,10 +360,6 @@ Program Behavior
|
||||
file. This uses timeline/EDL support internally. Note that this won't work
|
||||
for ordered chapter files.
|
||||
|
||||
This option is interpreted at program start, and doesn't affect for
|
||||
example files or playlists loaded with the ``loadfile`` or ``loadlist``
|
||||
commands.
|
||||
|
||||
``--no-resume-playback``
|
||||
Do not restore playback position from the ``watch_later`` configuration
|
||||
subdirectory (usually ``~/.config/mpv/watch_later/``).
|
||||
@@ -425,21 +421,21 @@ Program Behavior
|
||||
``--ytdl``, ``--no-ytdl``
|
||||
Enable the youtube-dl hook-script. It will look at the input URL, and will
|
||||
play the video located on the website. This works with many streaming sites,
|
||||
not just the one the scripts are named after. This requires a recent version
|
||||
of youtube-dl to be installed on the system.
|
||||
not just the one that the script is named after. This requires a recent
|
||||
version of youtube-dl to be installed on the system. (Enabled by default.)
|
||||
|
||||
If the script can't do anything with an URL, it will do nothing.
|
||||
|
||||
Currently disabled by default, because youtube-dl's generic extractor can
|
||||
get stuck on some URL, preventing playback.
|
||||
|
||||
(Note: this is the replacement for the now removed libquvi support.)
|
||||
|
||||
``--ytdl-format=<best|worst|mp4|webm|...>``
|
||||
Video format/quality that is directly passed to youtube-dl. The possible
|
||||
values are specific to the website and the video, for a given url the
|
||||
available formats can be found with the command
|
||||
``youtube-dl --list-formats URL``.
|
||||
``youtube-dl --list-formats URL``. See youtube-dl's documentation for
|
||||
available aliases. To use experimental DASH support for youtube, use
|
||||
``bestvideo+bestaudio``.
|
||||
(Default: ``best``)
|
||||
|
||||
Video
|
||||
-----
|
||||
@@ -498,7 +494,7 @@ Video
|
||||
in mpv 0.5.x and before.) This tells the decoder to skip frames (unless
|
||||
they are needed to decode future frames). May help with slow systems,
|
||||
but can produce unwatchably choppy output, or even freeze the display
|
||||
complete. Not recommended.
|
||||
completely. Not recommended.
|
||||
The ``--vd-lavc-framedrop`` option controls what frames to drop.
|
||||
<decoder+vo>
|
||||
Enable both modes. Not recommended.
|
||||
@@ -576,9 +572,8 @@ Video
|
||||
``--video-unscaled``
|
||||
Disable scaling of the video. If the window is larger than the video,
|
||||
black bars are added. Otherwise, the video is cropped. The video still
|
||||
can be influenced by the other ``--video-...`` options. (If the
|
||||
``--video-zoom`` option is set to a value other than ``1``, scaling is
|
||||
enabled, but the video isn't automatically scaled to the window size.)
|
||||
can be influenced by the other ``--video-...`` options. (But not all; for
|
||||
example ``--video-zoom`` does nothing if this option is enabled.)
|
||||
|
||||
The video and monitor aspects aspect will be ignored. Aspect correction
|
||||
would require to scale the video in the X or Y direction, but this option
|
||||
@@ -1434,15 +1429,16 @@ Window
|
||||
------
|
||||
|
||||
``--title=<string>``
|
||||
Set the window title. Properties are expanded on playback start.
|
||||
(See `Property Expansion`_.)
|
||||
Set the window title. This is used for the video window, and if possible,
|
||||
also sets the audio stream title.
|
||||
|
||||
Properties are expanded. (See `Property Expansion`_.)
|
||||
|
||||
.. warning::
|
||||
|
||||
There is a danger of this causing significant CPU usage, depending on
|
||||
the properties used and the window manager. Changing the window title
|
||||
is often a slow operation, and if the title changes every frame,
|
||||
playback can be ruined.
|
||||
the properties used. Changing the window title is often a slow
|
||||
operation, and if the title changes every frame, playback can be ruined.
|
||||
|
||||
``--screen=<default|0-32>``
|
||||
In multi-monitor configurations (i.e. a single desktop that spans across
|
||||
@@ -2434,7 +2430,7 @@ OSD
|
||||
Alternatively, the color can be specified as a RGB hex triplet in the form
|
||||
``#RRGGBB``, where each 2-digit group expresses a color value in the
|
||||
range 0 (``00``) to 255 (``FF``). For example, ``#FF0000`` is red.
|
||||
This is similar to web colors.
|
||||
This is similar to web colors. Alpha is given with ``#AARRGGBB``.
|
||||
|
||||
.. admonition:: Examples
|
||||
|
||||
|
||||
@@ -910,6 +910,26 @@ Available filters are:
|
||||
making it higher than the number of cores can actually make it
|
||||
slower.
|
||||
|
||||
The following variables are defined by mpv:
|
||||
|
||||
``video_in``
|
||||
The mpv video source as vapoursynth clip. Note that this has no length
|
||||
set, which confuses many filters. Using ``Trim`` on the clip with a
|
||||
high dummy length can turn it into a finite clip.
|
||||
|
||||
``video_in_dw``, ``video_in_dh``
|
||||
Display size of the video. Can be different from video size if the
|
||||
video does not use square pixels (e.g. DVD).
|
||||
|
||||
``container_fps``
|
||||
FPS value as reported by file headers. This value can be wrong or
|
||||
completely broken (e.g. 0 or NaN). Even if the value is correct,
|
||||
if another filter changes the real FPS (by dropping or inserting
|
||||
frames), the value of this variable might not be useful. Note that
|
||||
the ``--fps`` command line option overrides this value.
|
||||
|
||||
Useful for some filters which insist on having a FPS.
|
||||
|
||||
``vapoursynth-lazy``
|
||||
The same as ``vapoursynth``, but doesn't load Python scripts. Instead, a
|
||||
custom backend using Lua and the raw VapourSynth API is used. The syntax
|
||||
|
||||
+1
-1
@@ -456,7 +456,7 @@ Available video output drivers are:
|
||||
luma scaler is used instead. Setting this option is often useless.
|
||||
|
||||
``lscale-down=<filter>``, ``cscale-down=<filter>``
|
||||
Like ``lscale`` and ``cscale``, but apply these filters on downscaling
|
||||
Like ``lscale``, but apply these filters on downscaling
|
||||
instead. If these options are unset, the filter implied by ``lscale``
|
||||
(and ``cscale``, respectively) will be applied.
|
||||
|
||||
|
||||
@@ -46,16 +46,23 @@ Essential dependencies (incomplete list):
|
||||
|
||||
- gcc or clang
|
||||
- X development headers (xlib, X extensions, libvdpau, libGL, libXv, ...)
|
||||
- Audio output development headers (libasound, pulseaudio)
|
||||
- FFmpeg libraries (libavutil libavcodec libavformat libswscale libavresample)
|
||||
- Audio output development headers (libasound/ALSA, pulseaudio)
|
||||
- FFmpeg libraries (libavutil libavcodec libavformat libswscale libavfilter
|
||||
and either libswresample or libavresample)
|
||||
At least FFmpeg 2.1.4 or Libav 10 is required.
|
||||
- libjpeg (for screenshots)
|
||||
- Lua (strictly speaking optional, but required for the OSC pseudo-GUI)
|
||||
- libass
|
||||
- Lua (optional, required for the OSC pseudo-GUI)
|
||||
- libjpeg (optional, used for screenshots only)
|
||||
- Enca (optional, for subtitle charset detection)
|
||||
- vdpau and vaapi libraries for hardware decoding on Linux (optional)
|
||||
|
||||
Libass dependencies:
|
||||
|
||||
- gcc or clang, yasm on x86 and x86_64
|
||||
- fribidi, freetype, fontconfig development headers (for libass)
|
||||
- harfbuzz (optional, required for correct rendering of combining characters,
|
||||
particularly for correct rendering of non-English text on OSX, and
|
||||
Arabic/Indic scripts on any platform)
|
||||
|
||||
FFmpeg dependencies:
|
||||
|
||||
@@ -76,6 +83,7 @@ If you are running Mac OSX and using homebrew we provide [homebrew-mpv][homebrew
|
||||
to date formula that compiles mpv with sensible dependencies and defaults for
|
||||
OSX.
|
||||
|
||||
|
||||
FFmpeg vs. Libav
|
||||
----------------
|
||||
|
||||
|
||||
+214
@@ -0,0 +1,214 @@
|
||||
Release 0.7.3
|
||||
=============
|
||||
|
||||
Changes
|
||||
-------
|
||||
|
||||
- Several documentation updates and typo fixes.
|
||||
- Don't show "0%" position when the stream is infinite / has unknown length.
|
||||
- Fixes the config file loading order so that lower priority `mpv.conf` files
|
||||
don't override higher priority `config` files.
|
||||
- (OSX) Remove coreaudio_exclusive from the "auto" codec list.
|
||||
- Failing to create a GL3 context is now a warning instead of error, to reduce
|
||||
user confusion.
|
||||
- The subtitle decoder now gets reset when cycling subtitles. This makes the
|
||||
currently shown subtitle event disappear even if cycling back to the current
|
||||
subtitle track.
|
||||
- `--shuffle` and `--merge-files` now affects the contents of playlist files,
|
||||
instead of just the list of files given as arguments to mpv.
|
||||
- `./waf install` avoids installing a few additional data/config files if only
|
||||
libmpv was built.
|
||||
- Improved channel mapping when the file's channel map doesn't match the AO's
|
||||
available channel maps.
|
||||
- (OSX) VDA now gracefully refuses to run on non-OpenGL3-capable systems instead
|
||||
of trying and failing to build its shaders.
|
||||
- Add workaround for vf_vapoursynth filters that expect an FPS.
|
||||
- The default value for `--screenshot-template` now shows up in
|
||||
`--list-options`.
|
||||
- `ao_pulse`'s `latency-hacks` suboption is now off by default, as it causes
|
||||
issues with newer pulseaudio releases. If A/V Sync issues happen, either
|
||||
add the `latency-hacks=on` pulse suboption in mpv.conf, or update the
|
||||
pulseaudio daemon (bug #1430).
|
||||
- (Linux) `ao_alsa` now deals better with audio device disconnection.
|
||||
- Client API: timestamp properties that have no value return "no" instead of
|
||||
magic number.
|
||||
|
||||
Bug fixes
|
||||
---------
|
||||
|
||||
- (Windows, OSX) Many `ao_wasapi` and `ao_coreaudio` fixes.
|
||||
- (OSX) Make the window title update correctly on OS X Yosemite.
|
||||
- Fixes for handling mono audio on various AOs.
|
||||
- (Linux) Fixes resuming from suspend on ao_alsa.
|
||||
- Fixes for playlist file parsing.
|
||||
- Overly long options in the --list-options output now break the column layout
|
||||
instead of getting truncated; fixes shell completion scripts.
|
||||
- Fix rendering resolution of certain DVB subtitles (bug #1425).
|
||||
- Fix EDL or --merge-files breaking timestamps with .avi files.
|
||||
- Workaround for libavcodec bug with the VP9 codec parser (bug #1448),
|
||||
fixes possible crash.
|
||||
- Improve robustness of the matroska parser with broken files (bugs #1457,
|
||||
#1461).
|
||||
- Improve 10bit video compatibility with older GPUs (specially Intel) (bug
|
||||
#1383).
|
||||
- Fixes flashing the VO window when playing a list of files that includes
|
||||
non-media files (bug #1459).
|
||||
- Workaround for window embedding in OpenBox (bug #1235)
|
||||
- Fixes for several crashes and lockups (bugs #838, #1389, #1408, #1463, #1473,
|
||||
#1474).
|
||||
- Fixes for the zsh completion script (bugs #997)
|
||||
|
||||
New features
|
||||
------------
|
||||
|
||||
- mpv now prints the contents of its config.h when running in verbose mode, to
|
||||
aid with debugging.
|
||||
- The `include=` option now accepts `~` to refer to the HOME dir (bug #1406).
|
||||
- `af_volume` now prints ReplayGain values in verbose mode.
|
||||
- m3u playlists that don't have the `#EXTM3U` header are now accepted if they
|
||||
"look like" ASCII or UTF-8 text and have the .m3u extension.
|
||||
- Chapter marks in the seek bar now update when switching files.
|
||||
- Supports embedded cover art in MKV files (bug #1374).
|
||||
- (Windows) Video window can now be resized even with --no-border.
|
||||
- (Windows) Client API: the "run" command now works on Windows too.
|
||||
- (Linux) vo_wayland now supports key modifiers (Meta, Alt, Control, Shift)
|
||||
- Client API: "display-names" property is now observable.
|
||||
|
||||
This listing is not complete. A complete changelog can be seen by running
|
||||
`git log v0.7.2..v0.7.3` in the git repository.
|
||||
|
||||
Release 0.7.2
|
||||
=============
|
||||
|
||||
Changes
|
||||
-------
|
||||
|
||||
- Give precedence to the DVD menu navigation keyboard bindings so that user
|
||||
defined LEFT/RIGHT/... bindings don't break DVD menu navigation.
|
||||
- Try to fallback to the "default" device if the selected device is busy in the
|
||||
alsa AO.
|
||||
- Don't create Dock icon for audio only files on OS X.
|
||||
- Save screenshots to desktop when using the app bundle on OS X.
|
||||
- Restore ab-loop settings with playback resume.
|
||||
- Bump required youtube-dl version to 2014.11.26 and enable the ytdl_hook Lua
|
||||
script by default (now playing videos from YouTube and the like will work
|
||||
out of the box without any configuration change needed).
|
||||
|
||||
Bug fixes
|
||||
---------
|
||||
|
||||
- Don't signal an error if --stream-dump is used.
|
||||
- Fix removing key bindings from Lua scripts.
|
||||
- Reject channel descriptions with too many channels in the coreaudio AO.
|
||||
- Don't async redraw when waiting for VO redraw on OS X (this fixes the very
|
||||
annoying glitch where the black bars disappear for a single frame when going
|
||||
fullscreen).
|
||||
- Fix mono playback with the also AO.
|
||||
- Don't crash if framebuffers are not available in the opengl VO.
|
||||
|
||||
New features
|
||||
------------
|
||||
|
||||
- Try to handle multi-arc videos in the ytdl_hook Lua script.
|
||||
|
||||
This listing is not complete. There are many more bug fixes and changes. The
|
||||
complete change log can be viewed by running ``git log 47ec404..c7d6b21`` in
|
||||
the git repository.
|
||||
|
||||
Release 0.7.1
|
||||
=============
|
||||
|
||||
Changes
|
||||
-------
|
||||
|
||||
- Don't show the volume neutral marker on the OSD if softvol is disabled.
|
||||
- Don't select a subtitle track when executing the sub_add input command in
|
||||
"auto" mode.
|
||||
|
||||
Bug fixes
|
||||
---------
|
||||
|
||||
- Fix busy loop when seeking while paused (this fixes a problem with pulseaudio
|
||||
that caused mpv and the pulseaudio daemon to use 100% CPU).
|
||||
- Fix Lua function utils.subprocess() in Windows versions older than Vista.
|
||||
- Avoid creating a window bigger than the screen on Windows.
|
||||
- Don't ignore the last line in m3u playlists.
|
||||
- Don't crash if a codec could not be opened.
|
||||
- Dynamically allocate audio channel map entries (this should fix a crash in
|
||||
the alsa and coreaudio AOs with audio devices that support more than 20
|
||||
channel maps).
|
||||
- Ignore the "srgb" option in the opengl VO if hardware decoding is enabled.
|
||||
- Linearize non-RGB sRGB files correctly (eg. JPEG).
|
||||
- Fix opening reference URLs (.file/id=) on OS X.
|
||||
|
||||
This listing is not complete. There are many more bug fixes and changes. The
|
||||
complete change log can be viewed by running ``git log 8d8b36d..6583ad6`` in
|
||||
the git repository.
|
||||
|
||||
Release 0.7.0
|
||||
=============
|
||||
|
||||
Changes
|
||||
-------
|
||||
|
||||
- Buffer partial log messages in the client API (the client API will now only
|
||||
pass full log messages to clients).
|
||||
- Remove ncurses/terminfo/termcap support (it was disabled by default and
|
||||
replaced by new code since v0.6.0).
|
||||
- Enable cdda:// support by default again (it was disabled since v0.6.0).
|
||||
- Cascade-load input.conf (if there are several input.confs in the set of valid
|
||||
config paths, load them all).
|
||||
- Draw the OSD twice in 3D mode (this fixes subtitles display in 3D mode).
|
||||
- Make wasapi the default AO on Windows again since many of its problems have
|
||||
been solved.
|
||||
- Use "site-functions" subdir to install the zsh completion script instead of
|
||||
the Debian-specific "vendor-completions" (also provide the --zshdir waf
|
||||
configure option for changing this value).
|
||||
- Improve synchronization between the Cocoa GUI and the player (this fixes some
|
||||
long standing deadlock issues on Mac OS X).
|
||||
- Remove --fs-missioncontrol option (only relevant to Mac OS X).
|
||||
|
||||
New features
|
||||
------------
|
||||
|
||||
- Enable pitch correction by default when playing at higher speeds (this can be
|
||||
controlled with the --audio-pitch-correction option).
|
||||
- Open stream and demuxer asynchronously (this should avoid having the player
|
||||
get blocked on network streams).
|
||||
- Add cache-buffering-state property for querying the cache fill status until
|
||||
the player unpauses.
|
||||
- Add support for listing and selecting the audio device (note that it is not
|
||||
implemented for all AOs, see the --audio-device option for more information).
|
||||
- Add support for a JSON-based IPC mechanism (note that this is not currently
|
||||
supported on Windows, see the JSON IPC section in the manpage for more
|
||||
information).
|
||||
- Add Lua utility function for starting processes (see utils.subprocess() in the
|
||||
manpage).
|
||||
- Add Lua utility function for parsing JSON (see utils.parse_json() in the
|
||||
manpage).
|
||||
- Add field-dominance property (see --field-dominance option).
|
||||
- Add video-rotate property (see --video-rotate option).
|
||||
- Add playback-abort property for querying whether playback is stopped or is to
|
||||
be stopped.
|
||||
- Add cursor-autohide property (see --cursor-autohide option).
|
||||
- Add vo-configured property for querying whether a window is created.
|
||||
- Add support for dxva2 hardware acceleration on Windows.
|
||||
- Drop libquvi support (this has been replaced by a built-in Lua script that
|
||||
invokes the youtube-dl tool, which needs to be installed, see the --ytdl
|
||||
option).
|
||||
- Add support for loading chapters from an external file (see the
|
||||
--chapters-file option).
|
||||
- Add window-minimized property for querying whether the window is minimized
|
||||
(works for X11 only).
|
||||
- Make it possible to configure the OSC seekbar style (see the "seekbarstyle"
|
||||
OSC option).
|
||||
- Add support for libmpv on Mac OS X (it used to be broken, now it's fully
|
||||
functional and also provides support for embedding the mpv window inside a
|
||||
Cocoa/Qt application).
|
||||
- Try to use the audio channel map reported by ALSA in the alsa AO.
|
||||
- Add option to disable text OSD rendering completely (useful for working around
|
||||
certain fontconfig issues, see the --use-text-osd option).
|
||||
|
||||
This listing is not complete. There are many more bug fixes and changes. The
|
||||
complete change log can be viewed by running ``git log 7759c18..9479daa`` in
|
||||
the git repository.
|
||||
@@ -25,6 +25,9 @@ function find_and_add_entries()
|
||||
return
|
||||
end
|
||||
local files = mputils.readdir(dir, "files")
|
||||
if files == nil then
|
||||
return
|
||||
end
|
||||
table.sort(files)
|
||||
if dir == "." then
|
||||
dir = ""
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
-- Test script for property change notification mechanism.
|
||||
-- Note that watching/reading some properties can be very expensive, or
|
||||
-- require the player to synchronously wait on network (when playing
|
||||
-- remote files), so you should in general only watch properties you
|
||||
-- are interested in.
|
||||
|
||||
function format_property_val(v)
|
||||
if type(v) == "table" then
|
||||
return mp.format_table(v) -- undocumented function; might be removed
|
||||
else
|
||||
return tostring(v)
|
||||
end
|
||||
end
|
||||
local utils = require("mp.utils")
|
||||
|
||||
for i,name in ipairs(mp.get_property_native("property-list")) do
|
||||
mp.observe_property(name, "native", function(name, val)
|
||||
print("property '" .. name .. "' changed to '" ..
|
||||
format_property_val(val) .. "'")
|
||||
utils.to_string(val) .. "'")
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1 @@
|
||||
screenshot-template=~/Desktop/shot%n
|
||||
+43
-19
@@ -1,9 +1,12 @@
|
||||
#!/usr/bin/env python3
|
||||
import matplotlib.pyplot as plot
|
||||
import sys
|
||||
import re
|
||||
|
||||
filename = sys.argv[1]
|
||||
|
||||
event_regex = re.compile(".*")
|
||||
|
||||
"""
|
||||
This script is meant to display stats written by mpv --dump-stats=filename.
|
||||
In general, each line in that file is an event of the form:
|
||||
@@ -18,17 +21,19 @@ e.g.:
|
||||
|
||||
Currently, the following event types are supported:
|
||||
|
||||
'signal' <name> singular event
|
||||
'start' <name> start of the named event
|
||||
'end' <name> end of the named event
|
||||
'value' <float> <name> a normal value (as opposed to event)
|
||||
'event-timed' <ts> <name> singular event at the given timestamp
|
||||
<name> singular event
|
||||
'value-timed' <ts> <float> <name>
|
||||
a value for an event at the given timestamp
|
||||
<name> singular event (same as 'signal')
|
||||
|
||||
"""
|
||||
|
||||
class G:
|
||||
events = {}
|
||||
sevents = [] # events, deterministically sorted
|
||||
start = None
|
||||
# http://matplotlib.org/api/markers_api.html#module-matplotlib.markers
|
||||
markers = ["o", "8", "s", "p", "*", "h", "+", "x", "D"]
|
||||
@@ -46,16 +51,15 @@ class Event:
|
||||
def get_event(event, evtype):
|
||||
if event not in G.events:
|
||||
e = Event()
|
||||
G.events[event] = e
|
||||
e.name = event
|
||||
e.vals = []
|
||||
e.type = evtype
|
||||
e.marker = "o"
|
||||
e.numid = len(G.events)
|
||||
G.sevents = list(G.events.values())
|
||||
G.sevents.sort(key=lambda x: x.name)
|
||||
if e.type == "event-signal":
|
||||
e.marker = find_marker()
|
||||
if not event_regex.match(e.name):
|
||||
return e
|
||||
G.events[event] = e
|
||||
return G.events[event]
|
||||
|
||||
for line in [line.split("#")[0].strip() for line in open(filename, "r")]:
|
||||
@@ -85,25 +89,45 @@ for line in [line.split("#")[0].strip() for line in open(filename, "r")]:
|
||||
val = int(val) / 1000 - G.start
|
||||
e = get_event(name, "event-signal")
|
||||
e.vals.append((val, 1))
|
||||
elif event.startswith("value-timed "):
|
||||
_, tsval, val, name = event.split(" ", 3)
|
||||
tsval = int(tsval) / 1000 - G.start
|
||||
val = float(val)
|
||||
e = get_event(name, "value")
|
||||
e.vals.append((tsval, val))
|
||||
elif event.startswith("signal "):
|
||||
name = event.split(" ", 2)[1]
|
||||
e = get_event(name, "event-signal")
|
||||
e.vals.append((ts, 1))
|
||||
else:
|
||||
e = get_event(event, "event-signal")
|
||||
e.vals.append((ts, 1))
|
||||
|
||||
for e in G.sevents:
|
||||
e.vals = [(x, y * e.numid / len(G.events)) for (x, y) in e.vals]
|
||||
|
||||
plot.hold(True)
|
||||
mainpl = plot.subplot(2, 1, 1)
|
||||
legend = []
|
||||
for e in G.sevents:
|
||||
# deterministically sort them; make sure the legend is sorted too
|
||||
G.sevents = list(G.events.values())
|
||||
G.sevents.sort(key=lambda x: x.name)
|
||||
hasval = False
|
||||
for e, index in zip(G.sevents, range(len(G.sevents))):
|
||||
m = len(G.sevents)
|
||||
if e.type == "value":
|
||||
plot.subplot(2, 1, 2, sharex=mainpl)
|
||||
hasval = True
|
||||
else:
|
||||
plot.subplot(2, 1, 1)
|
||||
pl, = plot.plot([x for x,y in e.vals], [y for x,y in e.vals], label=e.name)
|
||||
e.vals = [(x, y * (m - index) / m) for (x, y) in e.vals]
|
||||
|
||||
fig = plot.figure()
|
||||
fig.hold(True)
|
||||
ax = [None, None]
|
||||
plots = 2 if hasval else 1
|
||||
ax[0] = fig.add_subplot(plots, 1, 1)
|
||||
if hasval:
|
||||
ax[1] = fig.add_subplot(plots, 1, 2, sharex=ax[0])
|
||||
legends = [[], []]
|
||||
for e in G.sevents:
|
||||
cur = ax[1 if e.type == "value" else 0]
|
||||
pl, = cur.plot([x for x,y in e.vals], [y for x,y in e.vals], label=e.name)
|
||||
if e.type == "event-signal":
|
||||
plot.setp(pl, marker = e.marker, linestyle = "None")
|
||||
legend.append(pl)
|
||||
plot.subplot(2, 1, 1)
|
||||
plot.legend(legend, [pl.get_label() for pl in legend])
|
||||
for cur in ax:
|
||||
if cur is not None:
|
||||
cur.legend()
|
||||
plot.show()
|
||||
@@ -6,17 +6,17 @@ cookies_dir="$(mktemp -d /tmp/youtube-dl_mpv.XXXX)"
|
||||
cookies_file="${cookies_dir}/cookies"
|
||||
user_agent="$(youtube-dl --dump-user-agent)" # or set whatever you want
|
||||
|
||||
video_url="$(youtube-dl \
|
||||
--user-agent="$user_agent" \
|
||||
--cookies="$cookies_file" \
|
||||
video_url=$(youtube-dl \
|
||||
--user-agent="${user_agent}" \
|
||||
--cookies="${cookies_file}" \
|
||||
--get-url \
|
||||
"$@")"
|
||||
"$@")
|
||||
|
||||
set -f
|
||||
${MPV:-mpv} \
|
||||
--cookies \
|
||||
--cookies-file="$cookies_file" \
|
||||
--user-agent="$user_agent" \
|
||||
-- $video_url
|
||||
--cookies-file="${cookies_file}" \
|
||||
--user-agent="${user_agent}" \
|
||||
-- ${video_url}
|
||||
|
||||
rm -rf "$cookies_dir"
|
||||
rm -rf "${cookies_dir}"
|
||||
+70
-31
@@ -7,7 +7,7 @@ use warnings;
|
||||
|
||||
my $mpv = $ARGV[0] || 'mpv';
|
||||
|
||||
my @opts = parse_opts("$mpv --list-options", '^ (\-\-[^\s\*]*)\*?\s*(.*)', 1);
|
||||
my @opts = parse_main_opts("$mpv --list-options", '^ (\-\-[^\s\*]*)\*?\s*(.*)');
|
||||
|
||||
my @ao = parse_opts("$mpv --ao=help", '^ ([^\s\:]*)\s*: (.*)');
|
||||
my @vo = parse_opts("$mpv --vo=help", '^ ([^\s\:]*)\s*: (.*)');
|
||||
@@ -54,7 +54,7 @@ my $profile_comp = <<'EOS';
|
||||
profiles=($profiles $current)
|
||||
fi
|
||||
done
|
||||
if [[ $state == profile ]]; then
|
||||
if [[ $state == show-profile ]]; then
|
||||
# For --show-profile, only one allowed
|
||||
if (( ${#profiles} > 0 )); then
|
||||
_values 'profile' $profiles && rc=0
|
||||
@@ -118,7 +118,7 @@ $vf_str
|
||||
_describe -t values 'video filters' values && rc=0
|
||||
;;
|
||||
|
||||
profile|profiles)
|
||||
profile|show-profile)
|
||||
$profile_comp
|
||||
;;
|
||||
|
||||
@@ -133,7 +133,7 @@ $profile_comp
|
||||
_tags files urls
|
||||
while _tags; do
|
||||
_requested files expl 'media file' _files -g \\
|
||||
"*.(#i)(asf|asx|avi|flac|flv|m1v|m2p|m2v|m4v|mjpg|mka|mkv|mov|mp3|mp4|mpe|mpeg|mpg|ogg|ogm|ogv|qt|rm|ts|vob|wav|webm|wma|wmv)(-.)" && rc=0
|
||||
"*.(#i)(asf|asx|avi|flac|flv|m1v|m2p|m2v|m4v|mjpg|mka|mkv|mov|mp3|mp4|mpe|mpeg|mpg|ogg|ogm|ogv|qt|rm|ts|vob|wav|webm|wma|wmv)(-.)" && rc=0
|
||||
if _requested urls; then
|
||||
while _next_label urls expl URL; do
|
||||
_urls "\$expl[@]" && rc=0
|
||||
@@ -150,8 +150,73 @@ EOS
|
||||
|
||||
print $tmpl;
|
||||
|
||||
sub parse_main_opts {
|
||||
my ($cmd, $regex) = @_;
|
||||
|
||||
my @list;
|
||||
my @lines = split /\n/, `$cmd`;
|
||||
|
||||
foreach my $line (@lines) {
|
||||
my ($name, $desc) = ($line =~ /^$regex/) or next;
|
||||
|
||||
next if ($desc eq 'removed' || $desc eq 'alias');
|
||||
|
||||
if ($desc =~ /^Flag/) {
|
||||
|
||||
push @list, $name;
|
||||
|
||||
$name =~ /^--(.*)/;
|
||||
if ($1 !~ /^(\{|\}|v|list-options|really-quiet|no-.*)$/) {
|
||||
push @list, "--no-$1";
|
||||
}
|
||||
|
||||
} elsif ($desc =~ /^Print/) {
|
||||
|
||||
push @list, $name;
|
||||
|
||||
} else {
|
||||
|
||||
# Option takes argument
|
||||
|
||||
my $entry = $name;
|
||||
|
||||
$desc =~ s/\:/\\:/g;
|
||||
$entry .= "=-:$desc:";
|
||||
|
||||
if ($desc =~ /^Choices\\: ([^(]*)/) {
|
||||
my $choices = $1;
|
||||
$choices =~ s/ +$//; # strip trailing space
|
||||
$entry .= "($choices)";
|
||||
|
||||
# If "no" is one of the choices, it can also be
|
||||
# negated like a flag (--no-whatever).
|
||||
if ($choices =~ /\bno\b/) {
|
||||
$name =~ s/^--/--no-/;
|
||||
push @list, $name;
|
||||
}
|
||||
} elsif ($line =~ /\[file\]/) {
|
||||
$entry .= '->files';
|
||||
} elsif ($name =~ /^--(ao|vo|af|vf|profile|show-profile)$/) {
|
||||
$entry .= "->$1";
|
||||
}
|
||||
push @list, $entry;
|
||||
}
|
||||
}
|
||||
|
||||
# Sort longest first, because zsh won't complete an option listed
|
||||
# after one that's a prefix of it.
|
||||
@list = sort {
|
||||
$a =~ /([^=]*)/; my $ma = $1;
|
||||
$b =~ /([^=]*)/; my $mb = $1;
|
||||
|
||||
length($mb) <=> length($ma)
|
||||
} @list;
|
||||
|
||||
return @list;
|
||||
}
|
||||
|
||||
sub parse_opts {
|
||||
my ($cmd, $regex, $parsing_main_options) = @_;
|
||||
my ($cmd, $regex) = @_;
|
||||
|
||||
my @list;
|
||||
my @lines = split /\n/, `$cmd`;
|
||||
@@ -163,40 +228,14 @@ sub parse_opts {
|
||||
|
||||
my $entry = $1;
|
||||
|
||||
if ($parsing_main_options) {
|
||||
$entry .= '=-';
|
||||
}
|
||||
|
||||
if (defined $2) {
|
||||
my $desc = $2;
|
||||
$desc =~ s/\:/\\:/g;
|
||||
|
||||
$entry .= ':' . $desc;
|
||||
}
|
||||
|
||||
if ($parsing_main_options) {
|
||||
$entry .= ':';
|
||||
|
||||
$entry .= '->ao' if ($1 eq '--ao');
|
||||
$entry .= '->vo' if ($1 eq '--vo');
|
||||
$entry .= '->af' if ($1 eq '--af');
|
||||
$entry .= '->vf' if ($1 eq '--vf');
|
||||
$entry .= '->profiles' if ($1 eq '--profile');
|
||||
$entry .= '->profile' if ($1 eq '--show-profile');
|
||||
$entry .= '->files' if ($line =~ /\[file\]/);
|
||||
}
|
||||
|
||||
push @list, $entry
|
||||
}
|
||||
|
||||
if ($parsing_main_options) {
|
||||
@list = sort {
|
||||
$a =~ /(.*?)\:/; my $ma = $1;
|
||||
$b =~ /(.*?)\:/; my $mb = $1;
|
||||
|
||||
length($mb) <=> length($ma)
|
||||
} @list;
|
||||
}
|
||||
|
||||
return @list;
|
||||
}
|
||||
@@ -85,6 +85,7 @@ static const char *const std_layout_names[][2] = {
|
||||
{"7.1(alsa)", "fl-fr-bl-br-fc-lfe-sl-sr"}, // not in lavc
|
||||
{"7.1(wide)", "fl-fr-fc-lfe-bl-br-flc-frc"},
|
||||
{"7.1(wide-side)", "fl-fr-fc-lfe-flc-frc-sl-sr"},
|
||||
{"7.1(rear)", "fl-fr-fc-lfe-bl-br-sdl-sdr"},
|
||||
{"octagonal", "fl-fr-fc-bl-br-bc-sl-sr"},
|
||||
{"downmix", "dl-dr"},
|
||||
{"auto", ""}, // not in lavc
|
||||
@@ -374,6 +375,29 @@ void mp_chmap_get_reorder(int dst[MP_NUM_CHANNELS], const struct mp_chmap *from,
|
||||
assert(to->speaker[dst[n]] == from->speaker[n]);
|
||||
}
|
||||
|
||||
// Performs the difference between a and b, and store it in diff. If b has
|
||||
// channels that do not appear in a, those will not appear in the difference.
|
||||
// To get to those the argument ordering in the function call has to be
|
||||
// inverted. For the same reason, the diff with a superset will return no
|
||||
// speakers.
|
||||
void mp_chmap_diff(const struct mp_chmap *a, const struct mp_chmap *b,
|
||||
struct mp_chmap *diff)
|
||||
{
|
||||
uint64_t a_mask = mp_chmap_to_lavc_unchecked(a);
|
||||
uint64_t b_mask = mp_chmap_to_lavc_unchecked(b);
|
||||
mp_chmap_from_lavc(diff, (a_mask ^ b_mask) & a_mask);
|
||||
}
|
||||
|
||||
// Checks whether a contains all the speakers in b
|
||||
bool mp_chmap_contains(const struct mp_chmap *a, const struct mp_chmap *b)
|
||||
{
|
||||
struct mp_chmap d1;
|
||||
struct mp_chmap d2;
|
||||
mp_chmap_diff(a, b, &d1);
|
||||
mp_chmap_diff(b, a, &d2);
|
||||
return a->num >= b->num && d1.num >= 0 && d2.num == 0;
|
||||
}
|
||||
|
||||
// Returns something like "fl-fr-fc". If there's a standard layout in lavc
|
||||
// order, return that, e.g. "3.0" instead of "fl-fr-fc".
|
||||
// Unassigned but valid speakers get names like "sp28".
|
||||
|
||||
@@ -120,6 +120,11 @@ void mp_chmap_reorder_to_lavc(struct mp_chmap *map);
|
||||
void mp_chmap_get_reorder(int dst[MP_NUM_CHANNELS], const struct mp_chmap *from,
|
||||
const struct mp_chmap *to);
|
||||
|
||||
void mp_chmap_diff(const struct mp_chmap *a, const struct mp_chmap *b,
|
||||
struct mp_chmap *diff);
|
||||
|
||||
bool mp_chmap_contains(const struct mp_chmap *a, const struct mp_chmap *b);
|
||||
|
||||
char *mp_chmap_to_str_buf(char *buf, size_t buf_size, const struct mp_chmap *src);
|
||||
#define mp_chmap_to_str(m) mp_chmap_to_str_buf((char[64]){0}, 64, (m))
|
||||
|
||||
|
||||
+110
-10
@@ -17,6 +17,7 @@
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "chmap_sel.h"
|
||||
@@ -81,14 +82,21 @@ void mp_chmap_sel_add_alsa_def(struct mp_chmap_sel *s)
|
||||
}
|
||||
}
|
||||
|
||||
#define ARRAY_LEN(x) (sizeof(x) / sizeof((x)[0]))
|
||||
|
||||
// Add a channel map that should be allowed.
|
||||
void mp_chmap_sel_add_map(struct mp_chmap_sel *s, const struct mp_chmap *map)
|
||||
{
|
||||
assert(s->num_chmaps < ARRAY_LEN(s->chmaps));
|
||||
if (mp_chmap_is_valid(map))
|
||||
s->chmaps[s->num_chmaps++] = *map;
|
||||
if (!mp_chmap_is_valid(map))
|
||||
return;
|
||||
if (!s->chmaps)
|
||||
s->chmaps = s->chmaps_storage;
|
||||
if (s->num_chmaps == MP_ARRAY_SIZE(s->chmaps_storage)) {
|
||||
if (!s->tmp)
|
||||
return;
|
||||
s->chmaps = talloc_memdup(s->tmp, s->chmaps, sizeof(s->chmaps_storage));
|
||||
}
|
||||
if (s->chmaps != s->chmaps_storage)
|
||||
MP_TARRAY_GROW(s->tmp, s->chmaps, s->num_chmaps);
|
||||
s->chmaps[s->num_chmaps++] = *map;
|
||||
}
|
||||
|
||||
// Allow all waveext formats in default order.
|
||||
@@ -176,18 +184,110 @@ bool mp_chmap_sel_adjust(const struct mp_chmap_sel *s, struct mp_chmap *map)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (mp_chmap_sel_fallback(s, map))
|
||||
return true;
|
||||
|
||||
// Fallback to mono/stereo as last resort
|
||||
if (map->num == 1) {
|
||||
*map = (struct mp_chmap) MP_CHMAP_INIT_MONO;
|
||||
} else if (map->num >= 2) {
|
||||
*map = (struct mp_chmap) MP_CHMAP_INIT_STEREO;
|
||||
}
|
||||
*map = (struct mp_chmap) MP_CHMAP_INIT_STEREO;
|
||||
if (test_layout(s, map))
|
||||
return true;
|
||||
*map = (struct mp_chmap) MP_CHMAP_INIT_MONO;
|
||||
if (test_layout(s, map))
|
||||
return true;
|
||||
*map = (struct mp_chmap) {0};
|
||||
return false;
|
||||
}
|
||||
|
||||
#define UPMIX_IDX 0
|
||||
#define DOWNMIX_IDX 1
|
||||
#define FALLBACK_IDX 2
|
||||
|
||||
static bool test_fallbacks(struct mp_chmap *a, struct mp_chmap *b,
|
||||
int best_diffs[2], struct mp_chmap best[2])
|
||||
{
|
||||
struct mp_chmap diff1, diff2;
|
||||
|
||||
mp_chmap_diff(a, b, &diff1);
|
||||
if (mp_chmap_contains(a, b) && best_diffs[UPMIX_IDX] > diff1.num) {
|
||||
best[UPMIX_IDX] = *a;
|
||||
best_diffs[UPMIX_IDX] = diff1.num;
|
||||
return true;
|
||||
}
|
||||
|
||||
mp_chmap_diff(b, a, &diff2);
|
||||
if (mp_chmap_contains(b, a) && best_diffs[DOWNMIX_IDX] > diff2.num) {
|
||||
best[DOWNMIX_IDX] = *a;
|
||||
best_diffs[DOWNMIX_IDX] = diff2.num;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (diff1.num > 0 && best_diffs[FALLBACK_IDX] > diff1.num) {
|
||||
best[FALLBACK_IDX] = *a;
|
||||
best_diffs[FALLBACK_IDX] = diff1.num;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (diff2.num > 0 && best_diffs[FALLBACK_IDX] > diff2.num) {
|
||||
best[FALLBACK_IDX] = *a;
|
||||
best_diffs[FALLBACK_IDX] = diff2.num;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Determine which channel map to fallback to given a source channel map. It
|
||||
// uses the following heuristic:
|
||||
// 1) If mono is required always prefer stereo to a multichannel upmix.
|
||||
// 2) Search for an upmix that is an exact superset of the required chmap.
|
||||
// 3) Search for a downmix that is the exact subset of the required chmap.
|
||||
// 4) Search for either an upmix or downmix that is the closest (minimum
|
||||
// difference of speakers) to the required chmap.
|
||||
bool mp_chmap_sel_fallback(const struct mp_chmap_sel *s, struct mp_chmap *map)
|
||||
{
|
||||
// special case: if possible always fallback mono to stereo (instead of
|
||||
// looking for a multichannel upmix)
|
||||
struct mp_chmap mono = MP_CHMAP_INIT_MONO;
|
||||
struct mp_chmap stereo = MP_CHMAP_INIT_STEREO;
|
||||
if (mp_chmap_equals(&mono, map) && test_layout(s, &stereo)) {
|
||||
*map = stereo;
|
||||
return true;
|
||||
}
|
||||
|
||||
int best_diffs[] = { INT_MAX, INT_MAX, INT_MAX };
|
||||
struct mp_chmap best[] = { {0}, {0}, {0} };
|
||||
|
||||
for (int n = 0; n < s->num_chmaps; n++) {
|
||||
struct mp_chmap e = s->chmaps[n];
|
||||
|
||||
if (mp_chmap_is_unknown(&e))
|
||||
continue;
|
||||
|
||||
if (test_fallbacks(&e, map, best_diffs, best))
|
||||
continue;
|
||||
|
||||
// in case we didn't match any fallback retry after replacing speakers
|
||||
for (int i = 0; i < MP_ARRAY_SIZE(speaker_replacements); i++) {
|
||||
struct mp_chmap t = e;
|
||||
struct mp_chmap *r = (struct mp_chmap *)speaker_replacements[i];
|
||||
if (replace_speakers(&t, r)) {
|
||||
if (test_fallbacks(&t, map, best_diffs, best))
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = UPMIX_IDX; i < MP_ARRAY_SIZE(best); i++) {
|
||||
if (best_diffs[i] < INT_MAX) {
|
||||
*map = best[i];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set map to a default layout with num channels. Used for audio APIs that
|
||||
// return a channel count as part of format negotiation, but give no
|
||||
// information about the channel layout.
|
||||
|
||||
+6
-1
@@ -26,8 +26,12 @@ struct mp_chmap_sel {
|
||||
// should be considered opaque
|
||||
bool allow_any, allow_waveext;
|
||||
bool speakers[MP_SPEAKER_ID_COUNT];
|
||||
struct mp_chmap chmaps[20];
|
||||
struct mp_chmap *chmaps;
|
||||
int num_chmaps;
|
||||
|
||||
struct mp_chmap chmaps_storage[20];
|
||||
|
||||
void *tmp; // set to any talloc context to allow more chmaps entries
|
||||
};
|
||||
|
||||
void mp_chmap_sel_add_any(struct mp_chmap_sel *s);
|
||||
@@ -37,6 +41,7 @@ void mp_chmap_sel_add_alsa_def(struct mp_chmap_sel *s);
|
||||
void mp_chmap_sel_add_map(struct mp_chmap_sel *s, const struct mp_chmap *map);
|
||||
void mp_chmap_sel_add_speaker(struct mp_chmap_sel *s, int id);
|
||||
bool mp_chmap_sel_adjust(const struct mp_chmap_sel *s, struct mp_chmap *map);
|
||||
bool mp_chmap_sel_fallback(const struct mp_chmap_sel *s, struct mp_chmap *map);
|
||||
bool mp_chmap_sel_get_def(const struct mp_chmap_sel *s, struct mp_chmap *map,
|
||||
int num);
|
||||
|
||||
|
||||
@@ -136,7 +136,7 @@ static int init(struct dec_audio *da, const char *decoder)
|
||||
if (sh->lav_headers)
|
||||
mp_copy_lav_codec_headers(lavc_context, sh->lav_headers);
|
||||
|
||||
mp_set_avcodec_threads(lavc_context, opts->threads);
|
||||
mp_set_avcodec_threads(da->log, lavc_context, opts->threads);
|
||||
|
||||
/* open it */
|
||||
if (avcodec_open2(lavc_context, lavc_codec, NULL) < 0) {
|
||||
|
||||
@@ -280,8 +280,13 @@ static inline void update_ch(af_hrtf_t *s, short *in, const int k)
|
||||
}
|
||||
|
||||
/* We need to update the bass compensation delay line, too. */
|
||||
s->ba_l[k] = in[0] + in[4] + in[2];
|
||||
s->ba_r[k] = in[4] + in[1] + in[3];
|
||||
// TODO: should this use lf/cf/rf etc. instead?
|
||||
s->ba_l[k] = in[0];
|
||||
s->ba_r[k] = in[1];
|
||||
if (s->decode_mode == HRTF_MIX_51) {
|
||||
s->ba_l[k] += in[4] + in[2];
|
||||
s->ba_r[k] += in[4] + in[3];
|
||||
}
|
||||
}
|
||||
|
||||
static void clear_coeff(af_hrtf_t *s, float *c)
|
||||
|
||||
@@ -76,8 +76,12 @@ static int control(struct af_instance *af, int cmd, void *arg)
|
||||
gain += s->rgain_preamp;
|
||||
af_from_dB(1, &gain, &s->rgain, 20.0, -200.0, 60.0);
|
||||
|
||||
if (!s->rgain_clip) // clipping prevention
|
||||
MP_VERBOSE(af, "Applying replay-gain: %f\n", s->rgain);
|
||||
|
||||
if (!s->rgain_clip) { // clipping prevention
|
||||
s->rgain = MPMIN(s->rgain, 1.0 / peak);
|
||||
MP_VERBOSE(af, "...with clipping prevention: %f\n", s->rgain);
|
||||
}
|
||||
}
|
||||
if (s->detach && fabs(s->level * s->rgain - 1.0) < 0.00001)
|
||||
return AF_DETACH;
|
||||
|
||||
@@ -45,6 +45,11 @@ int af_fmt2bits(int format)
|
||||
return af_fmt2bps(format) * 8;
|
||||
}
|
||||
|
||||
bool af_fmt_is_float(int format)
|
||||
{
|
||||
return !!(format & AF_FORMAT_F);
|
||||
}
|
||||
|
||||
static int bits_to_mask(int bits)
|
||||
{
|
||||
switch (bits) {
|
||||
|
||||
@@ -109,6 +109,7 @@ const char *af_fmt_to_str(int format);
|
||||
|
||||
int af_fmt2bps(int format);
|
||||
int af_fmt2bits(int format);
|
||||
bool af_fmt_is_float(int format);
|
||||
int af_fmt_change_bits(int format, int bits);
|
||||
|
||||
int af_fmt_to_planar(int format);
|
||||
|
||||
+2
-2
@@ -36,7 +36,7 @@ struct mixer {
|
||||
struct ao *ao;
|
||||
struct af_stream *af;
|
||||
// Static, dependent on ao/softvol settings
|
||||
bool softvol; // use AO (true) or af_volume (false)
|
||||
bool softvol; // use AO (false) or af_volume (true)
|
||||
bool ao_softvol; // AO has private or per-app volume
|
||||
bool emulate_mute; // if true, emulate mute with volume=0
|
||||
// Last known values (possibly out of sync with reality)
|
||||
@@ -72,7 +72,7 @@ bool mixer_audio_initialized(struct mixer *mixer)
|
||||
float mixer_getneutralvolume(struct mixer *mixer)
|
||||
{
|
||||
// gain == 1
|
||||
return 1.0 / mixer->opts->softvol_max * 100.0 * 100.0;
|
||||
return mixer->softvol ? 1.0 / mixer->opts->softvol_max * 100.0 * 100.0 : 100;
|
||||
}
|
||||
|
||||
static void checkvolume(struct mixer *mixer)
|
||||
|
||||
+8
-2
@@ -89,11 +89,11 @@ static const struct ao_driver * const audio_out_drivers[] = {
|
||||
#if HAVE_SNDIO
|
||||
&audio_out_sndio,
|
||||
#endif
|
||||
&audio_out_null,
|
||||
// should not be auto-selected:
|
||||
#if HAVE_COREAUDIO
|
||||
&audio_out_coreaudio_exclusive,
|
||||
#endif
|
||||
&audio_out_null,
|
||||
// should not be auto-selected:
|
||||
&audio_out_pcm,
|
||||
#if HAVE_ENCODING
|
||||
&audio_out_lavc,
|
||||
@@ -415,6 +415,12 @@ void ao_request_reload(struct ao *ao)
|
||||
bool ao_chmap_sel_adjust(struct ao *ao, const struct mp_chmap_sel *s,
|
||||
struct mp_chmap *map)
|
||||
{
|
||||
if (mp_msg_test(ao->log, MSGL_DEBUG)) {
|
||||
for (int i = 0; i < s->num_chmaps; i++) {
|
||||
struct mp_chmap c = s->chmaps[i];
|
||||
MP_DBG(ao, "chmap_sel #%d: %s\n", i, mp_chmap_to_str(&c));
|
||||
}
|
||||
}
|
||||
return mp_chmap_sel_adjust(s, map);
|
||||
}
|
||||
|
||||
|
||||
+42
-9
@@ -279,7 +279,7 @@ static int find_alsa_channel(int mp_channel)
|
||||
static bool query_chmaps(struct ao *ao, struct mp_chmap *chmap)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
struct mp_chmap_sel chmap_sel = {0};
|
||||
struct mp_chmap_sel chmap_sel = {.tmp = p};
|
||||
|
||||
snd_pcm_chmap_query_t **maps = snd_pcm_query_chmaps(p->alsa);
|
||||
if (!maps)
|
||||
@@ -461,17 +461,22 @@ static int init(struct ao *ao)
|
||||
if (strcmp(device, "default") != 0)
|
||||
device = talloc_asprintf(ao, "plug:%s", device);
|
||||
}
|
||||
const char *old_dev = device;
|
||||
if (ao->device)
|
||||
device = ao->device;
|
||||
if (p->cfg_device && p->cfg_device[0])
|
||||
device = p->cfg_device;
|
||||
bool user_set_device = device != old_dev; // not strcmp()
|
||||
|
||||
MP_VERBOSE(ao, "using device: %s\n", device);
|
||||
MP_VERBOSE(ao, "using ALSA version: %s\n", snd_asoundlib_version());
|
||||
|
||||
err = try_open_device(ao, device, p->cfg_block ? 0 : SND_PCM_NONBLOCK);
|
||||
if (err < 0) {
|
||||
if (err != -EBUSY && !p->cfg_block) {
|
||||
if (err == -EBUSY && !user_set_device && strcmp(device, "default") != 0) {
|
||||
MP_WARN(ao, "Device '%s' busy, retrying default.\n", device);
|
||||
err = try_open_device(ao, "default", 0);
|
||||
} else if (err != -EBUSY && !p->cfg_block) {
|
||||
MP_WARN(ao, "Open in nonblock-mode "
|
||||
"failed, trying to open in block-mode.\n");
|
||||
err = try_open_device(ao, device, 0);
|
||||
@@ -534,7 +539,9 @@ static int init(struct ao *ao)
|
||||
CHECK_ALSA_ERROR("Unable to set access type");
|
||||
|
||||
struct mp_chmap dev_chmap = ao->channels;
|
||||
if (query_chmaps(ao, &dev_chmap)) {
|
||||
if (AF_FORMAT_IS_IEC61937(ao->format)) {
|
||||
dev_chmap.num = 0; // disable chmap API
|
||||
} else if (query_chmaps(ao, &dev_chmap)) {
|
||||
ao->channels = dev_chmap;
|
||||
} else {
|
||||
dev_chmap.num = 0;
|
||||
@@ -593,16 +600,23 @@ static int init(struct ao *ao)
|
||||
for (int c = 0; c < dev_chmap.num; c++)
|
||||
alsa_chmap->pos[c] = find_alsa_channel(dev_chmap.speaker[c]);
|
||||
|
||||
// mpv and ALSA use different conventions for mono
|
||||
if (dev_chmap.num == 1 && dev_chmap.speaker[0] == MP_SP(FC))
|
||||
alsa_chmap->pos[0] = SND_CHMAP_MONO;
|
||||
|
||||
char tmp[128];
|
||||
if (snd_pcm_chmap_print(alsa_chmap, sizeof(tmp), tmp) > 0)
|
||||
MP_VERBOSE(ao, "trying to set ALSA channel map: %s\n", tmp);
|
||||
|
||||
err = snd_pcm_set_chmap(p->alsa, alsa_chmap);
|
||||
if (err == -ENXIO) {
|
||||
MP_WARN(ao, "Device does not support requested channel map\n");
|
||||
MP_WARN(ao, "Device does not support requested channel map (%s)\n",
|
||||
mp_chmap_to_str(&dev_chmap));
|
||||
} else {
|
||||
CHECK_ALSA_WARN("Channel map setup failed");
|
||||
}
|
||||
|
||||
free(alsa_chmap);
|
||||
}
|
||||
|
||||
snd_pcm_chmap_t *alsa_chmap = snd_pcm_get_chmap(p->alsa);
|
||||
@@ -617,7 +631,9 @@ static int init(struct ao *ao)
|
||||
|
||||
MP_VERBOSE(ao, "which we understand as: %s\n", mp_chmap_to_str(&chmap));
|
||||
|
||||
if (mp_chmap_is_valid(&chmap)) {
|
||||
if (AF_FORMAT_IS_IEC61937(ao->format)) {
|
||||
MP_VERBOSE(ao, "using spdif passthrough; ignoring the channel map.\n");
|
||||
} else if (mp_chmap_is_valid(&chmap)) {
|
||||
if (mp_chmap_equals(&chmap, &ao->channels)) {
|
||||
MP_VERBOSE(ao, "which is what we requested.\n");
|
||||
} else if (chmap.num == ao->channels.num) {
|
||||
@@ -630,6 +646,10 @@ static int init(struct ao *ao)
|
||||
MP_WARN(ao, "Got unknown channel map from ALSA.\n");
|
||||
}
|
||||
|
||||
// mpv and ALSA use different conventions for mono
|
||||
if (ao->channels.num == 1)
|
||||
ao->channels.speaker[0] = MP_SP(FC);
|
||||
|
||||
free(alsa_chmap);
|
||||
}
|
||||
#endif
|
||||
@@ -756,7 +776,7 @@ static void audio_pause(struct ao *ao)
|
||||
alsa_error: ;
|
||||
}
|
||||
|
||||
static void audio_resume(struct ao *ao)
|
||||
static void resume_device(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
int err;
|
||||
@@ -767,6 +787,14 @@ static void audio_resume(struct ao *ao)
|
||||
while ((err = snd_pcm_resume(p->alsa)) == -EAGAIN)
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
static void audio_resume(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
int err;
|
||||
|
||||
resume_device(ao);
|
||||
|
||||
if (p->can_pause) {
|
||||
if (snd_pcm_state(p->alsa) == SND_PCM_STATE_PAUSED) {
|
||||
@@ -818,10 +846,15 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
|
||||
if (res == -EINTR || res == -EAGAIN) { /* retry */
|
||||
res = 0;
|
||||
} else if (res == -ESTRPIPE) { /* suspend */
|
||||
audio_resume(ao);
|
||||
} else if (res == -ENODEV) {
|
||||
MP_WARN(ao, "Device lost, trying to recover...\n");
|
||||
ao_request_reload(ao);
|
||||
} else if (res < 0) {
|
||||
MP_ERR(ao, "Write error: %s\n", snd_strerror(res));
|
||||
if (res == -ESTRPIPE) { /* suspend */
|
||||
resume_device(ao);
|
||||
} else {
|
||||
MP_ERR(ao, "Write error: %s\n", snd_strerror(res));
|
||||
}
|
||||
res = snd_pcm_prepare(p->alsa);
|
||||
int err = res;
|
||||
CHECK_ALSA_ERROR("pcm prepare error");
|
||||
|
||||
+146
-71
@@ -173,32 +173,83 @@ coreaudio_error:
|
||||
return CONTROL_ERROR;
|
||||
}
|
||||
|
||||
static bool init_chmap(struct ao *ao)
|
||||
static AudioChannelLayout* ca_query_layout(struct ao *ao, void *talloc_ctx)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
OSStatus err;
|
||||
AudioChannelLayout *layouts;
|
||||
size_t n_layouts;
|
||||
uint32_t psize;
|
||||
AudioChannelLayout *r = NULL;
|
||||
|
||||
err = CA_GET_ARY_O(p->device,
|
||||
kAudioDevicePropertyPreferredChannelLayout,
|
||||
&layouts, &n_layouts);
|
||||
CHECK_CA_ERROR("could not get audio device prefered layouts");
|
||||
AudioObjectPropertyAddress p_addr = (AudioObjectPropertyAddress) {
|
||||
.mSelector = kAudioDevicePropertyPreferredChannelLayout,
|
||||
.mScope = kAudioDevicePropertyScopeOutput,
|
||||
.mElement = kAudioObjectPropertyElementWildcard,
|
||||
};
|
||||
|
||||
struct mp_chmap_sel chmap_sel = {0};
|
||||
for (int i = 0; i < n_layouts; i++) {
|
||||
struct mp_chmap chmap = {0};
|
||||
if (ca_layout_to_mp_chmap(ao, &layouts[i], &chmap))
|
||||
mp_chmap_sel_add_map(&chmap_sel, &chmap);
|
||||
err = AudioObjectGetPropertyDataSize(p->device, &p_addr, 0, NULL, &psize);
|
||||
CHECK_CA_ERROR("could not get device preferred layout (size)");
|
||||
|
||||
r = talloc_size(talloc_ctx, psize);
|
||||
|
||||
err = AudioObjectGetPropertyData(p->device, &p_addr, 0, NULL, &psize, r);
|
||||
CHECK_CA_ERROR("could not get device preferred layout (get)");
|
||||
|
||||
coreaudio_error:
|
||||
return r;
|
||||
}
|
||||
|
||||
static AudioChannelLayout* ca_query_stereo_layout(struct ao *ao, void *talloc_ctx)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
OSStatus err;
|
||||
const int nch = 2;
|
||||
uint32_t channels[nch];
|
||||
AudioChannelLayout *r = NULL;
|
||||
|
||||
AudioObjectPropertyAddress p_addr = (AudioObjectPropertyAddress) {
|
||||
.mSelector = kAudioDevicePropertyPreferredChannelsForStereo,
|
||||
.mScope = kAudioDevicePropertyScopeOutput,
|
||||
.mElement = kAudioObjectPropertyElementWildcard,
|
||||
};
|
||||
|
||||
uint32_t psize = sizeof(channels);
|
||||
err = AudioObjectGetPropertyData(p->device, &p_addr, 0, NULL, &psize, channels);
|
||||
CHECK_CA_ERROR("could not get device preferred stereo layout");
|
||||
|
||||
psize = sizeof(AudioChannelLayout) + nch * sizeof(AudioChannelDescription);
|
||||
r = talloc_zero_size(talloc_ctx, psize);
|
||||
r->mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions;
|
||||
r->mNumberChannelDescriptions = nch;
|
||||
|
||||
AudioChannelDescription desc = {0};
|
||||
desc.mChannelFlags = kAudioChannelFlags_AllOff;
|
||||
|
||||
for(int i = 0; i < nch; i++) {
|
||||
desc.mChannelLabel = channels[i];
|
||||
r->mChannelDescriptions[i] = desc;
|
||||
}
|
||||
|
||||
talloc_free(layouts);
|
||||
coreaudio_error:
|
||||
return r;
|
||||
}
|
||||
|
||||
if (ao->channels.num < 3) {
|
||||
struct mp_chmap chmap;
|
||||
mp_chmap_from_channels(&chmap, ao->channels.num);
|
||||
static bool init_chmap(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
void *ta_ctx = talloc_new(NULL);
|
||||
|
||||
struct mp_chmap_sel chmap_sel = {.tmp = p};
|
||||
struct mp_chmap chmap = {0};
|
||||
|
||||
AudioChannelLayout *ml = ca_query_layout(ao, ta_ctx);
|
||||
if (ml && ca_layout_to_mp_chmap(ao, ml, &chmap))
|
||||
mp_chmap_sel_add_map(&chmap_sel, &chmap);
|
||||
}
|
||||
|
||||
AudioChannelLayout *sl = ca_query_stereo_layout(ao, ta_ctx);
|
||||
if (sl && ca_layout_to_mp_chmap(ao, sl, &chmap))
|
||||
mp_chmap_sel_add_map(&chmap_sel, &chmap);
|
||||
|
||||
talloc_free(ta_ctx);
|
||||
|
||||
if (!ao_chmap_sel_adjust(ao, &chmap_sel, &ao->channels)) {
|
||||
MP_ERR(ao, "could not select a suitable channel map among the "
|
||||
@@ -336,7 +387,7 @@ static const int speaker_map[][2] = {
|
||||
{ kAudioChannelLabel_HeadphonesLeft, MP_SPEAKER_ID_DL },
|
||||
{ kAudioChannelLabel_HeadphonesRight, MP_SPEAKER_ID_DR },
|
||||
|
||||
{ kAudioChannelLabel_Unknown, -1 },
|
||||
{ kAudioChannelLabel_Unknown, MP_SPEAKER_ID_UNKNOWN0 },
|
||||
};
|
||||
|
||||
static int ca_label_to_mp_speaker_id(AudioChannelLabel label)
|
||||
@@ -347,86 +398,110 @@ static int ca_label_to_mp_speaker_id(AudioChannelLabel label)
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void ca_log_layout(struct ao *ao, AudioChannelLayout *layout)
|
||||
static void ca_log_layout(struct ao *ao, int l, AudioChannelLayout *layout)
|
||||
{
|
||||
if (!mp_msg_test(ao->log, MSGL_V))
|
||||
if (!mp_msg_test(ao->log, l))
|
||||
return;
|
||||
|
||||
AudioChannelDescription *descs = layout->mChannelDescriptions;
|
||||
|
||||
MP_VERBOSE(ao, "layout: tag: <%d>, bitmap: <%d>, "
|
||||
"descriptions <%d>\n",
|
||||
layout->mChannelLayoutTag,
|
||||
layout->mChannelBitmap,
|
||||
layout->mNumberChannelDescriptions);
|
||||
mp_msg(ao->log, l, "layout: tag: <%u>, bitmap: <%u>, "
|
||||
"descriptions <%u>\n",
|
||||
(unsigned) layout->mChannelLayoutTag,
|
||||
(unsigned) layout->mChannelBitmap,
|
||||
(unsigned) layout->mNumberChannelDescriptions);
|
||||
|
||||
for (int i = 0; i < layout->mNumberChannelDescriptions; i++) {
|
||||
AudioChannelDescription d = descs[i];
|
||||
MP_VERBOSE(ao, " - description %d: label <%d, %d>, flags: <%u>, "
|
||||
"coords: <%f, %f, %f>\n", i,
|
||||
d.mChannelLabel,
|
||||
ca_label_to_mp_speaker_id(d.mChannelLabel),
|
||||
d.mChannelFlags,
|
||||
d.mCoordinates[0],
|
||||
d.mCoordinates[1],
|
||||
d.mCoordinates[2]);
|
||||
mp_msg(ao->log, l, " - description %d: label <%u, %u>, "
|
||||
" flags: <%u>, coords: <%f, %f, %f>\n", i,
|
||||
(unsigned) d.mChannelLabel,
|
||||
(unsigned) ca_label_to_mp_speaker_id(d.mChannelLabel),
|
||||
(unsigned) d.mChannelFlags,
|
||||
d.mCoordinates[0],
|
||||
d.mCoordinates[1],
|
||||
d.mCoordinates[2]);
|
||||
}
|
||||
}
|
||||
|
||||
static AudioChannelLayout *ca_layout_to_custom_layout(
|
||||
struct ao *ao, void *talloc_ctx, AudioChannelLayout *l)
|
||||
{
|
||||
AudioChannelLayoutTag tag = l->mChannelLayoutTag;
|
||||
AudioChannelLayout *r;
|
||||
OSStatus err;
|
||||
|
||||
if (tag == kAudioChannelLayoutTag_UseChannelBitmap) {
|
||||
uint32_t psize;
|
||||
err = AudioFormatGetPropertyInfo(
|
||||
kAudioFormatProperty_ChannelLayoutForBitmap,
|
||||
sizeof(uint32_t), &l->mChannelBitmap, &psize);
|
||||
CHECK_CA_ERROR("failed to convert channel bitmap to descriptions (info)");
|
||||
r = talloc_size(NULL, psize);
|
||||
err = AudioFormatGetProperty(
|
||||
kAudioFormatProperty_ChannelLayoutForBitmap,
|
||||
sizeof(uint32_t), &l->mChannelBitmap, &psize, r);
|
||||
CHECK_CA_ERROR("failed to convert channel bitmap to descriptions (get)");
|
||||
} else if (tag != kAudioChannelLayoutTag_UseChannelDescriptions) {
|
||||
uint32_t psize;
|
||||
err = AudioFormatGetPropertyInfo(
|
||||
kAudioFormatProperty_ChannelLayoutForTag,
|
||||
sizeof(AudioChannelLayoutTag), &l->mChannelLayoutTag, &psize);
|
||||
r = talloc_size(NULL, psize);
|
||||
CHECK_CA_ERROR("failed to convert channel tag to descriptions (info)");
|
||||
err = AudioFormatGetProperty(
|
||||
kAudioFormatProperty_ChannelLayoutForTag,
|
||||
sizeof(AudioChannelLayoutTag), &l->mChannelLayoutTag, &psize, r);
|
||||
CHECK_CA_ERROR("failed to convert channel tag to descriptions (get)");
|
||||
} else {
|
||||
r = l;
|
||||
}
|
||||
|
||||
return r;
|
||||
coreaudio_error:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool ca_layout_to_mp_chmap(struct ao *ao, AudioChannelLayout *layout,
|
||||
struct mp_chmap *chmap)
|
||||
{
|
||||
AudioChannelLayoutTag tag = layout->mChannelLayoutTag;
|
||||
uint32_t layout_size = sizeof(layout);
|
||||
OSStatus err;
|
||||
void *talloc_ctx = talloc_new(NULL);
|
||||
|
||||
if (tag == kAudioChannelLayoutTag_UseChannelBitmap) {
|
||||
err = AudioFormatGetProperty(kAudioFormatProperty_ChannelLayoutForBitmap,
|
||||
sizeof(uint32_t),
|
||||
&layout->mChannelBitmap,
|
||||
&layout_size,
|
||||
layout);
|
||||
CHECK_CA_ERROR("failed to convert channel bitmap to descriptions");
|
||||
} else if (tag != kAudioChannelLayoutTag_UseChannelDescriptions) {
|
||||
err = AudioFormatGetProperty(kAudioFormatProperty_ChannelLayoutForTag,
|
||||
sizeof(AudioChannelLayoutTag),
|
||||
&layout->mChannelLayoutTag,
|
||||
&layout_size,
|
||||
layout);
|
||||
CHECK_CA_ERROR("failed to convert channel tag to descriptions");
|
||||
MP_DBG(ao, "input channel layout:\n");
|
||||
ca_log_layout(ao, MSGL_DEBUG, layout);
|
||||
|
||||
AudioChannelLayout *l = ca_layout_to_custom_layout(ao, talloc_ctx, layout);
|
||||
if (!l)
|
||||
goto coreaudio_error;
|
||||
|
||||
MP_VERBOSE(ao, "converted input channel layout:\n");
|
||||
ca_log_layout(ao, MSGL_V, l);
|
||||
|
||||
if (l->mNumberChannelDescriptions > MP_NUM_CHANNELS) {
|
||||
MP_VERBOSE(ao, "layout has too many descriptions (%u, max: %d)\n",
|
||||
(unsigned) l->mNumberChannelDescriptions, MP_NUM_CHANNELS);
|
||||
return false;
|
||||
}
|
||||
|
||||
ca_log_layout(ao, layout);
|
||||
|
||||
// If the channel layout uses channel descriptions, from my
|
||||
// experiments there are there three possibile cases:
|
||||
// * The description has a label kAudioChannelLabel_Unknown:
|
||||
// Can't do anything about this (looks like non surround
|
||||
// layouts are like this).
|
||||
// * The description uses positional information: this in
|
||||
// theory could be used but one would have to map spatial
|
||||
// positions to labels which is not really feasible.
|
||||
// * The description has a well known label which can be mapped
|
||||
// to the waveextensible definition: this is the kind of
|
||||
// descriptions we process here.
|
||||
|
||||
for (int n = 0; n < layout->mNumberChannelDescriptions; n++) {
|
||||
AudioChannelLabel label = layout->mChannelDescriptions[n].mChannelLabel;
|
||||
for (int n = 0; n < l->mNumberChannelDescriptions; n++) {
|
||||
AudioChannelLabel label = l->mChannelDescriptions[n].mChannelLabel;
|
||||
uint8_t speaker = ca_label_to_mp_speaker_id(label);
|
||||
if (label == kAudioChannelLabel_Unknown)
|
||||
continue;
|
||||
if (speaker < 0) {
|
||||
MP_VERBOSE(ao, "channel label=%d unusable to build channel "
|
||||
"bitmap, skipping layout\n", label);
|
||||
MP_VERBOSE(ao, "channel label=%u unusable to build channel "
|
||||
"bitmap, skipping layout\n", (unsigned) label);
|
||||
goto coreaudio_error;
|
||||
} else {
|
||||
chmap->speaker[n] = speaker;
|
||||
chmap->num = n + 1;
|
||||
}
|
||||
}
|
||||
|
||||
talloc_free(talloc_ctx);
|
||||
return chmap->num > 0;
|
||||
coreaudio_error:
|
||||
ca_log_layout(ao, layout);
|
||||
MP_VERBOSE(ao, "converted input channel layout (failed):\n");
|
||||
ca_log_layout(ao, MSGL_V, layout);
|
||||
talloc_free(talloc_ctx);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ OSStatus ca_get_ary(AudioObjectID id, ca_scope scope, ca_sel selector,
|
||||
err = AudioObjectGetPropertyDataSize(id, &p_addr, 0, NULL, &p_size);
|
||||
CHECK_CA_ERROR_SILENT_L(coreaudio_error);
|
||||
|
||||
*data = talloc_size(NULL, p_size);
|
||||
*data = talloc_zero_size(NULL, p_size);
|
||||
*elements = p_size / element_size;
|
||||
|
||||
err = ca_get(id, scope, selector, p_size, *data);
|
||||
@@ -87,7 +87,7 @@ OSStatus ca_get_str(AudioObjectID id, ca_scope scope, ca_sel selector,
|
||||
CFStringGetMaximumSizeForEncoding(
|
||||
CFStringGetLength(string), CA_CFSTR_ENCODING) + 1;
|
||||
|
||||
*data = talloc_size(NULL, size);
|
||||
*data = talloc_zero_size(NULL, size);
|
||||
CFStringGetCString(string, *data, size, CA_CFSTR_ENCODING);
|
||||
CFRelease(string);
|
||||
coreaudio_error:
|
||||
|
||||
@@ -65,7 +65,8 @@ coreaudio_error:
|
||||
OSStatus ca_select_device(struct ao *ao, char* name, AudioDeviceID *device)
|
||||
{
|
||||
OSStatus err = noErr;
|
||||
int selection = name ? strtol(name, (char **)NULL, 10) : -1;
|
||||
errno = 0;
|
||||
int selection = name && name[0] ? strtol(name, (char **)NULL, 10) : -1;
|
||||
if (errno == EINVAL || errno == ERANGE) {
|
||||
selection = -1;
|
||||
MP_WARN(ao, "device identifier '%s' is invalid\n", name);
|
||||
|
||||
@@ -228,7 +228,7 @@ static int LoadDirectSound(struct ao *ao)
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
// initialize directsound
|
||||
p->hdsound_dll = LoadLibrary("DSOUND.DLL");
|
||||
p->hdsound_dll = LoadLibrary(L"DSOUND.DLL");
|
||||
if (p->hdsound_dll == NULL) {
|
||||
MP_ERR(ao, "cannot load DSOUND.DLL\n");
|
||||
return 0;
|
||||
|
||||
+8
-5
@@ -38,6 +38,7 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "options/options.h"
|
||||
#include "common/common.h"
|
||||
#include "common/msg.h"
|
||||
#include "osdep/timer.h"
|
||||
#include "osdep/endian.h"
|
||||
@@ -107,11 +108,11 @@ static const struct mp_chmap oss_layouts[MP_NUM_CHANNELS + 1] = {
|
||||
#endif
|
||||
|
||||
#if !defined(AFMT_U32_NE) && defined(AFMT_U32_LE) && defined(AFMT_U32_BE)
|
||||
#define AFMT_U32MP_SELECT_LE_BE(AFMT_U32_LE, AFMT_U32_BE)
|
||||
#define AFMT_U32_NE AFMT_U32MP_SELECT_LE_BE(AFMT_U32_LE, AFMT_U32_BE)
|
||||
#endif
|
||||
|
||||
#if !defined(AFMT_S32_NE) && defined(AFMT_S32_LE) && defined(AFMT_S32_BE)
|
||||
#define AFMT_S32MP_SELECT_LE_BE(AFMT_S32_LE, AFMT_S32_BE)
|
||||
#define AFMT_S32_NE AFMT_S32MP_SELECT_LE_BE(AFMT_S32_LE, AFMT_S32_BE)
|
||||
#endif
|
||||
|
||||
static const int format_table[][2] = {
|
||||
@@ -305,14 +306,16 @@ static int reopen_device(struct ao *ao, bool allow_format_changes)
|
||||
p->audio_fd = open(p->dsp, O_WRONLY);
|
||||
#endif
|
||||
if (p->audio_fd < 0) {
|
||||
MP_ERR(ao, "Can't open audio device %s: %s\n", p->dsp, strerror(errno));
|
||||
MP_ERR(ao, "Can't open audio device %s: %s\n",
|
||||
p->dsp, mp_strerror(errno));
|
||||
goto fail;
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
/* Remove the non-blocking flag */
|
||||
if (fcntl(p->audio_fd, F_SETFL, 0) < 0) {
|
||||
MP_ERR(ao, "Can't make file descriptor blocking: %s\n", strerror(errno));
|
||||
MP_ERR(ao, "Can't make file descriptor blocking: %s\n",
|
||||
mp_strerror(errno));
|
||||
goto fail;
|
||||
}
|
||||
#endif
|
||||
@@ -434,7 +437,7 @@ static int init(struct ao *ao)
|
||||
|
||||
if ((fd = open(p->oss_mixer_device, O_RDONLY)) == -1) {
|
||||
MP_ERR(ao, "Can't open mixer device %s: %s\n",
|
||||
p->oss_mixer_device, strerror(errno));
|
||||
p->oss_mixer_device, mp_strerror(errno));
|
||||
} else {
|
||||
ioctl(fd, SOUND_MIXER_READ_DEVMASK, &devs);
|
||||
close(fd);
|
||||
|
||||
@@ -86,8 +86,12 @@ static void stream_state_cb(pa_stream *s, void *userdata)
|
||||
struct ao *ao = userdata;
|
||||
struct priv *priv = ao->priv;
|
||||
switch (pa_stream_get_state(s)) {
|
||||
case PA_STREAM_READY:
|
||||
case PA_STREAM_FAILED:
|
||||
MP_VERBOSE(ao, "Stream failed.\n");
|
||||
ao_request_reload(ao);
|
||||
pa_threaded_mainloop_signal(priv->mainloop, 0);
|
||||
break;
|
||||
case PA_STREAM_READY:
|
||||
case PA_STREAM_TERMINATED:
|
||||
pa_threaded_mainloop_signal(priv->mainloop, 0);
|
||||
break;
|
||||
@@ -409,6 +413,9 @@ static int init(struct ao *ao)
|
||||
&format, 1, proplist)))
|
||||
goto unlock_and_fail;
|
||||
|
||||
pa_format_info_free(format);
|
||||
format = NULL;
|
||||
|
||||
pa_proplist_free(proplist);
|
||||
proplist = NULL;
|
||||
|
||||
@@ -782,7 +789,6 @@ const struct ao_driver audio_out_pulse = {
|
||||
.priv_size = sizeof(struct priv),
|
||||
.priv_defaults = &(const struct priv) {
|
||||
.cfg_buffer = 250,
|
||||
.cfg_latency_hacks = 1,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_STRING("host", cfg_host, 0),
|
||||
|
||||
+51
-38
@@ -17,9 +17,6 @@
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#define COBJMACROS 1
|
||||
#define _WIN32_WINNT 0x600
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <inttypes.h>
|
||||
#include <process.h>
|
||||
@@ -36,34 +33,39 @@
|
||||
#include "osdep/timer.h"
|
||||
#include "osdep/io.h"
|
||||
|
||||
static double get_device_delay(struct wasapi_state *state) {
|
||||
static HRESULT get_device_delay(struct wasapi_state *state, double *delay) {
|
||||
UINT64 sample_count = atomic_load(&state->sample_count);
|
||||
UINT64 position, qpc_position;
|
||||
HRESULT hr;
|
||||
|
||||
switch (hr = IAudioClock_GetPosition(state->pAudioClock, &position, &qpc_position)) {
|
||||
case S_OK: case S_FALSE:
|
||||
break;
|
||||
default:
|
||||
MP_ERR(state, "IAudioClock::GetPosition returned %s (0x%"PRIx32")\n",
|
||||
wasapi_explain_err(hr), (uint32_t)hr);
|
||||
hr = IAudioClock_GetPosition(state->pAudioClock, &position, &qpc_position);
|
||||
/* GetPosition succeeded, but the result may be inaccurate due to the length of the call */
|
||||
/* http://msdn.microsoft.com/en-us/library/windows/desktop/dd370889%28v=vs.85%29.aspx */
|
||||
if (hr == S_FALSE) {
|
||||
MP_DBG(state, "Possibly inaccurate device position.\n");
|
||||
hr = S_OK;
|
||||
}
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
LARGE_INTEGER qpc_count;
|
||||
QueryPerformanceCounter(&qpc_count);
|
||||
double qpc_diff = (qpc_count.QuadPart * 1e7 / state->qpc_frequency.QuadPart) - qpc_position;
|
||||
|
||||
position += state->clock_frequency * (uint64_t)(qpc_diff / 1e7);
|
||||
position += state->clock_frequency * (uint64_t) (qpc_diff / 1e7);
|
||||
|
||||
/* convert position to the same base as sample_count */
|
||||
position = position * state->format.Format.nSamplesPerSec / state->clock_frequency;
|
||||
|
||||
double diff = sample_count - position;
|
||||
double delay = diff / state->format.Format.nSamplesPerSec;
|
||||
*delay = diff / state->format.Format.nSamplesPerSec;
|
||||
|
||||
MP_TRACE(state, "Device delay: %g samples (%g ms)\n", diff, delay * 1000);
|
||||
MP_TRACE(state, "Device delay: %g samples (%g ms)\n", diff, *delay * 1000);
|
||||
|
||||
return delay;
|
||||
return S_OK;
|
||||
exit_label:
|
||||
MP_ERR(state, "Error getting device delay: %s (0x%"PRIx32")\n",
|
||||
wasapi_explain_err(hr), (uint32_t) hr);
|
||||
return hr;
|
||||
}
|
||||
|
||||
static void thread_feed(struct ao *ao)
|
||||
@@ -81,6 +83,9 @@ static void thread_feed(struct ao *ao)
|
||||
frame_count -= padding;
|
||||
MP_TRACE(ao, "Frame to fill: %"PRIu32". Padding: %"PRIu32"\n", frame_count, padding);
|
||||
}
|
||||
double delay;
|
||||
hr = get_device_delay(state, &delay);
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
BYTE *pData;
|
||||
hr = IAudioRenderClient_GetBuffer(state->pRenderClient,
|
||||
@@ -88,9 +93,10 @@ static void thread_feed(struct ao *ao)
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
BYTE *data[1] = {pData};
|
||||
|
||||
ao_read_data(ao, (void**)data, frame_count, (int64_t) (
|
||||
mp_time_us() + get_device_delay(state) * 1e6 +
|
||||
frame_count * 1e6 / state->format.Format.nSamplesPerSec));
|
||||
mp_time_us() + delay * 1e6 +
|
||||
frame_count * 1e6 / state->format.Format.nSamplesPerSec));
|
||||
|
||||
hr = IAudioRenderClient_ReleaseBuffer(state->pRenderClient,
|
||||
frame_count, 0);
|
||||
@@ -101,7 +107,7 @@ static void thread_feed(struct ao *ao)
|
||||
return;
|
||||
exit_label:
|
||||
MP_ERR(state, "Error feeding audio: %s (0x%"PRIx32")\n",
|
||||
wasapi_explain_err(hr), (uint32_t)hr);
|
||||
wasapi_explain_err(hr), (uint32_t) hr);
|
||||
MP_VERBOSE(ao, "Requesting ao reload\n");
|
||||
ao_request_reload(ao);
|
||||
return;
|
||||
@@ -110,30 +116,24 @@ exit_label:
|
||||
static DWORD __stdcall ThreadLoop(void *lpParameter)
|
||||
{
|
||||
struct ao *ao = lpParameter;
|
||||
if (!ao || !ao->priv)
|
||||
return -1;
|
||||
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
|
||||
int thread_ret;
|
||||
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
|
||||
|
||||
state->init_ret = wasapi_thread_init(ao);
|
||||
SetEvent(state->init_done);
|
||||
if (state->init_ret != S_OK) {
|
||||
thread_ret = -1;
|
||||
if (state->init_ret != S_OK)
|
||||
goto exit_label;
|
||||
}
|
||||
|
||||
|
||||
DWORD waitstatus;
|
||||
HANDLE playcontrol[] =
|
||||
{state->hUninit, state->hFeed, state->hForceFeed, NULL};
|
||||
MP_DBG(ao, "Entering dispatch loop\n");
|
||||
while (1) { /* watch events */
|
||||
while (true) { /* watch events */
|
||||
waitstatus = MsgWaitForMultipleObjects(3, playcontrol, FALSE, INFINITE,
|
||||
QS_POSTMESSAGE | QS_SENDMESSAGE);
|
||||
switch (waitstatus) {
|
||||
case WAIT_OBJECT_0: /*shutdown*/
|
||||
thread_ret = 0;
|
||||
MP_DBG(ao, "Thread shutdown\n");
|
||||
goto exit_label;
|
||||
case (WAIT_OBJECT_0 + 1): /* feed */
|
||||
thread_feed(ao);
|
||||
@@ -143,11 +143,11 @@ static DWORD __stdcall ThreadLoop(void *lpParameter)
|
||||
SetEvent(state->hFeedDone);
|
||||
break;
|
||||
case (WAIT_OBJECT_0 + 3): /* messages to dispatch (COM marshalling) */
|
||||
MP_DBG(ao, "Dispatch\n");
|
||||
wasapi_dispatch();
|
||||
break;
|
||||
default:
|
||||
MP_ERR(ao, "Unhandled case in thread loop");
|
||||
thread_ret = -1;
|
||||
goto exit_label;
|
||||
}
|
||||
}
|
||||
@@ -155,7 +155,8 @@ exit_label:
|
||||
wasapi_thread_uninit(ao);
|
||||
|
||||
CoUninitialize();
|
||||
return thread_ret;
|
||||
MP_DBG(ao, "Thread return\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void closehandles(struct ao *ao)
|
||||
@@ -174,32 +175,40 @@ static void uninit(struct ao *ao)
|
||||
MP_DBG(ao, "Uninit wasapi\n");
|
||||
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
|
||||
wasapi_release_proxies(state);
|
||||
SetEvent(state->hUninit);
|
||||
if (state->hUninit)
|
||||
SetEvent(state->hUninit);
|
||||
/* wait up to 10 seconds */
|
||||
if (WaitForSingleObject(state->threadLoop, 10000) == WAIT_TIMEOUT){
|
||||
MP_ERR(ao, "Audio loop thread refuses to abort");
|
||||
if (WaitForSingleObject(state->threadLoop, 10000) == WAIT_TIMEOUT) {
|
||||
MP_ERR(ao, "Audio loop thread refuses to abort\n");
|
||||
return;
|
||||
}
|
||||
if (state->VistaBlob.hAvrt)
|
||||
FreeLibrary(state->VistaBlob.hAvrt);
|
||||
closehandles(ao);
|
||||
CoUninitialize();
|
||||
MP_DBG(ao, "Uninit wasapi done\n");
|
||||
}
|
||||
|
||||
static int init(struct ao *ao)
|
||||
{
|
||||
MP_DBG(ao, "Init wasapi\n");
|
||||
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
|
||||
|
||||
ao->format = af_fmt_from_planar(ao->format);
|
||||
struct mp_chmap_sel sel = {0};
|
||||
mp_chmap_sel_add_waveext(&sel);
|
||||
if (!ao_chmap_sel_adjust(ao, &sel, &ao->channels))
|
||||
if (!ao_chmap_sel_adjust(ao, &sel, &ao->channels)) {
|
||||
MP_ERR(ao, "Error adjusting channel map to waveext channel order\n");
|
||||
return -1;
|
||||
}
|
||||
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
|
||||
state->log = ao->log;
|
||||
wasapi_fill_VistaBlob(state);
|
||||
if(!wasapi_fill_VistaBlob(state))
|
||||
MP_WARN(ao, "Error loading thread priority functions\n");
|
||||
|
||||
if (state->opt_list) {
|
||||
wasapi_enumerate_devices(state->log, NULL, NULL);
|
||||
if(!wasapi_enumerate_devices(state->log, NULL, NULL))
|
||||
MP_WARN(ao, "Error enumerating devices\n");
|
||||
}
|
||||
|
||||
if (state->opt_exclusive) {
|
||||
@@ -216,16 +225,18 @@ static int init(struct ao *ao)
|
||||
if (!state->init_done || !state->hFeed || !state->hUninit ||
|
||||
!state->hForceFeed || !state->hFeedDone)
|
||||
{
|
||||
closehandles(ao);
|
||||
MP_ERR(ao, "Error initing events\n");
|
||||
uninit(ao);
|
||||
/* failed to init events */
|
||||
return -1;
|
||||
}
|
||||
|
||||
state->init_ret = E_FAIL;
|
||||
state->threadLoop = (HANDLE)CreateThread(NULL, 0, &ThreadLoop, ao, 0, NULL);
|
||||
state->threadLoop = (HANDLE) CreateThread(NULL, 0, &ThreadLoop, ao, 0, NULL);
|
||||
if (!state->threadLoop) {
|
||||
/* failed to init thread */
|
||||
MP_ERR(ao, "Failed to create thread\n");
|
||||
uninit(ao);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -321,7 +332,6 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void audio_reset(struct ao *ao)
|
||||
{
|
||||
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
|
||||
@@ -342,7 +352,10 @@ static void audio_resume(struct ao *ao)
|
||||
|
||||
static void list_devs(struct ao *ao, struct ao_device_list *list)
|
||||
{
|
||||
wasapi_enumerate_devices(mp_null_log, ao, list);
|
||||
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
|
||||
if(!wasapi_enumerate_devices(mp_null_log, ao, list))
|
||||
MP_WARN(ao, "Error enumerating devices\n");
|
||||
CoUninitialize();
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct wasapi_state
|
||||
|
||||
@@ -20,9 +20,6 @@
|
||||
#ifndef MP_AO_WASAPI_H_
|
||||
#define MP_AO_WASAPI_H_
|
||||
|
||||
#define COBJMACROS 1
|
||||
#define _WIN32_WINNT 0x600
|
||||
|
||||
#include <audioclient.h>
|
||||
#include <audiopolicy.h>
|
||||
#include <mmdeviceapi.h>
|
||||
@@ -106,7 +103,10 @@ typedef struct wasapi_state {
|
||||
int opt_list;
|
||||
char *opt_device;
|
||||
|
||||
/* We still need to support XP, don't use these functions directly, blob owned by main thread */
|
||||
/* Don't use these functions directly in case
|
||||
they are unimplemented for some reason.
|
||||
(XP shouldn't be an issue since it doesn't support wasapi, maybe wine?)
|
||||
Blob is owned by the main thread */
|
||||
struct {
|
||||
HMODULE hAvrt;
|
||||
HANDLE (WINAPI *pAvSetMmThreadCharacteristicsW)(LPCWSTR, LPDWORD);
|
||||
|
||||
@@ -17,9 +17,6 @@
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#define COBJMACROS 1
|
||||
#define _WIN32_WINNT 0x600
|
||||
|
||||
#include <initguid.h>
|
||||
#include <audioclient.h>
|
||||
#include <endpointvolume.h>
|
||||
@@ -30,50 +27,9 @@
|
||||
#include "ao_wasapi.h"
|
||||
#include "ao_wasapi_utils.h"
|
||||
|
||||
static int GUID_compare(const GUID *l, const GUID *r)
|
||||
{
|
||||
unsigned int i;
|
||||
if (l->Data1 != r->Data1) return 1;
|
||||
if (l->Data2 != r->Data2) return 1;
|
||||
if (l->Data3 != r->Data3) return 1;
|
||||
for (i = 0; i < 8; i++) {
|
||||
if (l->Data4[i] != r->Data4[i]) return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int PKEY_compare(const PROPERTYKEY *l, const PROPERTYKEY *r)
|
||||
{
|
||||
if (GUID_compare(&l->fmtid, &r->fmtid)) return 1;
|
||||
if (l->pid != r->pid) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char *GUID_to_str_buf(char *buf, size_t buf_size, const GUID *guid)
|
||||
{
|
||||
snprintf(buf, buf_size,
|
||||
"{%8.8x-%4.4x-%4.4x-%2.2x%2.2x-%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x}",
|
||||
(unsigned) guid->Data1, guid->Data2, guid->Data3,
|
||||
guid->Data4[0], guid->Data4[1],
|
||||
guid->Data4[2], guid->Data4[3],
|
||||
guid->Data4[4], guid->Data4[5],
|
||||
guid->Data4[6], guid->Data4[7]);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static char *PKEY_to_str_buf(char *buf, size_t buf_size, const PROPERTYKEY *pkey)
|
||||
{
|
||||
buf = GUID_to_str_buf(buf, buf_size, &pkey->fmtid);
|
||||
size_t guid_len = strnlen(buf, buf_size);
|
||||
snprintf(buf + guid_len, buf_size - guid_len, ",%"PRIu32, (uint32_t)pkey->pid );
|
||||
return buf;
|
||||
}
|
||||
|
||||
#define PKEY_to_str(pkey) PKEY_to_str_buf((char[42]){0}, 42, (pkey))
|
||||
|
||||
static char* ERole_to_str(ERole role)
|
||||
{
|
||||
switch(role){
|
||||
switch (role) {
|
||||
case eConsole: return "console";
|
||||
case eMultimedia: return "multimedia";
|
||||
case eCommunications: return "communications";
|
||||
@@ -83,7 +39,7 @@ static char* ERole_to_str(ERole role)
|
||||
|
||||
static char* EDataFlow_to_str(EDataFlow flow)
|
||||
{
|
||||
switch(flow){
|
||||
switch (flow) {
|
||||
case eRender: return "render";
|
||||
case eCapture: return "capture";
|
||||
case eAll: return "all";
|
||||
@@ -95,8 +51,8 @@ static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_QueryInterface(
|
||||
IMMNotificationClient* This, REFIID riid, void **ppvObject)
|
||||
{
|
||||
/* Compatible with IMMNotificationClient and IUnknown */
|
||||
if (!GUID_compare(&IID_IMMNotificationClient, riid) ||
|
||||
!GUID_compare(&IID_IUnknown, riid)) {
|
||||
if (IsEqualGUID(&IID_IMMNotificationClient, riid) ||
|
||||
IsEqualGUID(&IID_IUnknown, riid)) {
|
||||
*ppvObject = (void *)This;
|
||||
return S_OK;
|
||||
} else {
|
||||
@@ -127,7 +83,7 @@ static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnDeviceStateChanged(
|
||||
change_notify *change = (change_notify *)This;
|
||||
struct ao *ao = change->ao;
|
||||
|
||||
if (pwstrDeviceId && !wcscmp(change->monitored, pwstrDeviceId)){
|
||||
if (pwstrDeviceId && !wcscmp(change->monitored, pwstrDeviceId)) {
|
||||
switch (dwNewState) {
|
||||
case DEVICE_STATE_DISABLED:
|
||||
case DEVICE_STATE_NOTPRESENT:
|
||||
@@ -187,8 +143,7 @@ static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnDefaultDeviceChanged(
|
||||
MP_VERBOSE(ao, "New default device %S\n", pwstrDeviceId);
|
||||
|
||||
/* don't care about "eCapture" or non-"eMultimedia" roles */
|
||||
if ( flow == eCapture ||
|
||||
role != eMultimedia ) return S_OK;
|
||||
if (flow == eCapture || role != eMultimedia) return S_OK;
|
||||
|
||||
/* stay on the device the user specified */
|
||||
if (state->opt_device) {
|
||||
@@ -197,7 +152,7 @@ static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnDefaultDeviceChanged(
|
||||
}
|
||||
|
||||
/* don't reload if already on the new default */
|
||||
if ( pwstrDeviceId && !wcscmp(change->monitored, pwstrDeviceId) ){
|
||||
if (pwstrDeviceId && !wcscmp(change->monitored, pwstrDeviceId)) {
|
||||
MP_VERBOSE(ao, "Already using default device, no reload required\n");
|
||||
return S_OK;
|
||||
}
|
||||
@@ -219,12 +174,12 @@ static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnPropertyValueChanged(
|
||||
if (pwstrDeviceId && !wcscmp(change->monitored, pwstrDeviceId)) {
|
||||
MP_VERBOSE(ao, "OnPropertyValueChanged triggered\n");
|
||||
MP_VERBOSE(ao, "Changed property: ");
|
||||
if (!PKEY_compare(&PKEY_AudioEngine_DeviceFormat, &key)) {
|
||||
if (IsEqualPropertyKey(PKEY_AudioEngine_DeviceFormat, key)) {
|
||||
MP_VERBOSE(change->ao,
|
||||
"PKEY_AudioEngine_DeviceFormat - requesting ao reload\n");
|
||||
ao_request_reload(change->ao);
|
||||
} else {
|
||||
MP_VERBOSE(ao, "%s\n", PKEY_to_str(&key));
|
||||
MP_VERBOSE(ao, "%s\n", mp_PKEY_to_str(&key));
|
||||
}
|
||||
}
|
||||
return S_OK;
|
||||
@@ -276,7 +231,7 @@ void wasapi_change_uninit(struct ao *ao)
|
||||
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
|
||||
struct change_notify *change = &state->change;
|
||||
|
||||
if( state->pEnumerator && change->client.lpVtbl )
|
||||
if(state->pEnumerator && change->client.lpVtbl)
|
||||
IMMDeviceEnumerator_UnregisterEndpointNotificationCallback(
|
||||
state->pEnumerator, (IMMNotificationClient *)change);
|
||||
|
||||
|
||||
+312
-261
@@ -17,10 +17,8 @@
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#define COBJMACROS 1
|
||||
#define _WIN32_WINNT 0x600
|
||||
|
||||
#include <math.h>
|
||||
#include <libavutil/common.h>
|
||||
#include <initguid.h>
|
||||
#include <audioclient.h>
|
||||
#include <endpointvolume.h>
|
||||
@@ -35,46 +33,67 @@
|
||||
|
||||
#define MIXER_DEFAULT_LABEL L"mpv - video player"
|
||||
|
||||
#ifndef PKEY_Device_FriendlyName
|
||||
DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName,
|
||||
DEFINE_PROPERTYKEY(mp_PKEY_Device_FriendlyName,
|
||||
0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20,
|
||||
0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14);
|
||||
DEFINE_PROPERTYKEY(PKEY_Device_DeviceDesc,
|
||||
DEFINE_PROPERTYKEY(mp_PKEY_Device_DeviceDesc,
|
||||
0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20,
|
||||
0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 2);
|
||||
#endif
|
||||
|
||||
/* Supposed to use __uuidof, but it is C++ only, declare our own */
|
||||
static const GUID local_KSDATAFORMAT_SUBTYPE_PCM = {
|
||||
0x1, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
|
||||
};
|
||||
static const GUID local_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = {
|
||||
0x3, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
|
||||
};
|
||||
DEFINE_GUID(mp_KSDATAFORMAT_SUBTYPE_PCM,
|
||||
0x00000001, 0x0000, 0x0010, 0x80, 0x00,
|
||||
0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
|
||||
DEFINE_GUID(mp_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT,
|
||||
0x00000003, 0x0000, 0x0010, 0x80, 0x00,
|
||||
0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
|
||||
|
||||
union WAVEFMT {
|
||||
WAVEFORMATEX *ex;
|
||||
WAVEFORMATEXTENSIBLE *extensible;
|
||||
};
|
||||
char *mp_GUID_to_str_buf(char *buf, size_t buf_size, const GUID *guid)
|
||||
{
|
||||
snprintf(buf, buf_size,
|
||||
"{%8.8x-%4.4x-%4.4x-%2.2x%2.2x-%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x}",
|
||||
(unsigned) guid->Data1, guid->Data2, guid->Data3,
|
||||
guid->Data4[0], guid->Data4[1],
|
||||
guid->Data4[2], guid->Data4[3],
|
||||
guid->Data4[4], guid->Data4[5],
|
||||
guid->Data4[6], guid->Data4[7]);
|
||||
return buf;
|
||||
}
|
||||
|
||||
int wasapi_fill_VistaBlob(wasapi_state *state)
|
||||
char *mp_PKEY_to_str_buf(char *buf, size_t buf_size, const PROPERTYKEY *pkey)
|
||||
{
|
||||
buf = mp_GUID_to_str_buf(buf, buf_size, &pkey->fmtid);
|
||||
size_t guid_len = strnlen(buf, buf_size);
|
||||
snprintf(buf + guid_len, buf_size - guid_len, ",%"PRIu32, (uint32_t) pkey->pid);
|
||||
return buf;
|
||||
}
|
||||
|
||||
bool wasapi_fill_VistaBlob(wasapi_state *state)
|
||||
{
|
||||
if (!state)
|
||||
return 1;
|
||||
goto exit_label;
|
||||
state->VistaBlob.hAvrt = LoadLibraryW(L"avrt.dll");
|
||||
if (!state->VistaBlob.hAvrt)
|
||||
goto exit_label;
|
||||
|
||||
state->VistaBlob.pAvSetMmThreadCharacteristicsW =
|
||||
(HANDLE (WINAPI *)(LPCWSTR, LPDWORD))
|
||||
GetProcAddress(state->VistaBlob.hAvrt, "AvSetMmThreadCharacteristicsW");
|
||||
GetProcAddress(state->VistaBlob.hAvrt, "AvSetMmThreadCharacteristicsW");
|
||||
if (!state->VistaBlob.pAvSetMmThreadCharacteristicsW)
|
||||
goto exit_label;
|
||||
|
||||
state->VistaBlob.pAvRevertMmThreadCharacteristics =
|
||||
(WINBOOL (WINAPI *)(HANDLE))
|
||||
GetProcAddress(state->VistaBlob.hAvrt, "AvRevertMmThreadCharacteristics");
|
||||
return 0;
|
||||
GetProcAddress(state->VistaBlob.hAvrt, "AvRevertMmThreadCharacteristics");
|
||||
if (!state->VistaBlob.pAvRevertMmThreadCharacteristics)
|
||||
goto exit_label;
|
||||
|
||||
return true;
|
||||
exit_label:
|
||||
if (state->VistaBlob.hAvrt)
|
||||
if (state->VistaBlob.hAvrt) {
|
||||
FreeLibrary(state->VistaBlob.hAvrt);
|
||||
return 1;
|
||||
state->VistaBlob.hAvrt = NULL;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *wasapi_explain_err(const HRESULT hr)
|
||||
@@ -85,7 +104,11 @@ const char *wasapi_explain_err(const HRESULT hr)
|
||||
E(E_FAIL)
|
||||
E(E_OUTOFMEMORY)
|
||||
E(E_POINTER)
|
||||
E(E_HANDLE)
|
||||
E(E_NOTIMPL)
|
||||
E(E_INVALIDARG)
|
||||
E(REGDB_E_IIDNOTREG)
|
||||
E(CO_E_NOTINITIALIZED)
|
||||
E(AUDCLNT_E_NOT_INITIALIZED)
|
||||
E(AUDCLNT_E_ALREADY_INITIALIZED)
|
||||
E(AUDCLNT_E_WRONG_ENDPOINT_TYPE)
|
||||
@@ -123,148 +146,216 @@ const char *wasapi_explain_err(const HRESULT hr)
|
||||
#undef E
|
||||
}
|
||||
|
||||
static void set_format(WAVEFORMATEXTENSIBLE *wformat, WORD bytepersample,
|
||||
DWORD samplerate, WORD channels, DWORD chanmask)
|
||||
static void set_waveformat(WAVEFORMATEXTENSIBLE *wformat,
|
||||
WORD bits, WORD valid_bits, bool is_float,
|
||||
DWORD samplerate, struct mp_chmap *channels)
|
||||
{
|
||||
int block_align = channels * bytepersample;
|
||||
wformat->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; /* Only PCM is supported */
|
||||
wformat->Format.nChannels = channels;
|
||||
int block_align = channels->num * bits / 8;
|
||||
wformat->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
|
||||
wformat->Format.nChannels = channels->num;
|
||||
wformat->Format.nSamplesPerSec = samplerate;
|
||||
wformat->Format.nAvgBytesPerSec = samplerate * block_align;
|
||||
wformat->Format.nBlockAlign = block_align;
|
||||
wformat->Format.wBitsPerSample = bytepersample * 8;
|
||||
wformat->Format.cbSize =
|
||||
22; /* must be at least 22 for WAVE_FORMAT_EXTENSIBLE */
|
||||
if (bytepersample == 4)
|
||||
wformat->SubFormat = local_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
|
||||
wformat->Format.wBitsPerSample = bits;
|
||||
wformat->Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
|
||||
if (is_float)
|
||||
wformat->SubFormat = mp_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
|
||||
else
|
||||
wformat->SubFormat = local_KSDATAFORMAT_SUBTYPE_PCM;
|
||||
wformat->Samples.wValidBitsPerSample = wformat->Format.wBitsPerSample;
|
||||
wformat->dwChannelMask = chanmask;
|
||||
wformat->SubFormat = mp_KSDATAFORMAT_SUBTYPE_PCM;
|
||||
wformat->Samples.wValidBitsPerSample = valid_bits;
|
||||
wformat->dwChannelMask = mp_chmap_to_waveext(channels);
|
||||
}
|
||||
|
||||
static int format_set_bits(int old_format, int bits, int fp)
|
||||
static WORD waveformat_valid_bits(const WAVEFORMATEX *wf)
|
||||
{
|
||||
if (fp) {
|
||||
switch (bits) {
|
||||
case 64: return AF_FORMAT_DOUBLE;
|
||||
case 32: return AF_FORMAT_FLOAT;
|
||||
default: return 0;
|
||||
}
|
||||
if (wf->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
|
||||
WAVEFORMATEXTENSIBLE *wformat = (WAVEFORMATEXTENSIBLE *)wf;
|
||||
return wformat->Samples.wValidBitsPerSample;
|
||||
} else {
|
||||
return wf->wBitsPerSample;
|
||||
}
|
||||
|
||||
return af_fmt_change_bits(old_format, bits);
|
||||
}
|
||||
|
||||
static int set_ao_format(struct wasapi_state *state,
|
||||
struct ao *const ao,
|
||||
WAVEFORMATEXTENSIBLE wformat)
|
||||
static char *waveformat_to_str_buf(char *buf, size_t buf_size, WAVEFORMATEX *wf)
|
||||
{
|
||||
if (wformat.SubFormat.Data1 != 1 && wformat.SubFormat.Data1 != 3) {
|
||||
MP_ERR(ao, "Unknown SubFormat %"PRIu32"\n",
|
||||
(uint32_t)wformat.SubFormat.Data1);
|
||||
return 0;
|
||||
char* type = "?";
|
||||
switch (wf->wFormatTag) {
|
||||
case WAVE_FORMAT_EXTENSIBLE:
|
||||
{
|
||||
WAVEFORMATEXTENSIBLE *wformat = (WAVEFORMATEXTENSIBLE *)wf;
|
||||
if (IsEqualGUID(&mp_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT,
|
||||
&wformat->SubFormat))
|
||||
type = "float";
|
||||
else if (IsEqualGUID(&mp_KSDATAFORMAT_SUBTYPE_PCM,
|
||||
&wformat->SubFormat))
|
||||
type = "s";
|
||||
break;
|
||||
}
|
||||
case WAVE_FORMAT_IEEE_FLOAT:
|
||||
type = "float";
|
||||
break;
|
||||
case WAVE_FORMAT_PCM:
|
||||
type = "s";
|
||||
break;
|
||||
}
|
||||
int valid_bits = waveformat_valid_bits(wf);
|
||||
if (valid_bits == wf->wBitsPerSample) {
|
||||
snprintf(buf, buf_size, "%"PRIu16"ch %s%"PRIu16" @ %"PRIu32"hz",
|
||||
wf->nChannels, type, valid_bits, (unsigned) wf->nSamplesPerSec);
|
||||
} else {
|
||||
snprintf(buf, buf_size, "%"PRIu16"ch %s%"PRIu16" (in %s%"PRIu16") @ %"PRIu32"hz",
|
||||
wf->nChannels, type, valid_bits, type, wf->wBitsPerSample,
|
||||
(unsigned) wf->nSamplesPerSec);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
#define waveformat_to_str(wf) waveformat_to_str_buf((char[40]){0}, 40, (wf))
|
||||
|
||||
// .Data1 == 1 is PCM, .Data1 == 3 is IEEE_FLOAT
|
||||
int format = format_set_bits(ao->format,
|
||||
wformat.Format.wBitsPerSample, wformat.SubFormat.Data1 == 3);
|
||||
static bool waveformat_is_float(WAVEFORMATEX *wf)
|
||||
{
|
||||
switch (wf->wFormatTag) {
|
||||
case WAVE_FORMAT_EXTENSIBLE:
|
||||
{
|
||||
WAVEFORMATEXTENSIBLE *wformat = (WAVEFORMATEXTENSIBLE *)wf;
|
||||
return IsEqualGUID(&mp_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, &wformat->SubFormat);
|
||||
}
|
||||
case WAVE_FORMAT_IEEE_FLOAT:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool waveformat_is_pcm(WAVEFORMATEX *wf)
|
||||
{
|
||||
switch (wf->wFormatTag) {
|
||||
case WAVE_FORMAT_EXTENSIBLE:
|
||||
{
|
||||
WAVEFORMATEXTENSIBLE *wformat = (WAVEFORMATEXTENSIBLE *)wf;
|
||||
return IsEqualGUID(&mp_KSDATAFORMAT_SUBTYPE_PCM, &wformat->SubFormat);
|
||||
}
|
||||
case WAVE_FORMAT_PCM:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static void waveformat_copy(WAVEFORMATEXTENSIBLE* dst, WAVEFORMATEX* src)
|
||||
{
|
||||
if (src->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
|
||||
*dst = *(WAVEFORMATEXTENSIBLE *)src;
|
||||
else
|
||||
dst->Format = *src;
|
||||
}
|
||||
|
||||
static bool set_ao_format(struct ao *ao, WAVEFORMATEX *wf)
|
||||
{
|
||||
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
|
||||
// explicitly disallow 8 bits - this will catch if the
|
||||
// closestMatch returned by IsFormatSupported in try_format below returns
|
||||
// a bit depth of less than 16
|
||||
if (wf->wBitsPerSample < 16)
|
||||
return false;
|
||||
|
||||
int format;
|
||||
if (waveformat_is_float(wf)) {
|
||||
format = AF_FORMAT_FLOAT;
|
||||
} else if (waveformat_is_pcm(wf)) {
|
||||
format = AF_FORMAT_S32;
|
||||
} else {
|
||||
MP_ERR(ao, "Unknown WAVEFORMAT\n");
|
||||
return false;
|
||||
}
|
||||
// set the correct number of bits (the 32-bits assumed above may be wrong)
|
||||
format = af_fmt_change_bits(format, wf->wBitsPerSample);
|
||||
if (!format)
|
||||
return 0;
|
||||
return false;
|
||||
|
||||
ao->samplerate = wformat.Format.nSamplesPerSec;
|
||||
ao->bps = wformat.Format.nAvgBytesPerSec;
|
||||
ao->samplerate = wf->nSamplesPerSec;
|
||||
ao->bps = wf->nAvgBytesPerSec;
|
||||
ao->format = format;
|
||||
|
||||
if (ao->channels.num != wformat.Format.nChannels) {
|
||||
mp_chmap_from_channels(&ao->channels, wformat.Format.nChannels);
|
||||
if (wf->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
|
||||
WAVEFORMATEXTENSIBLE *wformat = (WAVEFORMATEXTENSIBLE *)wf;
|
||||
mp_chmap_from_waveext(&ao->channels, wformat->dwChannelMask);
|
||||
} else {
|
||||
mp_chmap_from_channels(&ao->channels, wf->nChannels);
|
||||
}
|
||||
|
||||
state->format = wformat;
|
||||
return 1;
|
||||
if (ao->channels.num != wf->nChannels) {
|
||||
MP_ERR(ao, "Channel map doesn't match number of channels\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
waveformat_copy(&state->format, wf);
|
||||
return true;
|
||||
}
|
||||
|
||||
static int try_format(struct wasapi_state *state,
|
||||
struct ao *const ao,
|
||||
int bits, int samplerate,
|
||||
const struct mp_chmap channels)
|
||||
static bool try_format(struct ao *ao,
|
||||
int bits, int valid_bits, bool is_float,
|
||||
int samplerate, struct mp_chmap *channels)
|
||||
{
|
||||
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
|
||||
/* If for some reason we get 8-bits, try 16-bit equivalent instead.
|
||||
This can happen if 8-bits is initially requested in ao->format
|
||||
or in the unlikely event that GetMixFormat return 8-bits.
|
||||
If someone with a "vintage" sound card or something complains
|
||||
this can be reconsidered */
|
||||
bits = FFMAX(16, bits);
|
||||
|
||||
WAVEFORMATEXTENSIBLE wformat;
|
||||
set_format(&wformat, bits / 8, samplerate, channels.num, mp_chmap_to_waveext(&channels));
|
||||
|
||||
int af_format = format_set_bits(ao->format, bits, bits == 32);
|
||||
if (!af_format)
|
||||
return 0;
|
||||
|
||||
MP_VERBOSE(ao, "Trying %dch %s @ %dhz\n",
|
||||
channels.num, af_fmt_to_str(af_format), samplerate);
|
||||
|
||||
union WAVEFMT u;
|
||||
u.extensible = &wformat;
|
||||
set_waveformat(&wformat, bits, valid_bits, is_float, samplerate, channels);
|
||||
MP_VERBOSE(ao, "Trying %s\n", waveformat_to_str(&wformat.Format));
|
||||
|
||||
WAVEFORMATEX *closestMatch;
|
||||
HRESULT hr = IAudioClient_IsFormatSupported(state->pAudioClient,
|
||||
state->share_mode,
|
||||
u.ex, &closestMatch);
|
||||
&wformat.Format, &closestMatch);
|
||||
|
||||
if (closestMatch) {
|
||||
if (closestMatch->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
|
||||
u.ex = closestMatch;
|
||||
wformat = *u.extensible;
|
||||
} else {
|
||||
wformat.Format = *closestMatch;
|
||||
}
|
||||
|
||||
waveformat_copy(&wformat, closestMatch);
|
||||
CoTaskMemFree(closestMatch);
|
||||
}
|
||||
|
||||
if (hr == S_FALSE) {
|
||||
if (set_ao_format(state, ao, wformat)) {
|
||||
if (set_ao_format(ao, &wformat.Format)) {
|
||||
MP_VERBOSE(ao, "Accepted as %dch %s @ %dhz\n",
|
||||
ao->channels.num, af_fmt_to_str(ao->format), ao->samplerate);
|
||||
|
||||
return 1;
|
||||
return true;
|
||||
}
|
||||
} if (hr == S_OK || (!state->opt_exclusive && hr == AUDCLNT_E_UNSUPPORTED_FORMAT)) {
|
||||
// AUDCLNT_E_UNSUPPORTED_FORMAT here means "works in shared, doesn't in exclusive"
|
||||
if (set_ao_format(state, ao, wformat)) {
|
||||
} else if (hr == S_OK) {
|
||||
if (set_ao_format(ao, &wformat.Format)) {
|
||||
MP_VERBOSE(ao, "%dch %s @ %dhz accepted\n",
|
||||
ao->channels.num, af_fmt_to_str(af_format), samplerate);
|
||||
return 1;
|
||||
ao->channels.num, af_fmt_to_str(ao->format), samplerate);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
static int try_mix_format(struct wasapi_state *state,
|
||||
struct ao *const ao)
|
||||
static bool try_mix_format(struct ao *ao)
|
||||
{
|
||||
WAVEFORMATEX *deviceFormat = NULL;
|
||||
WAVEFORMATEX *closestMatch = NULL;
|
||||
int ret = 0;
|
||||
|
||||
HRESULT hr = IAudioClient_GetMixFormat(state->pAudioClient, &deviceFormat);
|
||||
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
|
||||
WAVEFORMATEX *wf = NULL;
|
||||
HRESULT hr = IAudioClient_GetMixFormat(state->pAudioClient, &wf);
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
union WAVEFMT u;
|
||||
u.ex = deviceFormat;
|
||||
WAVEFORMATEXTENSIBLE wformat = *u.extensible;
|
||||
bool ret = try_format(ao, wf->wBitsPerSample, waveformat_valid_bits(wf),
|
||||
waveformat_is_float(wf), wf->nSamplesPerSec, &ao->channels);
|
||||
|
||||
ret = try_format(state, ao, wformat.Format.wBitsPerSample,
|
||||
wformat.Format.nSamplesPerSec, ao->channels);
|
||||
if (ret)
|
||||
state->format = wformat;
|
||||
|
||||
exit_label:
|
||||
SAFE_RELEASE(deviceFormat, CoTaskMemFree(deviceFormat));
|
||||
SAFE_RELEASE(closestMatch, CoTaskMemFree(closestMatch));
|
||||
SAFE_RELEASE(wf, CoTaskMemFree(wf));
|
||||
return ret;
|
||||
exit_label:
|
||||
MP_ERR(state, "Error getting mix format: %s (0x%"PRIx32")\n",
|
||||
wasapi_explain_err(hr), (uint32_t) hr);
|
||||
SAFE_RELEASE(wf, CoTaskMemFree(wf));
|
||||
return false;
|
||||
}
|
||||
|
||||
static int try_passthrough(struct wasapi_state *state,
|
||||
struct ao *const ao)
|
||||
static bool try_passthrough(struct ao *ao)
|
||||
{
|
||||
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
|
||||
WAVEFORMATEXTENSIBLE wformat = {
|
||||
.Format = {
|
||||
.wFormatTag = WAVE_FORMAT_EXTENSIBLE,
|
||||
@@ -277,129 +368,94 @@ static int try_passthrough(struct wasapi_state *state,
|
||||
},
|
||||
.Samples.wValidBitsPerSample = 16,
|
||||
.dwChannelMask = mp_chmap_to_waveext(&ao->channels),
|
||||
.SubFormat = local_KSDATAFORMAT_SUBTYPE_PCM,
|
||||
.SubFormat = mp_KSDATAFORMAT_SUBTYPE_PCM,
|
||||
};
|
||||
wformat.SubFormat.Data1 = WAVE_FORMAT_DOLBY_AC3_SPDIF; // see INIT_WAVEFORMATEX_GUID macro
|
||||
|
||||
union WAVEFMT u;
|
||||
u.extensible = &wformat;
|
||||
|
||||
MP_VERBOSE(ao, "Trying passthrough for %s...\n", af_fmt_to_str(ao->format));
|
||||
|
||||
HRESULT hr = IAudioClient_IsFormatSupported(state->pAudioClient,
|
||||
state->share_mode,
|
||||
u.ex, NULL);
|
||||
&wformat.Format, NULL);
|
||||
if (!FAILED(hr)) {
|
||||
ao->format = ao->format;
|
||||
state->format = wformat;
|
||||
return 1;
|
||||
return true;
|
||||
}
|
||||
return 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
static int find_formats(struct ao *const ao)
|
||||
static bool search_sample_formats(struct ao *ao, int samplerate, struct mp_chmap *channels)
|
||||
{
|
||||
// try float
|
||||
for (int bits = 64; bits; bits -= 32) {
|
||||
if (try_format(ao, bits, bits, true, samplerate, channels))
|
||||
return true;
|
||||
}
|
||||
|
||||
// try int
|
||||
// some common bit depths / container sizes (requests welcome)
|
||||
int bits[] = {32, 24, 32, 24, 32, 16};
|
||||
int valid_bits[] = {32, 24, 24, 20, 20, 16};
|
||||
for (int i = 0; i < MP_ARRAY_SIZE(bits); i++) {
|
||||
if (try_format(ao, bits[i], valid_bits[i], false, samplerate, channels))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool find_formats(struct ao *ao)
|
||||
{
|
||||
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
|
||||
|
||||
if (AF_FORMAT_IS_IEC61937(ao->format)) {
|
||||
if (try_passthrough(state, ao))
|
||||
return 0;
|
||||
if (try_passthrough(ao))
|
||||
return true;
|
||||
|
||||
MP_ERR(ao, "Couldn't use passthrough");
|
||||
if (!state->opt_exclusive)
|
||||
MP_ERR(ao, " (try exclusive mode)");
|
||||
MP_ERR(ao, "\n");
|
||||
return -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* See if the format works as-is */
|
||||
int bits = af_fmt2bits(ao->format);
|
||||
/* don't try 8bits -- there are various 8bit modes other than PCM (*-law et al);
|
||||
let's just stick to PCM or float here. */
|
||||
if (bits == 8) {
|
||||
bits = 16;
|
||||
} else if (try_format(state, ao, bits, ao->samplerate, ao->channels)) {
|
||||
return 0;
|
||||
}
|
||||
if (try_format(ao, bits, bits, af_fmt_is_float(ao->format),
|
||||
ao->samplerate, &ao->channels))
|
||||
return true;
|
||||
|
||||
if (!state->opt_exclusive) {
|
||||
/* shared mode, we can use the system default mix format. */
|
||||
if (try_mix_format(state, ao)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (try_mix_format(ao))
|
||||
return true;
|
||||
MP_WARN(ao, "Couldn't use default mix format\n");
|
||||
}
|
||||
|
||||
/* Exclusive mode, we have to guess. */
|
||||
|
||||
/* as far as testing shows, only PCM 16/24LE (44100Hz - 192kHz) is supported
|
||||
* Tested on Realtek High Definition Audio, (Realtek Semiconductor Corp. 6.0.1.6312)
|
||||
* Drivers dated 2/18/2011
|
||||
*/
|
||||
|
||||
/* try float first for non-16bit audio */
|
||||
if (bits != 16) {
|
||||
bits = 32;
|
||||
}
|
||||
|
||||
int start_bits = bits;
|
||||
while (1) { // not infinite -- returns at bottom
|
||||
for (; bits > 8; bits -= 8) {
|
||||
int samplerate = ao->samplerate;
|
||||
if (try_format(state, ao, bits, samplerate, ao->channels)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// make samplerate fit in [44100 192000]
|
||||
// we check for samplerate > 96k so that we can upsample instead of downsampling later
|
||||
if (samplerate < 44100 || samplerate > 96000) {
|
||||
if (samplerate < 44100)
|
||||
samplerate = 44100;
|
||||
if (samplerate > 96000)
|
||||
samplerate = 192000;
|
||||
|
||||
if (try_format(state, ao, bits, samplerate, ao->channels)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// try bounding to 96kHz
|
||||
if (samplerate > 48000) {
|
||||
samplerate = 96000;
|
||||
if (try_format(state, ao, bits, samplerate, ao->channels)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// try bounding to 48kHz
|
||||
if (samplerate > 44100) {
|
||||
samplerate = 48000;
|
||||
if (try_format(state, ao, bits, samplerate, ao->channels)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* How bad is this? try 44100hz, but only on 16bit */
|
||||
if (bits == 16 && samplerate != 44100) {
|
||||
samplerate = 44100;
|
||||
|
||||
if (try_format(state, ao, bits, samplerate, ao->channels)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
while (true) {
|
||||
int samplerate = ao->samplerate;
|
||||
// try integer multiples of requested sample rate first
|
||||
while (samplerate <= 384000) {
|
||||
if (search_sample_formats(ao, samplerate, &ao->channels))
|
||||
return true;
|
||||
samplerate += ao->samplerate;
|
||||
}
|
||||
|
||||
if (ao->channels.num > 6) {
|
||||
/* Maybe this is 5.1 hardware with no support for more. */
|
||||
bits = start_bits;
|
||||
mp_chmap_from_channels(&ao->channels, 6);
|
||||
} else if (ao->channels.num != 2) {
|
||||
/* Poor quality hardware? Try stereo mode, go through the list again. */
|
||||
bits = start_bits;
|
||||
mp_chmap_from_channels(&ao->channels, 2);
|
||||
// now try list of typical sample rates (requests welcome)
|
||||
int samplerates[] = {384000, 352800, 192000, 176400, 96000, 88200,
|
||||
48000, 44100, 32000, 22050, 16000, 11025,
|
||||
8000};
|
||||
for (int i = 0; i < MP_ARRAY_SIZE(samplerates); i++) {
|
||||
if (search_sample_formats(ao, samplerates[i], &ao->channels))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ao->channels.num > 1) {
|
||||
mp_chmap_from_channels(&ao->channels, ao->channels.num - 1);
|
||||
} else {
|
||||
MP_ERR(ao, "Couldn't find acceptable audio format\n");
|
||||
return -1;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -424,7 +480,7 @@ static HRESULT init_clock(struct wasapi_state *state) {
|
||||
return S_OK;
|
||||
exit_label:
|
||||
MP_ERR(state, "Error obtaining the audio device's timing: %s (0x%"PRIx32")\n",
|
||||
wasapi_explain_err(hr), (uint32_t)hr);
|
||||
wasapi_explain_err(hr), (uint32_t) hr);
|
||||
return hr;
|
||||
}
|
||||
|
||||
@@ -434,7 +490,7 @@ static HRESULT init_session_display(struct wasapi_state *state) {
|
||||
|
||||
hr = IAudioClient_GetService(state->pAudioClient,
|
||||
&IID_IAudioSessionControl,
|
||||
(void **) &state->pSessionControl);
|
||||
(void **)&state->pSessionControl);
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
GetModuleFileNameW(NULL, path, MAX_PATH);
|
||||
@@ -447,9 +503,9 @@ static HRESULT init_session_display(struct wasapi_state *state) {
|
||||
|
||||
return S_OK;
|
||||
exit_label:
|
||||
MP_ERR(state, "Error setting audio session display name: %s (0x%"PRIx32")\n",
|
||||
wasapi_explain_err(hr), (uint32_t)hr);
|
||||
return hr;
|
||||
MP_WARN(state, "Error setting audio session display name: %s (0x%"PRIx32")\n",
|
||||
wasapi_explain_err(hr), (uint32_t) hr);
|
||||
return S_OK; // No reason to abort initialization.
|
||||
}
|
||||
|
||||
static HRESULT fix_format(struct ao *ao)
|
||||
@@ -463,15 +519,16 @@ static HRESULT fix_format(struct ao *ao)
|
||||
MP_VERBOSE(state, "Device period: %.2g ms\n", (double) devicePeriod / 10000.0 );
|
||||
|
||||
/* integer multiple of device period close to 50ms */
|
||||
bufferPeriod = bufferDuration = ceil( 50.0 * 10000.0 / devicePeriod ) * devicePeriod;
|
||||
if (state->share_mode == AUDCLNT_SHAREMODE_SHARED)
|
||||
bufferPeriod = 0;
|
||||
bufferPeriod = bufferDuration = ceil(50.0 * 10000.0 / devicePeriod) * devicePeriod;
|
||||
|
||||
/* handle unsupported buffer size */
|
||||
/* hopefully this shouldn't happen because of the above integer device period */
|
||||
/* http://msdn.microsoft.com/en-us/library/windows/desktop/dd370875%28v=vs.85%29.aspx */
|
||||
int retries=0;
|
||||
reinit:
|
||||
if (state->share_mode == AUDCLNT_SHAREMODE_SHARED)
|
||||
bufferPeriod = 0;
|
||||
|
||||
MP_DBG(state, "IAudioClient::Initialize\n");
|
||||
hr = IAudioClient_Initialize(state->pAudioClient,
|
||||
state->share_mode,
|
||||
@@ -482,21 +539,18 @@ reinit:
|
||||
NULL);
|
||||
/* something about buffer sizes on Win7 */
|
||||
if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) {
|
||||
MP_VERBOSE(state, "IAudioClient::Initialize negotiation failed with %s (0x%"PRIx32"), used %lld * 100ns\n",
|
||||
wasapi_explain_err(hr), (uint32_t)hr, bufferDuration);
|
||||
if (retries > 0) {
|
||||
hr = E_FAIL;
|
||||
if (retries > 0)
|
||||
EXIT_ON_ERROR(hr);
|
||||
} else {
|
||||
else
|
||||
retries ++;
|
||||
}
|
||||
|
||||
MP_VERBOSE(state, "IAudioClient::Initialize negotiation failed with %s (0x%"PRIx32"), used %lld * 100ns\n",
|
||||
wasapi_explain_err(hr), (uint32_t) hr, bufferDuration);
|
||||
|
||||
IAudioClient_GetBufferSize(state->pAudioClient, &state->bufferFrameCount);
|
||||
bufferPeriod = bufferDuration =
|
||||
(REFERENCE_TIME)((10000.0 * 1000 / state->format.Format.nSamplesPerSec *
|
||||
state->bufferFrameCount) + 0.5);
|
||||
if (state->share_mode == AUDCLNT_SHAREMODE_SHARED)
|
||||
bufferPeriod = 0;
|
||||
(REFERENCE_TIME) ((10000.0 * 1000 / state->format.Format.nSamplesPerSec *
|
||||
state->bufferFrameCount) + 0.5);
|
||||
|
||||
IAudioClient_Release(state->pAudioClient);
|
||||
state->pAudioClient = NULL;
|
||||
@@ -516,7 +570,7 @@ reinit:
|
||||
MP_DBG(state, "IAudioClient::Initialize pAudioVolume\n");
|
||||
hr = IAudioClient_GetService(state->pAudioClient,
|
||||
&IID_ISimpleAudioVolume,
|
||||
(void **) &state->pAudioVolume);
|
||||
(void **)&state->pAudioVolume);
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
MP_DBG(state, "IAudioClient::Initialize IAudioClient_SetEventHandle\n");
|
||||
@@ -533,8 +587,8 @@ reinit:
|
||||
state->format.Format.wBitsPerSample / 8 *
|
||||
state->bufferFrameCount;
|
||||
bufferDuration =
|
||||
(REFERENCE_TIME)((10000.0 * 1000 / state->format.Format.nSamplesPerSec *
|
||||
state->bufferFrameCount) + 0.5);
|
||||
(REFERENCE_TIME) ((10000.0 * 1000 / state->format.Format.nSamplesPerSec *
|
||||
state->bufferFrameCount) + 0.5);
|
||||
MP_VERBOSE(state, "Buffer frame count: %"PRIu32" (%.2g ms)\n",
|
||||
state->bufferFrameCount, (double) bufferDuration / 10000.0 );
|
||||
|
||||
@@ -544,15 +598,18 @@ reinit:
|
||||
hr = init_session_display(state);
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
state->hTask =
|
||||
state->VistaBlob.pAvSetMmThreadCharacteristicsW(L"Pro Audio", &state->taskIndex);
|
||||
if (state->VistaBlob.hAvrt) {
|
||||
state->hTask =
|
||||
state->VistaBlob.pAvSetMmThreadCharacteristicsW(L"Pro Audio", &state->taskIndex);
|
||||
}
|
||||
|
||||
MP_VERBOSE(state, "Format fixed. Using %lld byte buffer block size\n",
|
||||
(long long) state->buffer_block_size);
|
||||
|
||||
return S_OK;
|
||||
exit_label:
|
||||
MP_ERR(state, "Error initializing device: %s (0x%"PRIx32")\n",
|
||||
wasapi_explain_err(hr), (uint32_t)hr);
|
||||
wasapi_explain_err(hr), (uint32_t) hr);
|
||||
return hr;
|
||||
}
|
||||
|
||||
@@ -594,7 +651,7 @@ static char* get_device_name(IMMDevice *pDevice) {
|
||||
PROPVARIANT devname;
|
||||
PropVariantInit(&devname);
|
||||
|
||||
hr = IPropertyStore_GetValue(pProps, &PKEY_Device_FriendlyName, &devname);
|
||||
hr = IPropertyStore_GetValue(pProps, &mp_PKEY_Device_FriendlyName, &devname);
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
namestr = mp_to_utf8(NULL, devname.pwszVal);
|
||||
@@ -619,7 +676,7 @@ static char* get_device_desc(IMMDevice *pDevice) {
|
||||
PROPVARIANT devdesc;
|
||||
PropVariantInit(&devdesc);
|
||||
|
||||
hr = IPropertyStore_GetValue(pProps, &PKEY_Device_DeviceDesc, &devdesc);
|
||||
hr = IPropertyStore_GetValue(pProps, &mp_PKEY_Device_DeviceDesc, &devdesc);
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
desc = mp_to_utf8(NULL, devdesc.pwszVal);
|
||||
@@ -724,25 +781,21 @@ exit_label:
|
||||
return hr;
|
||||
}
|
||||
|
||||
int wasapi_enumerate_devices(struct mp_log *log, struct ao *ao,
|
||||
bool wasapi_enumerate_devices(struct mp_log *log, struct ao *ao,
|
||||
struct ao_device_list *list)
|
||||
{
|
||||
HRESULT hr;
|
||||
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
|
||||
|
||||
hr = enumerate_with_state(log, ao, list, "Active devices:",
|
||||
DEVICE_STATE_ACTIVE, 1);
|
||||
EXIT_ON_ERROR(hr);
|
||||
hr = enumerate_with_state(log, ao, list, "Unplugged devices:",
|
||||
DEVICE_STATE_UNPLUGGED, 0);
|
||||
EXIT_ON_ERROR(hr);
|
||||
CoUninitialize();
|
||||
return 0;
|
||||
return true;
|
||||
exit_label:
|
||||
mp_err(log, "Error enumerating devices: %s (0x%"PRIx32")\n",
|
||||
wasapi_explain_err(hr), (uint32_t)hr);
|
||||
CoUninitialize();
|
||||
return 1;
|
||||
wasapi_explain_err(hr), (uint32_t) hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
static HRESULT load_default_device(struct ao *ao, IMMDeviceEnumerator* pEnumerator,
|
||||
@@ -760,7 +813,7 @@ static HRESULT load_default_device(struct ao *ao, IMMDeviceEnumerator* pEnumerat
|
||||
return S_OK;
|
||||
exit_label:
|
||||
MP_ERR(ao , "Error loading default device: %s (0x%"PRIx32")\n",
|
||||
wasapi_explain_err(hr), (uint32_t)hr);
|
||||
wasapi_explain_err(hr), (uint32_t) hr);
|
||||
return hr;
|
||||
}
|
||||
|
||||
@@ -869,7 +922,7 @@ static HRESULT find_and_load_device(struct ao *ao, IMMDeviceEnumerator* pEnumera
|
||||
|
||||
exit_label:
|
||||
SAFE_RELEASE(pTempDevice, IMMDevice_Release(pTempDevice));
|
||||
SAFE_RELEASE(pDevices, IMMDeviceCollection_Release(pDevices));
|
||||
SAFE_RELEASE(pDevices, IMMDeviceCollection_Release(pDevices));
|
||||
|
||||
CoTaskMemFree(deviceID);
|
||||
return hr;
|
||||
@@ -889,7 +942,7 @@ int wasapi_validate_device(struct mp_log *log, const m_option_t *opt,
|
||||
int devno = (int) strtol(param.start, &end, 10);
|
||||
|
||||
int ret = 1;
|
||||
if ((end == (void*)param.start || *end) && devno < 0)
|
||||
if ((end == (void *)param.start || *end) && devno < 0)
|
||||
ret = M_OPT_OUT_OF_RANGE;
|
||||
|
||||
mp_dbg(log, "device=%s %svalid\n", param.start, ret == 1 ? "" : "not ");
|
||||
@@ -899,10 +952,8 @@ int wasapi_validate_device(struct mp_log *log, const m_option_t *opt,
|
||||
HRESULT wasapi_setup_proxies(struct wasapi_state *state) {
|
||||
HRESULT hr;
|
||||
|
||||
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
|
||||
|
||||
#define UNMARSHAL(type, to, from) do { \
|
||||
hr = CoGetInterfaceAndReleaseStream((from), &(type), (void**) &(to)); \
|
||||
hr = CoGetInterfaceAndReleaseStream((from), &(type), (void **)&(to)); \
|
||||
(from) = NULL; \
|
||||
EXIT_ON_ERROR(hr); \
|
||||
} while (0)
|
||||
@@ -914,11 +965,10 @@ HRESULT wasapi_setup_proxies(struct wasapi_state *state) {
|
||||
|
||||
#undef UNMARSHAL
|
||||
|
||||
return S_OK;
|
||||
exit_label:
|
||||
if (hr != S_OK) {
|
||||
MP_ERR(state, "Error reading COM proxy: %s (0x%"PRIx32")\n",
|
||||
wasapi_explain_err(hr), (uint32_t)hr);
|
||||
}
|
||||
MP_ERR(state, "Error reading COM proxy: %s (0x%"PRIx32")\n",
|
||||
wasapi_explain_err(hr), (uint32_t) hr);
|
||||
return hr;
|
||||
}
|
||||
|
||||
@@ -927,8 +977,6 @@ void wasapi_release_proxies(wasapi_state *state) {
|
||||
SAFE_RELEASE(state->pAudioVolumeProxy, IUnknown_Release(state->pAudioVolumeProxy));
|
||||
SAFE_RELEASE(state->pEndpointVolumeProxy, IUnknown_Release(state->pEndpointVolumeProxy));
|
||||
SAFE_RELEASE(state->pSessionControlProxy, IUnknown_Release(state->pSessionControlProxy));
|
||||
|
||||
CoUninitialize();
|
||||
}
|
||||
|
||||
static HRESULT create_proxies(struct wasapi_state *state) {
|
||||
@@ -938,7 +986,7 @@ static HRESULT create_proxies(struct wasapi_state *state) {
|
||||
hr = CreateStreamOnHGlobal(NULL, TRUE, &(to)); \
|
||||
EXIT_ON_ERROR(hr); \
|
||||
hr = CoMarshalInterThreadInterfaceInStream(&(type), \
|
||||
(IUnknown*) (from), \
|
||||
(IUnknown *)(from), \
|
||||
&(to)); \
|
||||
EXIT_ON_ERROR(hr); \
|
||||
} while (0)
|
||||
@@ -948,13 +996,16 @@ static HRESULT create_proxies(struct wasapi_state *state) {
|
||||
MARSHAL(IID_IAudioEndpointVolume, state->sEndpointVolume, state->pEndpointVolume);
|
||||
MARSHAL(IID_IAudioSessionControl, state->sSessionControl, state->pSessionControl);
|
||||
|
||||
return S_OK;
|
||||
exit_label:
|
||||
MP_ERR(state, "Error creating COM proxy: %s (0x%"PRIx32")\n",
|
||||
wasapi_explain_err(hr), (uint32_t) hr);
|
||||
return hr;
|
||||
}
|
||||
|
||||
static void destroy_proxies(struct wasapi_state *state) {
|
||||
SAFE_RELEASE(state->sAudioClient, IUnknown_Release(state->sAudioClient));
|
||||
SAFE_RELEASE(state->sAudioVolume, IUnknown_Release(state->sAudioVolume));
|
||||
SAFE_RELEASE(state->sAudioClient, IUnknown_Release(state->sAudioClient));
|
||||
SAFE_RELEASE(state->sAudioVolume, IUnknown_Release(state->sAudioVolume));
|
||||
SAFE_RELEASE(state->sEndpointVolume, IUnknown_Release(state->sEndpointVolume));
|
||||
SAFE_RELEASE(state->sSessionControl, IUnknown_Release(state->sSessionControl));
|
||||
}
|
||||
@@ -977,7 +1028,7 @@ retry:
|
||||
state->initial_volume = -1.0;
|
||||
|
||||
hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
|
||||
&IID_IMMDeviceEnumerator, (void**)&state->pEnumerator);
|
||||
&IID_IMMDeviceEnumerator, (void **)&state->pEnumerator);
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
char *device = state->opt_device;
|
||||
@@ -1009,21 +1060,21 @@ retry:
|
||||
MP_DBG(ao, "Query hardware volume support\n");
|
||||
hr = IAudioEndpointVolume_QueryHardwareSupport(state->pEndpointVolume,
|
||||
&state->vol_hw_support);
|
||||
if ( hr != S_OK )
|
||||
MP_WARN(ao, "Query hardware volume control: %s (0x%"PRIx32")\n",
|
||||
wasapi_explain_err(hr), (uint32_t)hr);
|
||||
if (hr != S_OK)
|
||||
MP_WARN(ao, "Error querying hardware volume control: %s (0x%"PRIx32")\n",
|
||||
wasapi_explain_err(hr), (uint32_t) hr);
|
||||
|
||||
MP_DBG(ao, "Probing formats\n");
|
||||
if (find_formats(ao)){
|
||||
if (!find_formats(ao)) {
|
||||
hr = E_FAIL;
|
||||
EXIT_ON_ERROR(hr);
|
||||
}
|
||||
|
||||
MP_DBG(ao, "Fixing format\n");
|
||||
hr = fix_format(ao);
|
||||
if ( (hr == AUDCLNT_E_DEVICE_IN_USE ||
|
||||
hr == AUDCLNT_E_DEVICE_INVALIDATED) &&
|
||||
retry_wait <= 8 ) {
|
||||
if ((hr == AUDCLNT_E_DEVICE_IN_USE ||
|
||||
hr == AUDCLNT_E_DEVICE_INVALIDATED) &&
|
||||
retry_wait <= 8) {
|
||||
wasapi_thread_uninit(ao);
|
||||
MP_WARN(ao, "Retrying in %"PRId64" us\n", retry_wait);
|
||||
mp_sleep_us(retry_wait);
|
||||
@@ -1052,7 +1103,7 @@ retry:
|
||||
return S_OK;
|
||||
exit_label:
|
||||
MP_ERR(state, "Error setting up audio thread: %s (0x%"PRIx32")\n",
|
||||
wasapi_explain_err(hr), (uint32_t)hr);
|
||||
wasapi_explain_err(hr), (uint32_t) hr);
|
||||
return hr;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,12 +27,17 @@
|
||||
#include "ao.h"
|
||||
#include "internal.h"
|
||||
|
||||
int wasapi_fill_VistaBlob(wasapi_state *state);
|
||||
char *mp_GUID_to_str_buf(char *buf, size_t buf_size, const GUID *guid);
|
||||
char *mp_PKEY_to_str_buf(char *buf, size_t buf_size, const PROPERTYKEY *pkey);
|
||||
#define mp_GUID_to_str(guid) mp_GUID_to_str_buf((char[40]){0}, 40, (guid))
|
||||
#define mp_PKEY_to_str(pkey) mp_PKEY_to_str_buf((char[42]){0}, 42, (pkey))
|
||||
|
||||
bool wasapi_fill_VistaBlob(wasapi_state *state);
|
||||
|
||||
const char *wasapi_explain_err(const HRESULT hr);
|
||||
|
||||
int wasapi_enumerate_devices(struct mp_log *log, struct ao *ao,
|
||||
struct ao_device_list *list);
|
||||
bool wasapi_enumerate_devices(struct mp_log *log, struct ao *ao,
|
||||
struct ao_device_list *list);
|
||||
|
||||
int wasapi_validate_device(struct mp_log *log, const m_option_t *opt,
|
||||
struct bstr name, struct bstr param);
|
||||
|
||||
+7
-6
@@ -22,6 +22,7 @@
|
||||
#include <libavutil/dict.h>
|
||||
#include <libavutil/opt.h>
|
||||
#include <libavutil/error.h>
|
||||
#include <libavutil/cpu.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
|
||||
#include "common/common.h"
|
||||
@@ -30,8 +31,6 @@
|
||||
#include "av_common.h"
|
||||
#include "codecs.h"
|
||||
|
||||
#include "osdep/numcores.h"
|
||||
|
||||
int mp_lavc_set_extradata(AVCodecContext *avctx, void *ptr, int size)
|
||||
{
|
||||
if (size) {
|
||||
@@ -126,19 +125,21 @@ void mp_set_av_packet(AVPacket *dst, struct demux_packet *mpkt, AVRational *tb)
|
||||
dst->dts = mp_pts_to_av(mpkt ? mpkt->dts : MP_NOPTS_VALUE, tb);
|
||||
}
|
||||
|
||||
void mp_set_avcodec_threads(AVCodecContext *avctx, int threads)
|
||||
void mp_set_avcodec_threads(struct mp_log *l, AVCodecContext *avctx, int threads)
|
||||
{
|
||||
if (threads == 0) {
|
||||
threads = default_thread_count();
|
||||
threads = av_cpu_count();
|
||||
if (threads < 1) {
|
||||
av_log(avctx, AV_LOG_WARNING, "Could not determine "
|
||||
"thread count to use, defaulting to 1.\n");
|
||||
mp_warn(l, "Could not determine thread count to use, defaulting to 1.\n");
|
||||
threads = 1;
|
||||
} else {
|
||||
mp_verbose(l, "Detected %d logical cores.\n", threads);
|
||||
}
|
||||
// Apparently some libavcodec versions have or had trouble with more
|
||||
// than 16 threads, and/or print a warning when using > 16.
|
||||
threads = MPMIN(threads, 16);
|
||||
}
|
||||
mp_verbose(l, "Requesting %d threads for decoding.\n", threads);
|
||||
avctx->thread_count = threads;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ void mp_copy_lav_codec_headers(AVCodecContext *avctx, AVCodecContext *st);
|
||||
void mp_set_av_packet(AVPacket *dst, struct demux_packet *mpkt, AVRational *tb);
|
||||
int64_t mp_pts_to_av(double mp_pts, AVRational *tb);
|
||||
double mp_pts_from_av(int64_t av_pts, AVRational *tb);
|
||||
void mp_set_avcodec_threads(AVCodecContext *avctx, int threads);
|
||||
void mp_set_avcodec_threads(struct mp_log *l, AVCodecContext *avctx, int threads);
|
||||
void mp_add_lavc_decoders(struct mp_decoder_list *list, enum AVMediaType type);
|
||||
int mp_codec_to_av_codec_id(const char *codec);
|
||||
const char *mp_codec_from_av_codec_id(int codec_id);
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <assert.h>
|
||||
|
||||
#include <libavutil/common.h>
|
||||
#include <libavutil/error.h>
|
||||
|
||||
#include "talloc.h"
|
||||
#include "misc/bstr.h"
|
||||
@@ -243,3 +244,11 @@ bool mp_append_escaped_string(void *talloc_ctx, bstr *dst, bstr *src)
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Behaves like strerror()/strerror_r(), but is thread- and GNU-safe.
|
||||
char *mp_strerror_buf(char *buf, size_t buf_size, int errnum)
|
||||
{
|
||||
// This handles the nasty details of calling the right function for us.
|
||||
av_strerror(AVERROR(errnum), buf, buf_size);
|
||||
return buf;
|
||||
}
|
||||
@@ -89,4 +89,7 @@ bool mp_append_escaped_string_noalloc(void *talloc_ctx, struct bstr *dst,
|
||||
bool mp_append_escaped_string(void *talloc_ctx, struct bstr *dst,
|
||||
struct bstr *src);
|
||||
|
||||
char *mp_strerror_buf(char *buf, size_t buf_size, int errnum);
|
||||
#define mp_strerror(e) mp_strerror_buf((char[80]){0}, 80, e)
|
||||
|
||||
#endif /* MPLAYER_MPCOMMON_H */
|
||||
+3
-2
@@ -185,10 +185,11 @@ static void flush_status_line(struct mp_log_root *root)
|
||||
root->blank_lines = 0;
|
||||
}
|
||||
|
||||
void mp_msg_flush_status_line(struct mpv_global *global)
|
||||
void mp_msg_flush_status_line(struct mp_log *log)
|
||||
{
|
||||
pthread_mutex_lock(&mp_msg_lock);
|
||||
flush_status_line(global->log->root);
|
||||
if (log->root)
|
||||
flush_status_line(log->root);
|
||||
pthread_mutex_unlock(&mp_msg_lock);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,9 +9,10 @@ void mp_msg_uninit(struct mpv_global *global);
|
||||
void mp_msg_update_msglevels(struct mpv_global *global);
|
||||
void mp_msg_mute(struct mpv_global *global, bool mute);
|
||||
void mp_msg_force_stderr(struct mpv_global *global, bool force_stderr);
|
||||
void mp_msg_flush_status_line(struct mpv_global *global);
|
||||
bool mp_msg_has_status_line(struct mpv_global *global);
|
||||
|
||||
void mp_msg_flush_status_line(struct mp_log *log);
|
||||
|
||||
struct mp_log_buffer_entry {
|
||||
char *prefix;
|
||||
int level;
|
||||
|
||||
+3
-3
@@ -29,15 +29,15 @@ void mp_tags_set_bstr(struct mp_tags *tags, bstr key, bstr value)
|
||||
for (int n = 0; n < tags->num_keys; n++) {
|
||||
if (bstrcasecmp0(key, tags->keys[n]) == 0) {
|
||||
talloc_free(tags->values[n]);
|
||||
tags->values[n] = talloc_strndup(tags, value.start, value.len);
|
||||
tags->values[n] = bstrto0(tags, value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
MP_RESIZE_ARRAY(tags, tags->keys, tags->num_keys + 1);
|
||||
MP_RESIZE_ARRAY(tags, tags->values, tags->num_keys + 1);
|
||||
tags->keys[tags->num_keys] = talloc_strndup(tags, key.start, key.len);
|
||||
tags->values[tags->num_keys] = talloc_strndup(tags, value.start, value.len);
|
||||
tags->keys[tags->num_keys] = bstrto0(tags, key);
|
||||
tags->values[tags->num_keys] = bstrto0(tags, value);
|
||||
tags->num_keys++;
|
||||
}
|
||||
|
||||
|
||||
@@ -421,3 +421,20 @@ void mp_set_pcm_codec(struct sh_stream *sh, bool sign, bool is_float, int bits,
|
||||
mp_snprintf_cat(codec, sizeof(codec), is_be ? "be" : "le");
|
||||
sh->codec = talloc_strdup(sh->audio, codec);
|
||||
}
|
||||
|
||||
static const char *const mimetype_to_codec[][2] = {
|
||||
{"image/jpeg", "mjpeg"},
|
||||
{"image/png", "png"},
|
||||
{0}
|
||||
};
|
||||
|
||||
const char *mp_map_mimetype_to_video_codec(const char *mimetype)
|
||||
{
|
||||
if (mimetype) {
|
||||
for (int n = 0; mimetype_to_codec[n][0]; n++) {
|
||||
if (strcmp(mimetype_to_codec[n][0], mimetype) == 0)
|
||||
return mimetype_to_codec[n][1];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
@@ -30,4 +30,6 @@ void mp_set_codec_from_tag(struct sh_stream *sh);
|
||||
void mp_set_pcm_codec(struct sh_stream *sh, bool sign, bool is_float, int bits,
|
||||
bool is_be);
|
||||
|
||||
const char *mp_map_mimetype_to_video_codec(const char *mimetype);
|
||||
|
||||
#endif
|
||||
+1
-1
@@ -554,7 +554,7 @@ int demux_read_packet_async(struct sh_stream *sh, struct demux_packet **out_pkt)
|
||||
if (ds->in->threading) {
|
||||
pthread_mutex_lock(&ds->in->lock);
|
||||
*out_pkt = dequeue_packet(ds);
|
||||
r = *out_pkt ? 1 : (ds->eof ? -1 : 0);
|
||||
r = *out_pkt ? 1 : ((ds->eof || !ds->selected) ? -1 : 0);
|
||||
ds->active = ds->selected; // enable readahead
|
||||
ds->in->eof = false; // force retry
|
||||
pthread_cond_signal(&ds->in->wakeup); // possibly read more
|
||||
|
||||
@@ -286,6 +286,8 @@ static int lavf_check_file(demuxer_t *demuxer, enum demux_check check)
|
||||
.buf_size = 0,
|
||||
.buf = av_mallocz(PROBE_BUF_SIZE + FF_INPUT_BUFFER_PADDING_SIZE),
|
||||
};
|
||||
if (!avpd.buf)
|
||||
return -1;
|
||||
|
||||
bool final_probe = false;
|
||||
do {
|
||||
@@ -679,6 +681,8 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
return -1;
|
||||
|
||||
avfc = avformat_alloc_context();
|
||||
if (!avfc)
|
||||
return -1;
|
||||
|
||||
if (lavfdopts->cryptokey)
|
||||
parse_cryptokey(avfc, lavfdopts->cryptokey);
|
||||
|
||||
+5
-1
@@ -33,6 +33,7 @@
|
||||
#include "stream/stream.h"
|
||||
#include "demux.h"
|
||||
#include "stheader.h"
|
||||
#include "codec_tags.h"
|
||||
#include "mf.h"
|
||||
|
||||
#define MF_MAX_FILE_SIZE (1024 * 1024 * 256)
|
||||
@@ -186,7 +187,10 @@ static int demux_open_mf(demuxer_t *demuxer, enum demux_check check)
|
||||
if (!mf || mf->nr_of_files < 1)
|
||||
goto error;
|
||||
|
||||
const char *codec = probe_format(mf, demuxer->opts->mf_type, check);
|
||||
char *force_type = demuxer->opts->mf_type;
|
||||
const char *codec = mp_map_mimetype_to_video_codec(demuxer->stream->mime_type);
|
||||
if (!codec || (force_type && force_type[0]))
|
||||
codec = probe_format(mf, force_type, check);
|
||||
if (!codec)
|
||||
goto error;
|
||||
|
||||
|
||||
+46
-19
@@ -307,7 +307,7 @@ static bstr demux_mkv_decode(struct mp_log *log, mkv_track_t *track,
|
||||
dest = NULL;
|
||||
goto error;
|
||||
}
|
||||
dstlen *= 2;
|
||||
dstlen = MPMAX(1, 2 * dstlen);
|
||||
}
|
||||
size = dstlen - out_avail;
|
||||
} else if (enc->comp_algo == 3) {
|
||||
@@ -345,6 +345,10 @@ static int demux_mkv_read_info(demuxer_t *demuxer)
|
||||
if (info.n_timecode_scale) {
|
||||
mkv_d->tc_scale = info.timecode_scale;
|
||||
MP_VERBOSE(demuxer, "| + timecode scale: %" PRIu64 "\n", mkv_d->tc_scale);
|
||||
if (mkv_d->tc_scale < 1 || mkv_d->tc_scale > INT_MAX) {
|
||||
res = -1;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
if (info.n_duration) {
|
||||
mkv_d->duration = info.duration * mkv_d->tc_scale / 1e9;
|
||||
@@ -1119,7 +1123,26 @@ skip:
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void add_coverart(struct demuxer *demuxer)
|
||||
{
|
||||
for (int n = 0; n < demuxer->num_attachments; n++) {
|
||||
struct demux_attachment *att = &demuxer->attachments[n];
|
||||
const char *codec = mp_map_mimetype_to_video_codec(att->type);
|
||||
if (!codec)
|
||||
continue;
|
||||
struct sh_stream *sh = new_sh_stream(demuxer, STREAM_VIDEO);
|
||||
if (!sh)
|
||||
break;
|
||||
sh->codec = codec;
|
||||
sh->attached_picture = new_demux_packet_from(att->data, att->data_size);
|
||||
if (sh->attached_picture) {
|
||||
sh->attached_picture->pts = 0;
|
||||
talloc_steal(sh, sh->attached_picture);
|
||||
sh->attached_picture->keyframe = true;
|
||||
}
|
||||
sh->title = att->name;
|
||||
}
|
||||
}
|
||||
|
||||
static int demux_mkv_open_video(demuxer_t *demuxer, mkv_track_t *track);
|
||||
static int demux_mkv_open_audio(demuxer_t *demuxer, mkv_track_t *track);
|
||||
@@ -1148,7 +1171,6 @@ typedef struct {
|
||||
char *id;
|
||||
int fourcc;
|
||||
int extradata;
|
||||
bool parse;
|
||||
} videocodec_info_t;
|
||||
|
||||
static const videocodec_info_t vinfo[] = {
|
||||
@@ -1161,7 +1183,7 @@ static const videocodec_info_t vinfo[] = {
|
||||
{MKV_V_MPEG4_AVC, MP_FOURCC('a', 'v', 'c', '1'), 1},
|
||||
{MKV_V_THEORA, MP_FOURCC('t', 'h', 'e', 'o'), 1},
|
||||
{MKV_V_VP8, MP_FOURCC('V', 'P', '8', '0'), 0},
|
||||
{MKV_V_VP9, MP_FOURCC('V', 'P', '9', '0'), 0, true},
|
||||
{MKV_V_VP9, MP_FOURCC('V', 'P', '9', '0'), 0},
|
||||
{MKV_V_DIRAC, MP_FOURCC('d', 'r', 'a', 'c'), 0},
|
||||
{MKV_V_PRORES, MP_FOURCC('p', 'r', '0', '0'), 0},
|
||||
{MKV_V_HEVC, MP_FOURCC('H', 'E', 'V', 'C'), 1},
|
||||
@@ -1234,7 +1256,6 @@ static int demux_mkv_open_video(demuxer_t *demuxer, mkv_track_t *track)
|
||||
extradata = track->private_data;
|
||||
extradata_size = track->private_size;
|
||||
}
|
||||
track->parse = vi->parse;
|
||||
if (!vi->id) {
|
||||
MP_WARN(demuxer, "Unknown/unsupported "
|
||||
"CodecID (%s) or missing/bad CodecPrivate\n"
|
||||
@@ -1245,6 +1266,9 @@ static int demux_mkv_open_video(demuxer_t *demuxer, mkv_track_t *track)
|
||||
}
|
||||
}
|
||||
|
||||
if (sh->format == MP_FOURCC('V', 'P', '9', '0'))
|
||||
track->parse = true;
|
||||
|
||||
if (extradata_size > 0x1000000) {
|
||||
MP_WARN(demuxer, "Invalid CodecPrivate\n");
|
||||
return 1;
|
||||
@@ -1289,10 +1313,9 @@ static int demux_mkv_open_video(demuxer_t *demuxer, mkv_track_t *track)
|
||||
|
||||
static const struct mkv_audio_tag {
|
||||
char *id; bool prefix; uint32_t formattag;
|
||||
bool parse;
|
||||
} mkv_audio_tags[] = {
|
||||
{ MKV_A_MP2, 0, 0x0055 },
|
||||
{ MKV_A_MP3, 0, 0x0055, true },
|
||||
{ MKV_A_MP3, 0, 0x0055 },
|
||||
{ MKV_A_AC3, 1, 0x2000 },
|
||||
{ MKV_A_EAC3, 1, MP_FOURCC('E', 'A', 'C', '3') },
|
||||
{ MKV_A_DTS, 0, 0x2001 },
|
||||
@@ -1313,7 +1336,7 @@ static const struct mkv_audio_tag {
|
||||
{ MKV_A_QDMC, 0, MP_FOURCC('Q', 'D', 'M', 'C') },
|
||||
{ MKV_A_QDMC2, 0, MP_FOURCC('Q', 'D', 'M', '2') },
|
||||
{ MKV_A_WAVPACK, 0, MP_FOURCC('W', 'V', 'P', 'K') },
|
||||
{ MKV_A_TRUEHD, 0, MP_FOURCC('T', 'R', 'H', 'D'), true },
|
||||
{ MKV_A_TRUEHD, 0, MP_FOURCC('T', 'R', 'H', 'D') },
|
||||
{ MKV_A_FLAC, 0, MP_FOURCC('f', 'L', 'a', 'C') },
|
||||
{ MKV_A_ALAC, 0, MP_FOURCC('a', 'L', 'a', 'C') },
|
||||
{ MKV_A_REAL28, 0, MP_FOURCC('2', '8', '_', '8') },
|
||||
@@ -1374,7 +1397,6 @@ static int demux_mkv_open_audio(demuxer_t *demuxer, mkv_track_t *track)
|
||||
continue;
|
||||
}
|
||||
track->a_formattag = t->formattag;
|
||||
track->parse = t->parse;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1389,6 +1411,7 @@ static int demux_mkv_open_audio(demuxer_t *demuxer, mkv_track_t *track)
|
||||
if (track->a_formattag == 0x0055) { /* MP3 || MP2 */
|
||||
sh_a->bitrate = 16000 * 8;
|
||||
sh_a->block_align = 1152;
|
||||
track->parse = true;
|
||||
} else if ((track->a_formattag == 0x2000) /* AC3 */
|
||||
|| track->a_formattag == MP_FOURCC('E', 'A', 'C', '3')
|
||||
|| (track->a_formattag == 0x2001)) { /* DTS */
|
||||
@@ -1414,20 +1437,22 @@ static int demux_mkv_open_audio(demuxer_t *demuxer, mkv_track_t *track)
|
||||
sh_a->bitrate = 16000 * 8;
|
||||
sh_a->block_align = 1024;
|
||||
|
||||
if (!strcmp(track->codec_id, MKV_A_AAC) && track->private_data) {
|
||||
if (!extradata_len) {
|
||||
if (!strcmp(track->codec_id, MKV_A_AAC)) {
|
||||
if (track->private_data && !extradata_len) {
|
||||
extradata = track->private_data;
|
||||
extradata_len = track->private_size;
|
||||
}
|
||||
} else {
|
||||
/* Recreate the 'private data' */
|
||||
/* which faad2 uses in its initialization */
|
||||
srate_idx = aac_get_sample_rate_index(track->a_sfreq);
|
||||
if (!strncmp(&track->codec_id[12], "MAIN", 4))
|
||||
const char *tail = "";
|
||||
if (strlen(track->codec_id) >= 12)
|
||||
tail = &track->codec_id[12];
|
||||
if (!strncmp(tail, "MAIN", 4))
|
||||
profile = 0;
|
||||
else if (!strncmp(&track->codec_id[12], "LC", 2))
|
||||
else if (!strncmp(tail, "LC", 2))
|
||||
profile = 1;
|
||||
else if (!strncmp(&track->codec_id[12], "SSR", 3))
|
||||
else if (!strncmp(tail, "SSR", 3))
|
||||
profile = 2;
|
||||
else
|
||||
profile = 3;
|
||||
@@ -1572,12 +1597,13 @@ static int demux_mkv_open_audio(demuxer_t *demuxer, mkv_track_t *track)
|
||||
AV_WB32(data + 8, 0);
|
||||
memcpy(data + 12, track->private_data, track->private_size);
|
||||
}
|
||||
} else if (track->a_formattag == MP_FOURCC('W', 'V', 'P', 'K') ||
|
||||
track->a_formattag == MP_FOURCC('T', 'R', 'H', 'D')) {
|
||||
} else if (track->a_formattag == MP_FOURCC('W', 'V', 'P', 'K')) {
|
||||
if (!track->ms_compat) {
|
||||
extradata = track->private_data;
|
||||
extradata_len = track->private_size;
|
||||
}
|
||||
} else if (track->a_formattag == MP_FOURCC('T', 'R', 'H', 'D')) {
|
||||
track->parse = true;
|
||||
} else if (track->a_formattag == MP_FOURCC('T', 'T', 'A', '1')) {
|
||||
sh_a->codecdata_len = 30;
|
||||
sh_a->codecdata = talloc_zero_size(sh_a, sh_a->codecdata_len);
|
||||
@@ -1852,6 +1878,7 @@ static int demux_mkv_open(demuxer_t *demuxer, enum demux_check check)
|
||||
|
||||
process_tags(demuxer);
|
||||
display_create_tracks(demuxer);
|
||||
add_coverart(demuxer);
|
||||
|
||||
if (demuxer->opts->mkv_probe_duration)
|
||||
probe_last_timestamp(demuxer);
|
||||
@@ -2272,8 +2299,8 @@ static bool mkv_parse_packet(mkv_track_t *track, bstr *raw, bstr *out)
|
||||
}
|
||||
if (track->av_parser && track->av_parser_codec) {
|
||||
while (raw->len) {
|
||||
uint8_t *data;
|
||||
int size;
|
||||
uint8_t *data = NULL;
|
||||
int size = 0;
|
||||
int len = av_parser_parse2(track->av_parser, track->av_parser_codec,
|
||||
&data, &size, raw->start, raw->len,
|
||||
AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
|
||||
|
||||
+29
-3
@@ -47,6 +47,8 @@ struct pl_parser {
|
||||
bool error;
|
||||
bool probing;
|
||||
bool force;
|
||||
enum demux_check check_level;
|
||||
struct stream *real_stream;
|
||||
};
|
||||
|
||||
static char *pl_get_line0(struct pl_parser *p)
|
||||
@@ -79,14 +81,33 @@ static bool pl_eof(struct pl_parser *p)
|
||||
return p->error || p->s->eof;
|
||||
}
|
||||
|
||||
static bool maybe_text(bstr d)
|
||||
{
|
||||
for (int n = 0; n < d.len; n++) {
|
||||
unsigned char c = d.start[n];
|
||||
if (c < 32 && c != '\n' && c != '\r' && c != '\t')
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static int parse_m3u(struct pl_parser *p)
|
||||
{
|
||||
bstr line = bstr_strip(pl_get_line(p));
|
||||
if (p->probing && !bstr_equals0(line, "#EXTM3U"))
|
||||
if (p->probing && !bstr_equals0(line, "#EXTM3U")) {
|
||||
// Last resort: if the file extension is m3u, it might be headerless.
|
||||
if (p->check_level == DEMUX_CHECK_UNSAFE) {
|
||||
char *ext = mp_splitext(p->real_stream->url, NULL);
|
||||
bstr data = stream_peek(p->real_stream, PROBE_SIZE);
|
||||
if (ext && strcmp(ext, "m3u") && data.len > 10 && maybe_text(data))
|
||||
goto ok;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
ok:
|
||||
if (p->probing)
|
||||
return 0;
|
||||
while (!pl_eof(p)) {
|
||||
while (line.len || !pl_eof(p)) {
|
||||
if (line.len > 0 && !bstr_startswith0(line, "#"))
|
||||
pl_add(p, line);
|
||||
line = bstr_strip(pl_get_line(p));
|
||||
@@ -110,7 +131,7 @@ static int parse_ref_init(struct pl_parser *p)
|
||||
"application/vnd.ms.wms-hdr.asfv1", NULL};
|
||||
bstr burl = bstr0(p->s->url);
|
||||
if (bstr_eatstart0(&burl, "http://") && check_mimetype(p->s, mmsh_types)) {
|
||||
MP_INFO(p, "Redirectiong to mmsh://\n");
|
||||
MP_INFO(p, "Redirecting to mmsh://\n");
|
||||
playlist_add_file(p->pl, talloc_asprintf(p, "mmsh://%.*s", BSTR_P(burl)));
|
||||
return 0;
|
||||
}
|
||||
@@ -158,6 +179,9 @@ static int parse_pls(struct pl_parser *p)
|
||||
if (bstr_split_tok(line, "=", &key, &value) &&
|
||||
bstr_case_startswith(key, bstr0("File")))
|
||||
{
|
||||
value = bstr_strip(value);
|
||||
if (bstr_startswith0(value, "\"") && bstr_endswith0(value, "\""))
|
||||
value = bstr_splice(value, 1, -1);
|
||||
pl_add(p, value);
|
||||
}
|
||||
}
|
||||
@@ -223,12 +247,14 @@ static int open_file(struct demuxer *demuxer, enum demux_check check)
|
||||
struct pl_parser *p = talloc_zero(NULL, struct pl_parser);
|
||||
p->log = demuxer->log;
|
||||
p->pl = talloc_zero(p, struct playlist);
|
||||
p->real_stream = demuxer->stream;
|
||||
|
||||
bstr probe_buf = stream_peek(demuxer->stream, PROBE_SIZE);
|
||||
p->s = open_memory_stream(probe_buf.start, probe_buf.len);
|
||||
p->s->mime_type = demuxer->stream->mime_type;
|
||||
p->utf16 = stream_skip_bom(p->s);
|
||||
p->force = force;
|
||||
p->check_level = check;
|
||||
p->probing = true;
|
||||
const struct pl_format *fmt = probe_pl(p);
|
||||
free_stream(p->s);
|
||||
|
||||
+32
-27
@@ -262,34 +262,38 @@ int ebml_resync_cluster(struct mp_log *log, stream_t *s)
|
||||
struct generic;
|
||||
#define generic_struct struct generic
|
||||
|
||||
static uint32_t ebml_parse_id(uint8_t *data, int *length)
|
||||
static uint32_t ebml_parse_id(uint8_t *data, size_t data_len, int *length)
|
||||
{
|
||||
*length = -1;
|
||||
uint8_t *end = data + data_len;
|
||||
if (data == end)
|
||||
return EBML_ID_INVALID;
|
||||
int len = 1;
|
||||
uint32_t id = *data++;
|
||||
for (int len_mask = 0x80; !(id & len_mask); len_mask >>= 1) {
|
||||
len++;
|
||||
if (len > 4) {
|
||||
*length = -1;
|
||||
if (len > 4)
|
||||
return EBML_ID_INVALID;
|
||||
}
|
||||
}
|
||||
*length = len;
|
||||
while (--len)
|
||||
while (--len && data < end)
|
||||
id = (id << 8) | *data++;
|
||||
return id;
|
||||
}
|
||||
|
||||
static uint64_t parse_vlen(uint8_t *data, int *length, bool is_length)
|
||||
static uint64_t ebml_parse_length(uint8_t *data, size_t data_len, int *length)
|
||||
{
|
||||
*length = -1;
|
||||
uint8_t *end = data + data_len;
|
||||
if (data == end)
|
||||
return -1;
|
||||
uint64_t r = *data++;
|
||||
int len = 1;
|
||||
int len_mask;
|
||||
for (len_mask = 0x80; !(r & len_mask); len_mask >>= 1) {
|
||||
len++;
|
||||
if (len > 8) {
|
||||
*length = -1;
|
||||
if (len > 8)
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
r &= len_mask - 1;
|
||||
|
||||
@@ -297,25 +301,20 @@ static uint64_t parse_vlen(uint8_t *data, int *length, bool is_length)
|
||||
if (r == len_mask - 1)
|
||||
num_allones++;
|
||||
for (int i = 1; i < len; i++) {
|
||||
if (data == end)
|
||||
return -1;
|
||||
if (*data == 255)
|
||||
num_allones++;
|
||||
r = (r << 8) | *data++;
|
||||
}
|
||||
if (is_length && num_allones == len) {
|
||||
// According to Matroska specs this means "unknown length"
|
||||
// Could be supported if there are any actual files using it
|
||||
*length = -1;
|
||||
// According to Matroska specs this means "unknown length"
|
||||
// Could be supported if there are any actual files using it
|
||||
if (num_allones == len)
|
||||
return -1;
|
||||
}
|
||||
*length = len;
|
||||
return r;
|
||||
}
|
||||
|
||||
static uint64_t ebml_parse_length(uint8_t *data, int *length)
|
||||
{
|
||||
return parse_vlen(data, length, true);
|
||||
}
|
||||
|
||||
static uint64_t ebml_parse_uint(uint8_t *data, int length)
|
||||
{
|
||||
assert(length >= 1 && length <= 8);
|
||||
@@ -363,7 +362,7 @@ static void ebml_parse_element(struct ebml_parse_ctx *ctx, void *target,
|
||||
while (p < end) {
|
||||
uint8_t *startp = p;
|
||||
int len;
|
||||
uint32_t id = ebml_parse_id(p, &len);
|
||||
uint32_t id = ebml_parse_id(p, end - p, &len);
|
||||
if (len > end - p)
|
||||
goto past_end_error;
|
||||
if (len < 0) {
|
||||
@@ -371,7 +370,7 @@ static void ebml_parse_element(struct ebml_parse_ctx *ctx, void *target,
|
||||
goto other_error;
|
||||
}
|
||||
p += len;
|
||||
uint64_t length = ebml_parse_length(p, &len);
|
||||
uint64_t length = ebml_parse_length(p, end - p, &len);
|
||||
if (len > end - p)
|
||||
goto past_end_error;
|
||||
if (len < 0) {
|
||||
@@ -458,11 +457,17 @@ static void ebml_parse_element(struct ebml_parse_ctx *ctx, void *target,
|
||||
|
||||
while (data < end) {
|
||||
int len;
|
||||
uint32_t id = ebml_parse_id(data, &len);
|
||||
assert(len >= 0 && len <= end - data);
|
||||
uint32_t id = ebml_parse_id(data, end - data, &len);
|
||||
if (len < 0 || len > end - data) {
|
||||
MP_DBG(ctx, "Error parsing subelement\n");
|
||||
break;
|
||||
}
|
||||
data += len;
|
||||
uint64_t length = ebml_parse_length(data, &len);
|
||||
assert(len >= 0 && len <= end - data);
|
||||
uint64_t length = ebml_parse_length(data, end - data, &len);
|
||||
if (len < 0 || len > end - data) {
|
||||
MP_DBG(ctx, "Error parsing subelement length\n");
|
||||
break;
|
||||
}
|
||||
data += len;
|
||||
if (length > end - data) {
|
||||
// Try to parse what is possible from inside this partial element
|
||||
@@ -582,7 +587,7 @@ static void ebml_parse_element(struct ebml_parse_ctx *ctx, void *target,
|
||||
case EBML_TYPE_EBML_ID:;
|
||||
uint32_t *idptr;
|
||||
GETPTR(idptr, uint32_t);
|
||||
*idptr = ebml_parse_id(data, &len);
|
||||
*idptr = ebml_parse_id(data, end - data, &len);
|
||||
if (len != length) {
|
||||
MP_DBG(ctx, "ebml_id broken value\n");
|
||||
goto error;
|
||||
@@ -614,7 +619,7 @@ int ebml_read_element(struct stream *s, struct ebml_parse_ctx *ctx,
|
||||
MP_MSG(ctx, msglevel, "Refusing to read element over 100 MB in size\n");
|
||||
return -1;
|
||||
}
|
||||
ctx->talloc_ctx = talloc_size(NULL, length + 8);
|
||||
ctx->talloc_ctx = talloc_size(NULL, length);
|
||||
int read_len = stream_read(s, ctx->talloc_ctx, length);
|
||||
if (read_len < length)
|
||||
MP_MSG(ctx, msglevel, "Unexpected end of file - partial or corrupt file?\n");
|
||||
|
||||
@@ -409,6 +409,7 @@ mp_cmd_t *mp_cmd_clone(mp_cmd_t *cmd)
|
||||
memset(&ret->args[i].v, 0, ret->args[i].type->type->size);
|
||||
m_option_copy(ret->args[i].type, &ret->args[i].v, &cmd->args[i].v);
|
||||
}
|
||||
ret->original = bstrdup(ret, cmd->original);
|
||||
|
||||
if (cmd->id == MP_CMD_COMMAND_LIST) {
|
||||
struct mp_cmd *prev = NULL;
|
||||
|
||||
+13
-7
@@ -445,6 +445,8 @@ static struct cmd_bind *find_any_bind_for_key(struct input_ctx *ictx,
|
||||
}
|
||||
if (s->flags & MP_INPUT_EXCLUSIVE)
|
||||
break;
|
||||
if (best_bind && (s->flags & MP_INPUT_ON_TOP))
|
||||
break;
|
||||
}
|
||||
|
||||
return best_bind;
|
||||
@@ -458,6 +460,8 @@ static mp_cmd_t *get_cmd_from_keys(struct input_ctx *ictx, char *force_section,
|
||||
|
||||
struct cmd_bind *cmd = find_any_bind_for_key(ictx, force_section, code);
|
||||
if (cmd == NULL) {
|
||||
if (code == MP_KEY_CLOSE_WIN)
|
||||
return mp_input_parse_cmd_strv(ictx->log, (const char*[]){"quit", 0});
|
||||
int msgl = MSGL_WARN;
|
||||
if (code == MP_KEY_MOUSE_MOVE || code == MP_KEY_MOUSE_LEAVE)
|
||||
msgl = MSGL_DEBUG;
|
||||
@@ -589,10 +593,11 @@ static void interpret_key(struct input_ctx *ictx, int code, double scale)
|
||||
release_down_cmd(ictx, false);
|
||||
} else {
|
||||
// Press of key with no separate down/up events
|
||||
if (ictx->last_key_down == code) {
|
||||
// Mixing press events and up/down with the same key is not allowed
|
||||
MP_WARN(ictx, "Mixing key presses and up/down.\n");
|
||||
}
|
||||
// Mixing press events and up/down with the same key is not supported,
|
||||
// and input sources shouldn't do this, but can happen anyway if
|
||||
// multiple input sources interfere with each others.
|
||||
if (ictx->last_key_down == code)
|
||||
release_down_cmd(ictx, false);
|
||||
cmd = resolve_key(ictx, code);
|
||||
}
|
||||
|
||||
@@ -1019,14 +1024,15 @@ void mp_input_define_section(struct input_ctx *ictx, char *name, char *location,
|
||||
if (!name || !name[0])
|
||||
return; // parse_config() changes semantics with restrict_section==empty
|
||||
input_lock(ictx);
|
||||
// Delete:
|
||||
struct cmd_bind_section *bs = get_bind_section(ictx, bstr0(name));
|
||||
remove_binds(bs, builtin);
|
||||
if (contents) {
|
||||
// Redefine:
|
||||
parse_config(ictx, builtin, bstr0(contents), location, name);
|
||||
} else {
|
||||
// Disable:
|
||||
mp_input_disable_section(ictx, name);
|
||||
// Delete:
|
||||
struct cmd_bind_section *bs = get_bind_section(ictx, bstr0(name));
|
||||
remove_binds(bs, builtin);
|
||||
}
|
||||
input_unlock(ictx);
|
||||
}
|
||||
|
||||
+9
-18
@@ -61,8 +61,6 @@ struct client_arg {
|
||||
bool close_client_fd;
|
||||
|
||||
bool writable;
|
||||
|
||||
int suspend_counter;
|
||||
};
|
||||
|
||||
static mpv_node *mpv_node_map_get(mpv_node *src, const char *key)
|
||||
@@ -245,6 +243,7 @@ static char *json_execute_command(struct client_arg *arg, void *ta_parent,
|
||||
|
||||
rc = json_parse(ta_parent, &msg_node, &src, 3);
|
||||
if (rc < 0) {
|
||||
MP_ERR(arg, "malformed JSON received\n");
|
||||
rc = MPV_ERROR_INVALID_PARAMETER;
|
||||
goto error;
|
||||
}
|
||||
@@ -420,21 +419,11 @@ static char *json_execute_command(struct client_arg *arg, void *ta_parent,
|
||||
rc = mpv_request_log_messages(arg->client,
|
||||
cmd_node->u.list->values[1].u.string);
|
||||
} else if (!strcmp("suspend", cmd)) {
|
||||
if (arg->suspend_counter < INT_MAX) {
|
||||
mpv_suspend(arg->client);
|
||||
arg->suspend_counter++;
|
||||
rc = MPV_ERROR_SUCCESS;
|
||||
} else {
|
||||
rc = MPV_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
mpv_suspend(arg->client);
|
||||
rc = MPV_ERROR_SUCCESS;
|
||||
} else if (!strcmp("resume", cmd)) {
|
||||
if (arg->suspend_counter > 0) {
|
||||
mpv_resume(arg->client);
|
||||
arg->suspend_counter--;
|
||||
rc = MPV_ERROR_SUCCESS;
|
||||
} else {
|
||||
rc = MPV_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
mpv_resume(arg->client);
|
||||
rc = MPV_ERROR_SUCCESS;
|
||||
} else {
|
||||
mpv_node result_node;
|
||||
|
||||
@@ -558,7 +547,7 @@ static void *client_thread(void *p)
|
||||
if (errno == EAGAIN)
|
||||
break;
|
||||
|
||||
MP_ERR(arg, "Read error\n");
|
||||
MP_ERR(arg, "Read error (%s)\n", mp_strerror(errno));
|
||||
goto done;
|
||||
}
|
||||
|
||||
@@ -594,7 +583,7 @@ static void *client_thread(void *p)
|
||||
rc = ipc_write(arg->client_fd, reply_msg,
|
||||
strlen(reply_msg));
|
||||
if (rc < 0) {
|
||||
MP_ERR(arg, "Write error\n");
|
||||
MP_ERR(arg, "Write error (%s)\n", mp_strerror(errno));
|
||||
talloc_free(tmp);
|
||||
goto done;
|
||||
}
|
||||
@@ -607,6 +596,8 @@ static void *client_thread(void *p)
|
||||
}
|
||||
|
||||
done:
|
||||
if (client_msg.len > 0)
|
||||
MP_WARN(arg, "Ignoring unterminated command on disconnect.\n");
|
||||
talloc_free(client_msg.start);
|
||||
if (arg->close_client_fd)
|
||||
close(arg->client_fd);
|
||||
|
||||
+10
-5
@@ -28,6 +28,7 @@
|
||||
#include <errno.h>
|
||||
#include <poll.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/msg.h"
|
||||
#include "keycodes.h"
|
||||
|
||||
@@ -65,7 +66,8 @@ static struct ctx *joystick_init(struct input_ctx *ictx, struct mp_log *log, cha
|
||||
|
||||
fd = open( dev ? dev : JS_DEV , O_RDONLY | O_NONBLOCK );
|
||||
if(fd < 0) {
|
||||
mp_err(log, "Can't open joystick device %s: %s\n",dev ? dev : JS_DEV,strerror(errno));
|
||||
mp_err(log, "Can't open joystick device %s: %s\n",dev ? dev : JS_DEV,
|
||||
mp_strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -83,7 +85,8 @@ static struct ctx *joystick_init(struct input_ctx *ictx, struct mp_log *log, cha
|
||||
initialized = 1;
|
||||
break;
|
||||
}
|
||||
MP_ERR(ctx, "Error while reading joystick device: %s\n",strerror(errno));
|
||||
MP_ERR(ctx, "Error while reading joystick device: %s\n",
|
||||
mp_strerror(errno));
|
||||
close(fd);
|
||||
talloc_free(ctx);
|
||||
return NULL;
|
||||
@@ -117,10 +120,12 @@ static int mp_input_joystick_read(void *pctx, int fd) {
|
||||
continue;
|
||||
else if(errno == EAGAIN)
|
||||
return 0;
|
||||
if( r < 0)
|
||||
MP_ERR(ctx, "Error while reading joystick device: %s\n",strerror(errno));
|
||||
else
|
||||
if( r < 0) {
|
||||
MP_ERR(ctx, "Error while reading joystick device: %s\n",
|
||||
mp_strerror(errno));
|
||||
} else {
|
||||
MP_ERR(ctx, "Error while reading joystick device: %s\n","EOF");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
l += r;
|
||||
|
||||
+2
-1
@@ -25,6 +25,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <poll.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/msg.h"
|
||||
#include "input.h"
|
||||
|
||||
@@ -49,7 +50,7 @@ static struct ctx *mp_input_lirc_init(struct input_ctx *ictx, struct mp_log *log
|
||||
|
||||
mode = fcntl(lirc_sock, F_GETFL);
|
||||
if (mode < 0 || fcntl(lirc_sock, F_SETFL, mode | O_NONBLOCK) < 0) {
|
||||
mp_err(log, "setting non-blocking mode failed: %s\n", strerror(errno));
|
||||
mp_err(log, "setting non-blocking mode failed: %s\n", mp_strerror(errno));
|
||||
lirc_deinit();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -45,6 +45,16 @@ static void read_pipe_thread(struct mp_input_src *src, void *param)
|
||||
goto done;
|
||||
}
|
||||
|
||||
// If we're reading from stdin, unset it. All I/O on synchronous handles is
|
||||
// serialized, so stupid DLLs that call GetFileType on stdin can hang the
|
||||
// process if they do it while we're reading from it. At least, the
|
||||
// VirtualBox OpenGL ICD is affected by this, but only on Windows XP.
|
||||
// GetFileType works differently in later versions of Windows. See:
|
||||
// https://support.microsoft.com/kb/2009703
|
||||
// http://blogs.msdn.com/b/oldnewthing/archive/2011/12/02/10243553.aspx
|
||||
if ((void*)_get_osfhandle(fd) == GetStdHandle(STD_INPUT_HANDLE))
|
||||
SetStdHandle(STD_INPUT_HANDLE, NULL);
|
||||
|
||||
waio = waio_alloc((void *)_get_osfhandle(fd), 0, NULL, NULL);
|
||||
if (!waio) {
|
||||
MP_ERR(src, "Can't initialize win32 file reader.\n");
|
||||
|
||||
@@ -117,6 +117,8 @@ extern "C" {
|
||||
* (used through libass), ALSA, FFmpeg, and possibly more.
|
||||
* - The FPU precision must be set at least to double precision.
|
||||
* - On Windows, mpv will call timeBeginPeriod(1).
|
||||
* - SIGPIPE should be blocked. Some parts rely on this signal not crashing the
|
||||
* process (such as ffmpeg OpenSSL support, or the mpv IPC code).
|
||||
* - On memory exhaustion, mpv will kill the process.
|
||||
*
|
||||
* Embedding the video window
|
||||
|
||||
+1
-1
@@ -207,7 +207,7 @@ static inline int bstr_eatstart0(struct bstr *s, const char *prefix)
|
||||
}
|
||||
|
||||
// create a pair (not single value!) for "%.*s" printf syntax
|
||||
#define BSTR_P(bstr) (int)((bstr).len), (bstr).start
|
||||
#define BSTR_P(bstr) (int)((bstr).len), ((bstr).start ? (char*)(bstr).start : "")
|
||||
|
||||
#define WHITESPACE " \f\n\r\t\v"
|
||||
|
||||
|
||||
+10
-11
@@ -607,13 +607,8 @@ define_yes_no $_vaapi HAVE_VAAPI_HWACCEL
|
||||
|
||||
if test "$_vaapi" = yes ; then
|
||||
check_pkg_config "VAAPI VPP" auto VAAPI_VPP 'libva >= 0.34.0'
|
||||
|
||||
check_pkg_config "VAAPI GLX" auto VAAPI_GLX 'libva-glx >= 0.32.0'
|
||||
_vaapi_glx=$(defretval)
|
||||
else
|
||||
check_yes_no no VAAPI_VPP
|
||||
check_yes_no no VAAPI_GLX
|
||||
_vaapi_glx=no
|
||||
fi
|
||||
|
||||
check_pkg_config "Xinerama" $_xinerama XINERAMA 'xinerama'
|
||||
@@ -696,6 +691,10 @@ _vdpau_gl_x11=no
|
||||
check_yes_no $_vdpau_gl_x11 VDPAU_GL_X11
|
||||
echores "$_vdpau_gl_x11"
|
||||
|
||||
_vaapi_glx=no
|
||||
(test "$_gl_x11" = yes && test "$_vaapi" = yes) && _vaapi_glx=auto
|
||||
check_pkg_config "VAAPI with OpenGL/X11" $_vaapi_glx VAAPI_GLX 'libva-glx >= 0.32.0'
|
||||
|
||||
check_pkg_config "SDL 2.0" $_sdl2 SDL2 'sdl2'
|
||||
|
||||
check_statement_libs "OSS Audio" $_ossaudio OSS_AUDIO $_soundcard_header "int x = SNDCTL_DSP_SETFRAGMENT;"
|
||||
@@ -752,14 +751,14 @@ check_pkg_config "FFmpeg/Libav" $_ffmpeg FFMPEG \
|
||||
"libavutil >= 52.48.101 libavcodec >= 55.34.1 libavformat >= 55.12.0 libswscale >= 2.1.2"
|
||||
test $(defretval) = no && die "Unable to find development files for some of the required Libav libraries above. Aborting."
|
||||
|
||||
check_pkg_config "Libavresample" $_libavresample LIBAVRESAMPLE 'libavresample >= 1.1.0'
|
||||
_libavresample=$(defretval)
|
||||
|
||||
_libswresample=auto
|
||||
test $_libavresample = yes && _libswresample=no
|
||||
check_pkg_config "Libswresample" $_libswresample LIBSWRESAMPLE 'libswresample >= 0.17.104'
|
||||
_libswresample=$(defretval)
|
||||
|
||||
_libavresample=auto
|
||||
test $_libswresample = yes && _libavresample=no
|
||||
check_pkg_config "Libavresample" $_libavresample LIBAVRESAMPLE 'libavresample >= 1.1.0'
|
||||
_libavresample=$(defretval)
|
||||
|
||||
if test "$_libswresample" = no && test "$_libavresample" = no ; then
|
||||
die "No resampler found. Install libavresample or libswresample (FFmpeg)."
|
||||
fi
|
||||
@@ -951,7 +950,7 @@ cat > $TMPC << EOF
|
||||
|
||||
#define CONFIGURATION "$configuration"
|
||||
|
||||
#define MPLAYER_CONFDIR "$_confdir"
|
||||
#define MPV_CONFDIR "$_confdir"
|
||||
|
||||
/* we didn't bother to add actual config checks for this, or they are
|
||||
for platforms not supported by this configure script */
|
||||
|
||||
@@ -191,7 +191,6 @@ SOURCES = audio/audio.c \
|
||||
options/parse_configfile.c \
|
||||
options/path.c \
|
||||
osdep/io.c \
|
||||
osdep/numcores.c \
|
||||
osdep/semaphore_osx.c \
|
||||
osdep/subprocess-posix.c \
|
||||
osdep/terminal-unix.c \
|
||||
|
||||
+1
-1
@@ -734,7 +734,7 @@ void m_config_print_option_list(const struct m_config *config)
|
||||
continue;
|
||||
if (co->is_generated)
|
||||
continue;
|
||||
MP_INFO(config, " %s%-30.30s", prefix, co->name);
|
||||
MP_INFO(config, " %s%-30s", prefix, co->name);
|
||||
if (opt->type == &m_option_type_choice) {
|
||||
MP_INFO(config, " Choices:");
|
||||
struct m_opt_choice_alternatives *alt = opt->priv;
|
||||
|
||||
+38
-7
@@ -2146,7 +2146,7 @@ static int parse_chmap(struct mp_log *log, const m_option_t *opt,
|
||||
|
||||
const m_option_type_t m_option_type_chmap = {
|
||||
.name = "Audio channels or channel map",
|
||||
.size = sizeof(struct mp_chmap *),
|
||||
.size = sizeof(struct mp_chmap),
|
||||
.parse = parse_chmap,
|
||||
.copy = copy_opt,
|
||||
};
|
||||
@@ -2171,6 +2171,7 @@ static int parse_timestring(struct bstr str, double *time, char endchar)
|
||||
return len;
|
||||
}
|
||||
|
||||
#define HAS_NOPTS(opt) ((opt)->min == MP_NOPTS_VALUE)
|
||||
|
||||
static int parse_time(struct mp_log *log, const m_option_t *opt,
|
||||
struct bstr name, struct bstr param, void *dst)
|
||||
@@ -2180,7 +2181,7 @@ static int parse_time(struct mp_log *log, const m_option_t *opt,
|
||||
if (param.len == 0)
|
||||
return M_OPT_MISSING_PARAM;
|
||||
|
||||
if (opt->min == MP_NOPTS_VALUE && bstr_equals0(param, "no")) {
|
||||
if (HAS_NOPTS(opt) == MP_NOPTS_VALUE && bstr_equals0(param, "no")) {
|
||||
time = MP_NOPTS_VALUE;
|
||||
} else if (!parse_timestring(param, &time, 0)) {
|
||||
mp_err(log, "Option %.*s: invalid time: '%.*s'\n",
|
||||
@@ -2193,25 +2194,55 @@ static int parse_time(struct mp_log *log, const m_option_t *opt,
|
||||
return 1;
|
||||
}
|
||||
|
||||
static char *print_time(const m_option_t *opt, const void *val)
|
||||
{
|
||||
double pts = *(double *)val;
|
||||
if (pts == MP_NOPTS_VALUE && HAS_NOPTS(opt))
|
||||
return talloc_strdup(NULL, "no"); // symmetry with parsing
|
||||
return talloc_asprintf(NULL, "%f", pts);
|
||||
}
|
||||
|
||||
static char *pretty_print_time(const m_option_t *opt, const void *val)
|
||||
{
|
||||
double pts = *(double *)val;
|
||||
if (pts == MP_NOPTS_VALUE && opt->min == MP_NOPTS_VALUE)
|
||||
if (pts == MP_NOPTS_VALUE && HAS_NOPTS(opt))
|
||||
return talloc_strdup(NULL, "no"); // symmetry with parsing
|
||||
return mp_format_time(pts, false);
|
||||
}
|
||||
|
||||
static int time_set(const m_option_t *opt, void *dst, struct mpv_node *src)
|
||||
{
|
||||
if (HAS_NOPTS(opt) && src->format == MPV_FORMAT_STRING) {
|
||||
if (strcmp(src->u.string, "no") == 0) {
|
||||
*(double *)dst = MP_NOPTS_VALUE;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return double_set(opt, dst, src);
|
||||
}
|
||||
|
||||
static int time_get(const m_option_t *opt, void *ta_parent,
|
||||
struct mpv_node *dst, void *src)
|
||||
{
|
||||
if (HAS_NOPTS(opt) && *(double *)src == MP_NOPTS_VALUE) {
|
||||
dst->format = MPV_FORMAT_STRING;
|
||||
dst->u.string = talloc_strdup(ta_parent, "no");
|
||||
return 1;
|
||||
}
|
||||
return double_get(opt, ta_parent, dst, src);
|
||||
}
|
||||
|
||||
const m_option_type_t m_option_type_time = {
|
||||
.name = "Time",
|
||||
.size = sizeof(double),
|
||||
.parse = parse_time,
|
||||
.print = print_double,
|
||||
.print = print_time,
|
||||
.pretty_print = pretty_print_time,
|
||||
.copy = copy_opt,
|
||||
.add = add_double,
|
||||
.clamp = clamp_double,
|
||||
.set = double_set,
|
||||
.get = double_get,
|
||||
.set = time_set,
|
||||
.get = time_get,
|
||||
};
|
||||
|
||||
|
||||
@@ -2620,7 +2651,7 @@ static int parse_obj_settings(struct mp_log *log, struct bstr opt,
|
||||
if (m_obj_list_find(&desc, list, str)) {
|
||||
if (desc.replaced_name)
|
||||
mp_warn(log, "Driver '%s' has been replaced with '%s'!\n",
|
||||
desc.name, desc.replaced_name);
|
||||
desc.replaced_name, desc.name);
|
||||
} else {
|
||||
if (!list->allow_unknown_entries) {
|
||||
mp_err(log, "Option %.*s: %.*s doesn't exist.\n",
|
||||
|
||||
+2
-1
@@ -614,7 +614,7 @@ const struct MPOpts mp_default_opts = {
|
||||
.use_text_osd = 1,
|
||||
#if HAVE_LUA
|
||||
.lua_load_osc = 1,
|
||||
.lua_load_ytdl = 0,
|
||||
.lua_load_ytdl = 1,
|
||||
.lua_ytdl_format = NULL,
|
||||
#endif
|
||||
.auto_load_scripts = 1,
|
||||
@@ -684,6 +684,7 @@ const struct MPOpts mp_default_opts = {
|
||||
.sub_fix_timing = 1,
|
||||
.sub_cp = "auto",
|
||||
.mkv_subtitle_preroll_secs = 1.0,
|
||||
.screenshot_template = "shot%n",
|
||||
|
||||
.hwdec_codecs = "h264,vc1,wmv3",
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "osdep/io.h"
|
||||
|
||||
#include "parse_configfile.h"
|
||||
#include "common/common.h"
|
||||
#include "common/msg.h"
|
||||
#include "misc/ctype.h"
|
||||
#include "m_option.h"
|
||||
@@ -79,7 +80,7 @@ int m_config_parse_config_file(m_config_t *config, const char *conffile,
|
||||
MP_VERBOSE(config, "\n");
|
||||
|
||||
if ((fp = fopen(conffile, "r")) == NULL) {
|
||||
MP_VERBOSE(config, "Can't open config file: %s\n", strerror(errno));
|
||||
MP_VERBOSE(config, "Can't open config file: %s\n", mp_strerror(errno));
|
||||
ret = 0;
|
||||
goto out;
|
||||
}
|
||||
|
||||
+1
-1
@@ -74,7 +74,7 @@ static int mp_add_xdg_config_dirs(struct mpv_global *global, char **dirs, int i)
|
||||
i = mp_add_macosx_bundle_dir(global, dirs, i);
|
||||
#endif
|
||||
|
||||
dirs[i++] = MPLAYER_CONFDIR;
|
||||
dirs[i++] = MPV_CONFDIR;
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
@@ -233,6 +233,8 @@ Application *mpv_shared_app(void)
|
||||
options:NSAnchoredSearch
|
||||
range:NSMakeRange(0, [MPV_PROTOCOL length])];
|
||||
|
||||
url = [url stringByRemovingPercentEncoding];
|
||||
|
||||
self.files = @[url];
|
||||
|
||||
if (self.willStopOnOpenEvent) {
|
||||
@@ -329,7 +331,10 @@ void init_cocoa_application(void)
|
||||
NSApp = mpv_shared_app();
|
||||
[NSApp setDelegate:NSApp];
|
||||
[NSApp initialize_menu];
|
||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||
|
||||
// Will be set to Regular from cocoa_common during UI creation so that we
|
||||
// don't create an icon when playing audio only files.
|
||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory];
|
||||
|
||||
atexit_b(^{
|
||||
// Because activation policy has just been set to behave like a real
|
||||
@@ -439,21 +444,6 @@ static bool bundle_started_from_finder(int argc, char **argv)
|
||||
}
|
||||
}
|
||||
|
||||
static const char macosx_icon[] =
|
||||
#include "osdep/macosx_icon.inc"
|
||||
;
|
||||
|
||||
static void set_application_icon(NSApplication *app)
|
||||
{
|
||||
NSData *icon_data = [NSData dataWithBytesNoCopy:(void *)macosx_icon
|
||||
length:sizeof(macosx_icon)
|
||||
freeWhenDone:NO];
|
||||
NSImage *icon = [[NSImage alloc] initWithData:icon_data];
|
||||
[app setApplicationIconImage:icon];
|
||||
[icon release];
|
||||
[icon_data release];
|
||||
}
|
||||
|
||||
void macosx_finder_args_preinit(int *argc, char ***argv)
|
||||
{
|
||||
Application *app = mpv_shared_app();
|
||||
@@ -475,7 +465,6 @@ void macosx_finder_args_preinit(int *argc, char ***argv)
|
||||
*argc = cocoa_argc;
|
||||
*argv = cocoa_argv;
|
||||
} else {
|
||||
set_application_icon(app);
|
||||
for (int i = 0; i < *argc; i++ ) {
|
||||
NSString *arg = [NSString stringWithUTF8String:(*argv)[i]];
|
||||
[app.argumentsList addObject:arg];
|
||||
|
||||
@@ -440,8 +440,10 @@ void cocoa_set_input_context(struct input_ctx *input_context)
|
||||
size_t num_files = [files count];
|
||||
char **files_utf8 = talloc_array(NULL, char*, num_files);
|
||||
[files enumerateObjectsUsingBlock:^(id obj, NSUInteger i, BOOL *_){
|
||||
char *filename = (char *)[obj UTF8String];
|
||||
size_t bytes = [obj lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
|
||||
NSURL *url = [NSURL URLWithString:obj];
|
||||
NSString *path = [url path];
|
||||
char *filename = (char *)[path UTF8String];
|
||||
size_t bytes = [path lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
|
||||
files_utf8[i] = talloc_memdup(files_utf8, filename, bytes + 1);
|
||||
}];
|
||||
mp_event_drop_files(_inputContext, num_files, files_utf8);
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ FILETYPE VFT_APP
|
||||
VALUE "CompanyName", "mpv\000"
|
||||
VALUE "FileDescription", "mpv - video player\000"
|
||||
VALUE "FileVersion",VERSION
|
||||
VALUE "LegalCopyright", " (C) 2000-2014 mpv/mplayer2/MPlayer\000"
|
||||
VALUE "LegalCopyright", " (C) 2000-2015 mpv/mplayer2/MPlayer\000"
|
||||
//VALUE "LegalTrademarks"," \000";
|
||||
VALUE "OriginalFilename", "mpv.exe\000"
|
||||
VALUE "ProductName", "mpv - video player\000"
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* This file is part of MPlayer.
|
||||
*
|
||||
* MPlayer 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.
|
||||
*
|
||||
* MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "numcores.h"
|
||||
|
||||
#ifdef _SC_NPROCESSORS_ONLN
|
||||
#define SCNAME _SC_NPROCESSORS_ONLN
|
||||
#elif defined _SC_NPROC_ONLN
|
||||
#define SCNAME _SC_NPROC_ONLN
|
||||
#elif defined _SC_CRAY_NCPU
|
||||
#define SCNAME _SC_CRAY_NCPU
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
int default_thread_count(void)
|
||||
{
|
||||
SYSTEM_INFO info;
|
||||
GetSystemInfo(&info);
|
||||
int count = info.dwNumberOfProcessors;
|
||||
if (count > 0)
|
||||
return count;
|
||||
return -1;
|
||||
}
|
||||
|
||||
#elif defined SCNAME
|
||||
|
||||
int default_thread_count(void)
|
||||
{
|
||||
long nprocs = sysconf(SCNAME);
|
||||
if (nprocs < 1)
|
||||
return -1;
|
||||
return nprocs;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
int default_thread_count(void)
|
||||
{
|
||||
return -2;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1 +0,0 @@
|
||||
int default_thread_count(void);
|
||||
@@ -66,18 +66,18 @@ int mp_subprocess(char **args, struct mp_cancel *cancel, void *ctx,
|
||||
int p_stderr[2] = {-1, -1};
|
||||
pid_t pid = -1;
|
||||
|
||||
if (mp_make_cloexec_pipe(p_stdout) < 0)
|
||||
if (on_stdout && mp_make_cloexec_pipe(p_stdout) < 0)
|
||||
goto done;
|
||||
if (mp_make_cloexec_pipe(p_stderr) < 0)
|
||||
if (on_stderr && mp_make_cloexec_pipe(p_stderr) < 0)
|
||||
goto done;
|
||||
|
||||
if (posix_spawn_file_actions_init(&fa))
|
||||
goto done;
|
||||
fa_destroy = true;
|
||||
// redirect stdout and stderr
|
||||
if (posix_spawn_file_actions_adddup2(&fa, p_stdout[1], 1))
|
||||
if (p_stdout[1] >= 0 && posix_spawn_file_actions_adddup2(&fa, p_stdout[1], 1))
|
||||
goto done;
|
||||
if (posix_spawn_file_actions_adddup2(&fa, p_stderr[1], 2))
|
||||
if (p_stderr[1] >= 0 && posix_spawn_file_actions_adddup2(&fa, p_stderr[1], 2))
|
||||
goto done;
|
||||
|
||||
if (posix_spawnp(&pid, args[0], &fa, NULL, args, environ)) {
|
||||
|
||||
+13
-9
@@ -15,7 +15,6 @@
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#define _WIN32_WINNT 0x0600
|
||||
#include <windows.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -84,12 +83,14 @@ static void write_arg(bstr *cmdline, char *arg)
|
||||
// Convert an array of arguments to a properly escaped command-line string
|
||||
static wchar_t *write_cmdline(void *ctx, char **argv)
|
||||
{
|
||||
// argv[0] should always be quoted. Otherwise, arguments may be interpreted
|
||||
// as part of the program name. Also, it can't contain escape sequences.
|
||||
bstr cmdline = {0};
|
||||
bstr_xappend_asprintf(NULL, &cmdline, "\"%s\"", argv[0]);
|
||||
|
||||
for (int i = 0; argv[i]; i++) {
|
||||
for (int i = 1; argv[i]; i++) {
|
||||
bstr_xappend(NULL, &cmdline, bstr0(" "));
|
||||
write_arg(&cmdline, argv[i]);
|
||||
if (argv[i + 1])
|
||||
bstr_xappend(NULL, &cmdline, bstr0(" "));
|
||||
}
|
||||
|
||||
wchar_t *wcmdline = mp_from_utf8(ctx, cmdline.start);
|
||||
@@ -111,9 +112,8 @@ static int create_overlapped_pipe(HANDLE *read, HANDLE *write)
|
||||
// overlapped pipes, so instead, use a named pipe with a unique name
|
||||
*read = CreateNamedPipeW(buf, PIPE_ACCESS_INBOUND |
|
||||
FILE_FLAG_FIRST_PIPE_INSTANCE | FILE_FLAG_OVERLAPPED,
|
||||
PIPE_TYPE_BYTE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS,
|
||||
1, 0, 4096, 0, NULL);
|
||||
if (!*read)
|
||||
PIPE_TYPE_BYTE | PIPE_WAIT, 1, 0, 4096, 0, NULL);
|
||||
if (*read == INVALID_HANDLE_VALUE)
|
||||
goto error;
|
||||
|
||||
// Open the write end of the pipe as a synchronous handle
|
||||
@@ -221,6 +221,10 @@ static int async_read(HANDLE file, void *buf, unsigned size, OVERLAPPED* ol)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void write_none(void *ctx, char *data, size_t size)
|
||||
{
|
||||
}
|
||||
|
||||
int mp_subprocess(char **args, struct mp_cancel *cancel, void *ctx,
|
||||
subprocess_read_cb on_stdout, subprocess_read_cb on_stderr,
|
||||
char **error)
|
||||
@@ -234,8 +238,8 @@ int mp_subprocess(char **args, struct mp_cancel *cancel, void *ctx,
|
||||
char buf[4096];
|
||||
subprocess_read_cb read_cb;
|
||||
} pipes[2] = {
|
||||
{ .read_cb = on_stdout },
|
||||
{ .read_cb = on_stderr },
|
||||
{ .read_cb = on_stdout ? on_stdout : write_none },
|
||||
{ .read_cb = on_stderr ? on_stderr : write_none },
|
||||
};
|
||||
|
||||
// If the function exits before CreateProcess, there was an init error
|
||||
|
||||
@@ -416,6 +416,11 @@ void terminal_setup_getch(struct input_ctx *ictx)
|
||||
if (mp_make_wakeup_pipe(death_pipe) < 0)
|
||||
return;
|
||||
|
||||
// Disable reading from the terminal even if stdout is not a tty, to make
|
||||
// mpv ... | less
|
||||
// do the right thing.
|
||||
read_terminal = isatty(STDIN_FILENO) && isatty(STDOUT_FILENO);
|
||||
|
||||
input_ctx = ictx;
|
||||
|
||||
if (pthread_create(&input_thread, NULL, terminal_thread, NULL)) {
|
||||
@@ -455,6 +460,7 @@ void terminal_uninit(void)
|
||||
}
|
||||
|
||||
getch2_enabled = 0;
|
||||
read_terminal = false;
|
||||
}
|
||||
|
||||
bool terminal_in_background(void)
|
||||
@@ -477,18 +483,13 @@ int terminal_init(void)
|
||||
assert(!getch2_enabled);
|
||||
getch2_enabled = 1;
|
||||
|
||||
// Disable reading from the terminal even if stdout is not a tty, to make
|
||||
// mpv ... | less
|
||||
// do the right thing.
|
||||
read_terminal = isatty(STDIN_FILENO) && isatty(STDOUT_FILENO);
|
||||
|
||||
// handlers to fix terminal settings
|
||||
setsigaction(SIGCONT, continue_sighandler, 0, true);
|
||||
setsigaction(SIGTSTP, stop_sighandler, SA_RESETHAND, false);
|
||||
setsigaction(SIGTTIN, SIG_IGN, 0, true);
|
||||
setsigaction(SIGTTOU, SIG_IGN, 0, true);
|
||||
|
||||
// don't crash on SIGPIPE caused by semi-broken libraries
|
||||
// get sane behavior, instead of hysteric UNIX-nonsense
|
||||
setsigaction(SIGPIPE, SIG_IGN, 0, true);
|
||||
|
||||
getch2_poll();
|
||||
|
||||
+6
-8
@@ -227,15 +227,13 @@ void reinit_audio_chain(struct MPContext *mpctx)
|
||||
|
||||
struct af_stream *afs = mpctx->d_audio->afilter;
|
||||
|
||||
afs->output = (struct mp_audio){0};
|
||||
if (mpctx->ao) {
|
||||
ao_get_format(mpctx->ao, &afs->output);
|
||||
} else {
|
||||
afs->output = (struct mp_audio){0};
|
||||
} else if (!AF_FORMAT_IS_SPECIAL(in_format.format)) {
|
||||
afs->output.rate = opts->force_srate;
|
||||
mp_audio_set_format(&afs->output, opts->audio_output_format);
|
||||
// automatic downmix
|
||||
if (!AF_FORMAT_IS_SPECIAL(in_format.format))
|
||||
mp_audio_set_channels(&afs->output, &opts->audio_output_channels);
|
||||
mp_audio_set_channels(&afs->output, &opts->audio_output_channels);
|
||||
}
|
||||
|
||||
// filter input format: same as codec's output format:
|
||||
@@ -460,8 +458,7 @@ static void do_fill_audio_out_buffers(struct MPContext *mpctx, double endpts)
|
||||
mpctx->d_audio->init_retries += 1;
|
||||
if (mpctx->d_audio->init_retries >= 50) {
|
||||
MP_ERR(mpctx, "Error initializing audio.\n");
|
||||
struct track *track = mpctx->current_track[0][STREAM_AUDIO];
|
||||
mp_deselect_track(mpctx, track);
|
||||
error_on_track(mpctx, mpctx->current_track[0][STREAM_AUDIO]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -508,6 +505,8 @@ static void do_fill_audio_out_buffers(struct MPContext *mpctx, double endpts)
|
||||
mpctx->sleeptime = 0;
|
||||
return; // retry on next iteration
|
||||
}
|
||||
if (status == AD_ERR)
|
||||
mpctx->sleeptime = 0;
|
||||
}
|
||||
|
||||
// If EOF was reached before, but now something can be decoded, try to
|
||||
@@ -515,7 +514,6 @@ static void do_fill_audio_out_buffers(struct MPContext *mpctx, double endpts)
|
||||
// later. Retrying is needed to get the correct sync PTS.
|
||||
if (mpctx->audio_status >= STATUS_DRAINING && status == AD_OK) {
|
||||
mpctx->audio_status = STATUS_SYNCING;
|
||||
mpctx->sleeptime = 0;
|
||||
return; // retry on next iteration
|
||||
}
|
||||
|
||||
|
||||
+50
-7
@@ -108,6 +108,7 @@ struct mpv_handle {
|
||||
uint64_t event_mask;
|
||||
bool queued_wakeup;
|
||||
bool choke_warning;
|
||||
int suspend_count;
|
||||
|
||||
mpv_event *events; // ringbuffer of max_events entries
|
||||
int max_events; // allocated number of entries in events
|
||||
@@ -300,12 +301,47 @@ void mpv_set_wakeup_callback(mpv_handle *ctx, void (*cb)(void *d), void *d)
|
||||
|
||||
void mpv_suspend(mpv_handle *ctx)
|
||||
{
|
||||
mp_dispatch_suspend(ctx->mpctx->dispatch);
|
||||
bool do_suspend = false;
|
||||
|
||||
pthread_mutex_lock(&ctx->lock);
|
||||
if (ctx->suspend_count == INT_MAX) {
|
||||
MP_ERR(ctx, "suspend counter overflow");
|
||||
} else {
|
||||
do_suspend = ctx->suspend_count == 0;
|
||||
ctx->suspend_count++;
|
||||
}
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
|
||||
if (do_suspend)
|
||||
mp_dispatch_suspend(ctx->mpctx->dispatch);
|
||||
}
|
||||
|
||||
void mpv_resume(mpv_handle *ctx)
|
||||
{
|
||||
mp_dispatch_resume(ctx->mpctx->dispatch);
|
||||
bool do_resume = false;
|
||||
|
||||
pthread_mutex_lock(&ctx->lock);
|
||||
if (ctx->suspend_count == 0) {
|
||||
MP_ERR(ctx, "suspend counter underflow");
|
||||
} else {
|
||||
do_resume = ctx->suspend_count == 1;
|
||||
ctx->suspend_count--;
|
||||
}
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
|
||||
if (do_resume)
|
||||
mp_dispatch_resume(ctx->mpctx->dispatch);
|
||||
}
|
||||
|
||||
void mp_resume_all(mpv_handle *ctx)
|
||||
{
|
||||
pthread_mutex_lock(&ctx->lock);
|
||||
bool do_resume = ctx->suspend_count > 0;
|
||||
ctx->suspend_count = 0;
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
|
||||
if (do_resume)
|
||||
mp_dispatch_resume(ctx->mpctx->dispatch);
|
||||
}
|
||||
|
||||
static void lock_core(mpv_handle *ctx)
|
||||
@@ -325,6 +361,8 @@ void mpv_detach_destroy(mpv_handle *ctx)
|
||||
if (!ctx)
|
||||
return;
|
||||
|
||||
mp_resume_all(ctx);
|
||||
|
||||
pthread_mutex_lock(&ctx->lock);
|
||||
// reserved_events equals the number of asynchronous requests that weren't
|
||||
// yet replied. In order to avoid that trying to reply to a removed client
|
||||
@@ -665,8 +703,6 @@ mpv_event *mpv_wait_event(mpv_handle *ctx, double timeout)
|
||||
|
||||
if (timeout < 0)
|
||||
timeout = 1e20;
|
||||
if (ctx->queued_wakeup)
|
||||
timeout = 0;
|
||||
|
||||
int64_t deadline = mp_add_timeout(mp_time_us(), timeout);
|
||||
|
||||
@@ -674,6 +710,13 @@ mpv_event *mpv_wait_event(mpv_handle *ctx, double timeout)
|
||||
talloc_free_children(event);
|
||||
|
||||
while (1) {
|
||||
if (ctx->queued_wakeup)
|
||||
deadline = 0;
|
||||
// This will almost surely lead to a deadlock. (Polling is still ok.)
|
||||
if (ctx->suspend_count && timeout > 0) {
|
||||
MP_ERR(ctx, "attempting to wait while core is suspended");
|
||||
break;
|
||||
}
|
||||
if (ctx->num_events) {
|
||||
*event = ctx->events[ctx->first_event];
|
||||
ctx->first_event = (ctx->first_event + 1) % ctx->max_events;
|
||||
@@ -691,13 +734,13 @@ mpv_event *mpv_wait_event(mpv_handle *ctx, double timeout)
|
||||
event->event_id = MPV_EVENT_LOG_MESSAGE;
|
||||
struct mpv_event_log_message *cmsg = talloc_ptrtype(event, cmsg);
|
||||
*cmsg = (struct mpv_event_log_message){
|
||||
.prefix = talloc_steal(event, msg->prefix),
|
||||
.prefix = msg->prefix,
|
||||
.level = mp_log_levels[msg->level],
|
||||
.log_level = mp_mpv_log_levels[msg->level],
|
||||
.text = talloc_steal(event, msg->text),
|
||||
.text = msg->text,
|
||||
};
|
||||
talloc_steal(event, msg);
|
||||
event->data = cmsg;
|
||||
talloc_free(msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,8 @@ struct mpv_handle *mp_new_client(struct mp_client_api *clients, const char *name
|
||||
struct mp_log *mp_client_get_log(struct mpv_handle *ctx);
|
||||
struct MPContext *mp_client_get_core(struct mpv_handle *ctx);
|
||||
|
||||
void mp_resume_all(struct mpv_handle *ctx);
|
||||
|
||||
// m_option.c
|
||||
void *node_get_alloc(struct mpv_node *node);
|
||||
|
||||
|
||||
+83
-55
@@ -23,6 +23,7 @@
|
||||
#include <stdbool.h>
|
||||
#include <assert.h>
|
||||
#include <time.h>
|
||||
#include <math.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
@@ -62,11 +63,9 @@
|
||||
#if HAVE_SYS_MMAN_H
|
||||
#include <sys/mman.h>
|
||||
#endif
|
||||
#ifndef __MINGW32__
|
||||
#include <sys/wait.h>
|
||||
#endif
|
||||
|
||||
#include "osdep/io.h"
|
||||
#include "osdep/subprocess.h"
|
||||
|
||||
#include "core.h"
|
||||
|
||||
@@ -568,10 +567,14 @@ static int mp_property_percent_pos(void *ctx, struct m_property *prop,
|
||||
.max = 100,
|
||||
};
|
||||
return M_PROPERTY_OK;
|
||||
case M_PROPERTY_PRINT:
|
||||
*(char **)arg = talloc_asprintf(NULL, "%d", get_percent_pos(mpctx));
|
||||
case M_PROPERTY_PRINT: {
|
||||
int pos = get_percent_pos(mpctx);
|
||||
if (pos < 0)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
*(char **)arg = talloc_asprintf(NULL, "%d", pos);
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
}
|
||||
return M_PROPERTY_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
@@ -690,6 +693,7 @@ static int mp_property_chapter(void *ctx, struct m_property *prop,
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
int chapter = get_current_chapter(mpctx);
|
||||
int num = get_chapter_count(mpctx);
|
||||
if (chapter < -1)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
|
||||
@@ -702,7 +706,7 @@ static int mp_property_chapter(void *ctx, struct m_property *prop,
|
||||
.type = CONF_TYPE_INT,
|
||||
.flags = M_OPT_MIN | M_OPT_MAX,
|
||||
.min = -1,
|
||||
.max = get_chapter_count(mpctx) - 1,
|
||||
.max = num - 1,
|
||||
};
|
||||
return M_PROPERTY_OK;
|
||||
case M_PROPERTY_PRINT: {
|
||||
@@ -716,6 +720,8 @@ static int mp_property_chapter(void *ctx, struct m_property *prop,
|
||||
if (action == M_PROPERTY_SWITCH) {
|
||||
struct m_property_switch_arg *sarg = arg;
|
||||
step_all = ROUND(sarg->inc);
|
||||
if (num < 2) // semi-broken file; ignore for user convenience
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
// Check threshold for relative backward seeks
|
||||
if (mpctx->opts->chapter_seek_threshold >= 0 && step_all < 0) {
|
||||
double current_chapter_start =
|
||||
@@ -732,7 +738,7 @@ static int mp_property_chapter(void *ctx, struct m_property *prop,
|
||||
chapter += step_all;
|
||||
if (chapter < -1)
|
||||
chapter = -1;
|
||||
if (chapter >= get_chapter_count(mpctx) && step_all > 0) {
|
||||
if (chapter >= num && step_all > 0) {
|
||||
mpctx->stop_play = PT_NEXT_ENTRY;
|
||||
} else {
|
||||
mp_seek_chapter(mpctx, chapter);
|
||||
@@ -1030,8 +1036,10 @@ static int tag_property(int action, void *arg, struct mp_tags *tags)
|
||||
res = talloc_asprintf_append_buffer(res, "%s: %s\n",
|
||||
tags->keys[n], tags->values[n]);
|
||||
}
|
||||
if (!res)
|
||||
res = talloc_strdup(NULL, "(empty)");
|
||||
*(char **)arg = res;
|
||||
return res ? M_PROPERTY_OK : M_PROPERTY_UNAVAILABLE;
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
case M_PROPERTY_KEY_ACTION: {
|
||||
struct m_property_action_arg *ka = arg;
|
||||
@@ -1098,22 +1106,16 @@ static int mp_property_vf_metadata(void *ctx, struct m_property *prop,
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
struct vf_chain *vf = mpctx->d_video->vfilter;
|
||||
|
||||
switch(action) {
|
||||
case M_PROPERTY_GET_TYPE:
|
||||
case M_PROPERTY_GET:
|
||||
case M_PROPERTY_GET_NODE:
|
||||
return M_PROPERTY_NOT_IMPLEMENTED;
|
||||
case M_PROPERTY_KEY_ACTION: {
|
||||
if (action == M_PROPERTY_KEY_ACTION) {
|
||||
struct m_property_action_arg *ka = arg;
|
||||
bstr key;
|
||||
char *rem;
|
||||
m_property_split_path(ka->key, &key, &rem);
|
||||
struct mp_tags vf_metadata;
|
||||
struct mp_tags vf_metadata = {0};
|
||||
switch (vf_control_by_label(vf, VFCTRL_GET_METADATA, &vf_metadata, key)) {
|
||||
case CONTROL_NA:
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
case CONTROL_UNKNOWN:
|
||||
return M_PROPERTY_UNKNOWN;
|
||||
case CONTROL_NA: // empty
|
||||
case CONTROL_OK:
|
||||
if (strlen(rem)) {
|
||||
struct m_property_action_arg next_ka = *ka;
|
||||
@@ -1127,7 +1129,6 @@ static int mp_property_vf_metadata(void *ctx, struct m_property *prop,
|
||||
return M_PROPERTY_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
return M_PROPERTY_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
@@ -2535,9 +2536,10 @@ static int mp_property_fps(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
if (!mpctx->d_video)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
return m_property_float_ro(action, arg, mpctx->d_video->fps);
|
||||
float fps = mpctx->d_video ? mpctx->d_video->fps : 0;
|
||||
if (fps < 0.1 || !isfinite(fps))
|
||||
return M_PROPERTY_UNAVAILABLE;;
|
||||
return m_property_float_ro(action, arg, fps);
|
||||
}
|
||||
|
||||
static int mp_property_vf_fps(void *ctx, struct m_property *prop,
|
||||
@@ -2868,10 +2870,16 @@ static int mp_property_playlist(void *ctx, struct m_property *prop,
|
||||
|
||||
for (struct playlist_entry *e = mpctx->playlist->first; e; e = e->next)
|
||||
{
|
||||
char *p = e->filename;
|
||||
if (!mp_is_url(bstr0(p))) {
|
||||
char *s = mp_basename(e->filename);
|
||||
if (s[0])
|
||||
p = s;
|
||||
}
|
||||
if (mpctx->playlist->current == e) {
|
||||
res = talloc_asprintf_append(res, "> %s <\n", e->filename);
|
||||
res = talloc_asprintf_append(res, "> %s <\n", p);
|
||||
} else {
|
||||
res = talloc_asprintf_append(res, "%s\n", e->filename);
|
||||
res = talloc_asprintf_append(res, "%s\n", p);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3342,14 +3350,14 @@ static const char *const *const mp_event_property_change[] = {
|
||||
"width", "height", "fps", "aspect", "vo-configured"),
|
||||
E(MPV_EVENT_AUDIO_RECONFIG, "audio-format", "audio-codec", "audio-bitrate",
|
||||
"samplerate", "channels", "audio"),
|
||||
E(MPV_EVENT_SEEK, "seeking"),
|
||||
E(MPV_EVENT_PLAYBACK_RESTART, "seeking"),
|
||||
E(MPV_EVENT_SEEK, "seeking", "core-idle"),
|
||||
E(MPV_EVENT_PLAYBACK_RESTART, "seeking", "core-idle"),
|
||||
E(MPV_EVENT_METADATA_UPDATE, "metadata"),
|
||||
E(MPV_EVENT_CHAPTER_CHANGE, "chapter", "chapter-metadata"),
|
||||
E(MP_EVENT_CACHE_UPDATE, "cache", "cache-free", "cache-used", "cache-idle",
|
||||
"demuxer-cache-duration", "demuxer-cache-idle"),
|
||||
"demuxer-cache-duration", "demuxer-cache-idle", "paused-for-cache"),
|
||||
E(MP_EVENT_WIN_RESIZE, "window-scale"),
|
||||
E(MP_EVENT_WIN_STATE, "window-minimized"),
|
||||
E(MP_EVENT_WIN_STATE, "window-minimized", "display-names"),
|
||||
};
|
||||
#undef E
|
||||
|
||||
@@ -3831,6 +3839,39 @@ static void overlay_uninit(struct MPContext *mpctx)
|
||||
osd_set_external2(mpctx->osd, NULL);
|
||||
}
|
||||
|
||||
struct subprocess_args {
|
||||
struct mp_log *log;
|
||||
char **args;
|
||||
};
|
||||
|
||||
static void *run_subprocess(void *ptr)
|
||||
{
|
||||
struct subprocess_args *p = ptr;
|
||||
pthread_detach(pthread_self());
|
||||
|
||||
mp_msg_flush_status_line(p->log);
|
||||
|
||||
char *err = NULL;
|
||||
if (mp_subprocess(p->args, NULL, NULL, NULL, NULL, &err) < 0)
|
||||
mp_err(p->log, "Running subprocess failed: %s\n", err);
|
||||
|
||||
talloc_free(p);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void subprocess_detached(struct mp_log *log, char **args)
|
||||
{
|
||||
struct subprocess_args *p = talloc_zero(NULL, struct subprocess_args);
|
||||
p->log = mp_log_new(p, log, NULL);
|
||||
int num_args = 0;
|
||||
for (int n = 0; args[n]; n++)
|
||||
MP_TARRAY_APPEND(p, p->args, num_args, talloc_strdup(p, args[n]));
|
||||
MP_TARRAY_APPEND(p, p->args, num_args, NULL);
|
||||
pthread_t thread;
|
||||
if (pthread_create(&thread, NULL, run_subprocess, p))
|
||||
talloc_free(p);
|
||||
}
|
||||
|
||||
struct cycle_counter {
|
||||
char **args;
|
||||
int counter;
|
||||
@@ -4166,7 +4207,7 @@ int run_command(MPContext *mpctx, mp_cmd_t *cmd)
|
||||
if (!mpctx->num_sources)
|
||||
return -1;
|
||||
struct osd_sub_state state;
|
||||
get_osd_sub_state(mpctx, 0, &state);
|
||||
update_osd_sub_state(mpctx, 0, &state);
|
||||
double refpts = get_current_time(mpctx);
|
||||
if (state.dec_sub && refpts != MP_NOPTS_VALUE) {
|
||||
double a[2];
|
||||
@@ -4255,16 +4296,16 @@ int run_command(MPContext *mpctx, mp_cmd_t *cmd)
|
||||
bool append = cmd->args[1].v.i;
|
||||
struct playlist *pl = playlist_parse_file(filename, mpctx->global);
|
||||
if (pl) {
|
||||
prepare_playlist(mpctx, pl);
|
||||
struct playlist_entry *new = pl->current;
|
||||
if (!append)
|
||||
playlist_clear(mpctx->playlist);
|
||||
playlist_append_entries(mpctx->playlist, pl);
|
||||
talloc_free(pl);
|
||||
|
||||
if (!append && mpctx->playlist->first) {
|
||||
struct playlist_entry *e =
|
||||
mp_check_playlist_resume(mpctx, mpctx->playlist);
|
||||
mp_set_playlist_entry(mpctx, e ? e : mpctx->playlist->first);
|
||||
}
|
||||
if (!append && mpctx->playlist->first)
|
||||
mp_set_playlist_entry(mpctx, new ? new : mpctx->playlist->first);
|
||||
|
||||
mp_notify_property(mpctx, "playlist");
|
||||
} else {
|
||||
MP_ERR(mpctx, "Unable to load playlist %s.\n", filename);
|
||||
@@ -4350,7 +4391,9 @@ int run_command(MPContext *mpctx, mp_cmd_t *cmd)
|
||||
struct track *sub = mp_add_subtitles(mpctx, cmd->args[0].v.s);
|
||||
if (!sub)
|
||||
return -1;
|
||||
if (cmd->args[1].v.i != 1) {
|
||||
if (cmd->args[1].v.i == 1) {
|
||||
sub->no_default = true;
|
||||
} else {
|
||||
mp_switch_track(mpctx, sub->type, sub);
|
||||
mp_mark_user_track_selection(mpctx, 0, sub->type);
|
||||
}
|
||||
@@ -4393,28 +4436,10 @@ int run_command(MPContext *mpctx, mp_cmd_t *cmd)
|
||||
break;
|
||||
|
||||
case MP_CMD_RUN: {
|
||||
#ifndef __MINGW32__
|
||||
mp_msg_flush_status_line(mpctx->global);
|
||||
char *args[MP_CMD_MAX_ARGS + 1] = {0};
|
||||
for (int n = 0; n < cmd->nargs; n++)
|
||||
args[n] = cmd->args[n].v.s;
|
||||
pid_t child = fork();
|
||||
if (child == 0) {
|
||||
// Fork twice; the second process will be made child of pid 1 as
|
||||
// soon as the first process exists, and we don't have to care
|
||||
// about having to wait for the second process to terminate.
|
||||
if (fork() == 0) {
|
||||
execvp(args[0], args);
|
||||
// mp_msg() is not safe to be called from a forked process.
|
||||
char s[] = "Executing program failed.\n";
|
||||
write(2, s, sizeof(s) - 1);
|
||||
_exit(1);
|
||||
}
|
||||
_exit(0);
|
||||
}
|
||||
int st;
|
||||
while (child != -1 && waitpid(child, &st, 0) < 0 && errno == EINTR) {}
|
||||
#endif
|
||||
subprocess_detached(mpctx->log, args);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -4492,7 +4517,7 @@ int run_command(MPContext *mpctx, mp_cmd_t *cmd)
|
||||
char *sep = strchr(name, '/');
|
||||
char *target = NULL;
|
||||
char space[MAX_CLIENT_NAME];
|
||||
if (name) {
|
||||
if (sep) {
|
||||
snprintf(space, sizeof(space), "%.*s", (int)(sep - name), name);
|
||||
target = space;
|
||||
name = sep + 1;
|
||||
@@ -4624,8 +4649,11 @@ static void command_event(struct MPContext *mpctx, int event, void *arg)
|
||||
}
|
||||
ctx->prev_pts = now;
|
||||
}
|
||||
if (event == MPV_EVENT_SEEK) {
|
||||
if (event == MPV_EVENT_SEEK)
|
||||
ctx->prev_pts = MP_NOPTS_VALUE;
|
||||
if (event == MPV_EVENT_END_FILE || event == MPV_EVENT_FILE_LOADED) {
|
||||
// Update chapters - does nothing if something else is visible.
|
||||
set_osd_bar_chapters(mpctx, OSD_BAR_SEEK);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -243,6 +243,8 @@ static const char *const backup_properties[] = {
|
||||
"options/ass-use-margins",
|
||||
"options/ass-vsfilter-aspect-compat",
|
||||
"options/ass-style-override",
|
||||
"options/ab-loop-a",
|
||||
"options/ab-loop-b",
|
||||
0
|
||||
};
|
||||
|
||||
|
||||
+4
-2
@@ -390,6 +390,7 @@ void mp_set_playlist_entry(struct MPContext *mpctx, struct playlist_entry *e);
|
||||
void mp_play_files(struct MPContext *mpctx);
|
||||
void update_demuxer_properties(struct MPContext *mpctx);
|
||||
void reselect_demux_streams(struct MPContext *mpctx);
|
||||
void prepare_playlist(struct MPContext *mpctx, struct playlist *pl);
|
||||
|
||||
// main.c
|
||||
int mpv_main(int argc, char *argv[]);
|
||||
@@ -397,6 +398,7 @@ int mp_initialize(struct MPContext *mpctx);
|
||||
struct MPContext *mp_create(void);
|
||||
void mp_destroy(struct MPContext *mpctx);
|
||||
void mp_print_version(struct mp_log *log, int always);
|
||||
void wakeup_playloop(void *ctx);
|
||||
|
||||
// misc.c
|
||||
double get_start_time(struct MPContext *mpctx);
|
||||
@@ -470,8 +472,8 @@ void uninit_sub_all(struct MPContext *mpctx);
|
||||
void update_osd_msg(struct MPContext *mpctx);
|
||||
void update_subtitles(struct MPContext *mpctx);
|
||||
void uninit_sub_renderer(struct MPContext *mpctx);
|
||||
void get_osd_sub_state(struct MPContext *mpctx, int order,
|
||||
struct osd_sub_state *out_state);
|
||||
void update_osd_sub_state(struct MPContext *mpctx, int order,
|
||||
struct osd_sub_state *out_state);
|
||||
|
||||
// timeline/tl_matroska.c
|
||||
void build_ordered_chapter_timeline(struct MPContext *mpctx);
|
||||
|
||||
+2
-3
@@ -125,9 +125,8 @@ void mp_nav_init(struct MPContext *mpctx)
|
||||
|
||||
MP_VERBOSE(mpctx->nav_state, "enabling\n");
|
||||
|
||||
mp_input_enable_section(mpctx->input, "discnav", 0);
|
||||
mp_input_set_section_mouse_area(mpctx->input, "discnav-menu",
|
||||
INT_MIN, INT_MIN, INT_MAX, INT_MAX);
|
||||
mp_input_enable_section(mpctx->input, "discnav",
|
||||
MP_INPUT_ALLOW_VO_DRAGGING | MP_INPUT_ALLOW_HIDE_CURSOR);
|
||||
|
||||
update_state(mpctx);
|
||||
}
|
||||
|
||||
+46
-16
@@ -241,6 +241,15 @@ static void enable_demux_thread(struct MPContext *mpctx)
|
||||
if (mpctx->demuxer && mpctx->opts->demuxer_thread) {
|
||||
demux_set_wakeup_cb(mpctx->demuxer, wakeup_demux, mpctx);
|
||||
demux_start_thread(mpctx->demuxer);
|
||||
for (int n = 0; n < mpctx->num_tracks; n++) {
|
||||
struct track *track = mpctx->tracks[n];
|
||||
if (track->is_external && track->stream &&
|
||||
track->stream->type != STREAM_SUB)
|
||||
{
|
||||
demux_set_wakeup_cb(track->demuxer, wakeup_demux, mpctx);
|
||||
demux_start_thread(track->demuxer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,14 +309,17 @@ double timeline_set_from_time(struct MPContext *mpctx, double pts, bool *need_re
|
||||
{
|
||||
if (pts < 0)
|
||||
pts = 0;
|
||||
|
||||
int new = mpctx->num_timeline_parts - 1;
|
||||
for (int i = 0; i < mpctx->num_timeline_parts; i++) {
|
||||
struct timeline_part *p = mpctx->timeline + i;
|
||||
if (pts < (p + 1)->start) {
|
||||
*need_reset = timeline_set_part(mpctx, i, false);
|
||||
return pts - p->start + p->source_start;
|
||||
if (pts < mpctx->timeline[i + 1].start) {
|
||||
new = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return MP_NOPTS_VALUE;
|
||||
|
||||
*need_reset = timeline_set_part(mpctx, new, false);
|
||||
return pts - mpctx->timeline[new].start + mpctx->timeline[new].source_start;
|
||||
}
|
||||
|
||||
static int find_new_tid(struct MPContext *mpctx, enum stream_type t)
|
||||
@@ -718,12 +730,30 @@ struct track *mp_add_subtitles(struct MPContext *mpctx, char *filename)
|
||||
STREAM_SUB);
|
||||
}
|
||||
|
||||
// Do stuff to a newly loaded playlist. This includes any processing that may
|
||||
// be required after loading a playlist.
|
||||
void prepare_playlist(struct MPContext *mpctx, struct playlist *pl)
|
||||
{
|
||||
struct MPOpts *opts = mpctx->opts;
|
||||
|
||||
if (opts->shuffle)
|
||||
playlist_shuffle(pl);
|
||||
|
||||
if (opts->merge_files)
|
||||
merge_playlist_files(pl);
|
||||
|
||||
pl->current = mp_check_playlist_resume(mpctx, pl);
|
||||
if (!pl->current)
|
||||
pl->current = pl->first;
|
||||
}
|
||||
|
||||
// Replace the current playlist entry with playlist contents. Moves the entries
|
||||
// from the given playlist pl, so the entries don't actually need to be copied.
|
||||
static void transfer_playlist(struct MPContext *mpctx, struct playlist *pl)
|
||||
{
|
||||
if (pl->first) {
|
||||
struct playlist_entry *new = mp_check_playlist_resume(mpctx, pl);
|
||||
prepare_playlist(mpctx, pl);
|
||||
struct playlist_entry *new = pl->current;
|
||||
playlist_transfer_entries(mpctx->playlist, pl);
|
||||
// current entry is replaced
|
||||
if (mpctx->playlist->current)
|
||||
@@ -900,7 +930,6 @@ static void play_current_file(struct MPContext *mpctx)
|
||||
mpctx->paused = false;
|
||||
mpctx->paused_for_cache = false;
|
||||
mpctx->playing_msg_shown = false;
|
||||
mpctx->step_frames = 0;
|
||||
mpctx->backstep_active = false;
|
||||
mpctx->audio_delay = 0;
|
||||
mpctx->max_frames = -1;
|
||||
@@ -964,6 +993,7 @@ static void play_current_file(struct MPContext *mpctx)
|
||||
|
||||
if (opts->stream_dump && opts->stream_dump[0]) {
|
||||
stream_dump(mpctx);
|
||||
mpctx->error_playing = 1;
|
||||
goto terminate_playback;
|
||||
}
|
||||
|
||||
@@ -1090,16 +1120,11 @@ goto_reopen_demuxer: ;
|
||||
}
|
||||
#endif
|
||||
|
||||
reinit_video_chain(mpctx);
|
||||
reinit_audio_chain(mpctx);
|
||||
reinit_subs(mpctx, 0);
|
||||
reinit_subs(mpctx, 1);
|
||||
|
||||
//==================== START PLAYING =======================
|
||||
|
||||
if (!mpctx->d_video && !mpctx->d_audio) {
|
||||
struct demuxer *d = mpctx->demuxer;
|
||||
if (!mpctx->current_track[0][STREAM_VIDEO] &&
|
||||
!mpctx->current_track[0][STREAM_AUDIO])
|
||||
{
|
||||
MP_FATAL(mpctx, "No video or audio streams selected.\n");
|
||||
struct demuxer *d = mpctx->demuxer;
|
||||
if (d->stream->uncached_type == STREAMTYPE_DVB) {
|
||||
int dir = mpctx->last_dvb_step;
|
||||
if (demux_stream_control(d, STREAM_CTRL_DVB_STEP_CHANNEL, &dir) > 0)
|
||||
@@ -1109,6 +1134,11 @@ goto_reopen_demuxer: ;
|
||||
goto terminate_playback;
|
||||
}
|
||||
|
||||
reinit_video_chain(mpctx);
|
||||
reinit_audio_chain(mpctx);
|
||||
reinit_subs(mpctx, 0);
|
||||
reinit_subs(mpctx, 1);
|
||||
|
||||
MP_VERBOSE(mpctx, "Starting playback...\n");
|
||||
|
||||
if (mpctx->max_frames == 0) {
|
||||
|
||||
+7
-28
@@ -81,7 +81,6 @@ struct script_ctx {
|
||||
struct mp_log *log;
|
||||
struct mpv_handle *client;
|
||||
struct MPContext *mpctx;
|
||||
int suspended;
|
||||
};
|
||||
|
||||
#if LUA_VERSION_NUM <= 501
|
||||
@@ -388,8 +387,7 @@ static int load_lua(struct mpv_handle *client, const char *fname)
|
||||
r = 0;
|
||||
|
||||
error_out:
|
||||
if (ctx->suspended)
|
||||
mpv_resume(ctx->client);
|
||||
mp_resume_all(client);
|
||||
if (ctx->state)
|
||||
lua_close(ctx->state);
|
||||
talloc_free(ctx);
|
||||
@@ -447,33 +445,20 @@ static int script_find_config_file(lua_State *L)
|
||||
static int script_suspend(lua_State *L)
|
||||
{
|
||||
struct script_ctx *ctx = get_ctx(L);
|
||||
if (!ctx->suspended)
|
||||
mpv_suspend(ctx->client);
|
||||
ctx->suspended++;
|
||||
mpv_suspend(ctx->client);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int script_resume(lua_State *L)
|
||||
{
|
||||
struct script_ctx *ctx = get_ctx(L);
|
||||
if (ctx->suspended < 1)
|
||||
luaL_error(L, "trying to resume, but core is not suspended");
|
||||
ctx->suspended--;
|
||||
if (!ctx->suspended)
|
||||
mpv_resume(ctx->client);
|
||||
mpv_resume(ctx->client);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void resume_all(struct script_ctx *ctx)
|
||||
{
|
||||
if (ctx->suspended)
|
||||
mpv_resume(ctx->client);
|
||||
ctx->suspended = 0;
|
||||
}
|
||||
|
||||
static int script_resume_all(lua_State *L)
|
||||
{
|
||||
resume_all(get_ctx(L));
|
||||
mp_resume_all(get_ctx(L)->client);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -483,13 +468,7 @@ static int script_wait_event(lua_State *L)
|
||||
{
|
||||
struct script_ctx *ctx = get_ctx(L);
|
||||
|
||||
double timeout = luaL_optnumber(L, 1, 1e20);
|
||||
|
||||
// This will almost surely lead to a deadlock. (Polling is still ok.)
|
||||
if (ctx->suspended && timeout > 0)
|
||||
luaL_error(L, "attempting to wait while core is suspended");
|
||||
|
||||
mpv_event *event = mpv_wait_event(ctx->client, timeout);
|
||||
mpv_event *event = mpv_wait_event(ctx->client, luaL_optnumber(L, 1, 1e20));
|
||||
|
||||
lua_newtable(L); // event
|
||||
lua_pushstring(L, mpv_event_name(event->event_id)); // event name
|
||||
@@ -1043,7 +1022,7 @@ static int script_input_enable_section(lua_State *L)
|
||||
} else if (bstr_equals0(val, "exclusive")) {
|
||||
flags |= MP_INPUT_EXCLUSIVE;
|
||||
} else {
|
||||
luaL_error(L, "invalid flag: '%.*s'", BSTR_P(val));
|
||||
luaL_error(L, "invalid flag");
|
||||
}
|
||||
}
|
||||
mp_input_enable_section(mpctx->input, section, flags);
|
||||
@@ -1191,7 +1170,7 @@ static int script_subprocess(lua_State *L)
|
||||
luaL_checktype(L, 1, LUA_TTABLE);
|
||||
void *tmp = mp_lua_PITA(L);
|
||||
|
||||
resume_all(ctx);
|
||||
mp_resume_all(ctx->client);
|
||||
|
||||
lua_getfield(L, 1, "args"); // args
|
||||
int num_args = mp_lua_len(L, -1);
|
||||
|
||||
@@ -21,7 +21,7 @@ function ass_mt.draw_stop(ass)
|
||||
end
|
||||
|
||||
function ass_mt.coord(ass, x, y)
|
||||
local scale = math.pow(2, ass.scale - 1)
|
||||
local scale = 2 ^ (ass.scale - 1)
|
||||
local ix = math.ceil(x * scale)
|
||||
local iy = math.ceil(y * scale)
|
||||
ass.text = string.format("%s %d %d", ass.text, ix, iy)
|
||||
|
||||
+23
-18
@@ -177,9 +177,9 @@ local function add_binding(attrs, key, name, fn, rp)
|
||||
if event == "r" and not repeatable then
|
||||
return
|
||||
end
|
||||
if is_mouse and event == "u" then
|
||||
if is_mouse and (event == "u" or event == "p") then
|
||||
fn()
|
||||
elseif (not is_mouse) and (event == "d" or event == "r") then
|
||||
elseif (not is_mouse) and (event == "d" or event == "r" or event == "p") then
|
||||
fn()
|
||||
end
|
||||
end
|
||||
@@ -485,7 +485,9 @@ function mp.add_hook(name, pri, cb)
|
||||
mp.commandv("hook_add", name, id, pri)
|
||||
end
|
||||
|
||||
function mp.format_table(t, set)
|
||||
local mp_utils = package.loaded["mp.utils"]
|
||||
|
||||
function mp_utils.format_table(t, set)
|
||||
if not set then
|
||||
set = { [t] = true }
|
||||
end
|
||||
@@ -508,30 +510,33 @@ function mp.format_table(t, set)
|
||||
vals[#keys] = v
|
||||
end
|
||||
end
|
||||
local function fmtval(v)
|
||||
if type(v) == "string" then
|
||||
return "\"" .. v .. "\""
|
||||
elseif type(v) == "table" then
|
||||
if set[v] then
|
||||
return "[cycle]"
|
||||
end
|
||||
set[v] = true
|
||||
return mp.format_table(v, set)
|
||||
else
|
||||
return tostring(v)
|
||||
end
|
||||
end
|
||||
for i = 1, #keys do
|
||||
if #res > 1 then
|
||||
res = res .. ", "
|
||||
end
|
||||
if i > arr then
|
||||
res = res .. fmtval(keys[i]) .. " = "
|
||||
res = res .. mp_utils.to_string(keys[i], set) .. " = "
|
||||
end
|
||||
res = res .. fmtval(vals[i])
|
||||
res = res .. mp_utils.to_string(vals[i], set)
|
||||
end
|
||||
res = res .. "}"
|
||||
return res
|
||||
end
|
||||
|
||||
function mp_utils.to_string(v, set)
|
||||
if type(v) == "string" then
|
||||
return "\"" .. v .. "\""
|
||||
elseif type(v) == "table" then
|
||||
if set then
|
||||
if set[v] then
|
||||
return "[cycle]"
|
||||
end
|
||||
set[v] = true
|
||||
end
|
||||
return mp_utils.format_table(v, set)
|
||||
else
|
||||
return tostring(v)
|
||||
end
|
||||
end
|
||||
|
||||
return {}
|
||||
+72
-5
@@ -71,6 +71,8 @@ local state = {
|
||||
fullscreen = false,
|
||||
timer = nil,
|
||||
cache_idle = false,
|
||||
idle = false,
|
||||
enabled = true,
|
||||
}
|
||||
|
||||
|
||||
@@ -1723,6 +1725,7 @@ function render()
|
||||
for k,cords in pairs(osc_param.areas["showhide"]) do
|
||||
mp.set_mouse_area(cords.x1, cords.y1, cords.x2, cords.y2, "showhide")
|
||||
end
|
||||
do_enable_keybindings()
|
||||
|
||||
--mouse input area
|
||||
local mouse_over_osc = false
|
||||
@@ -1846,16 +1849,61 @@ end
|
||||
|
||||
-- called by mpv on every frame
|
||||
function tick()
|
||||
if (state.fullscreen and user_opts.showfullscreen) or (not state.fullscreen and user_opts.showwindowed) then
|
||||
if (state.idle) then
|
||||
|
||||
-- render idle message
|
||||
msg.debug("idle message")
|
||||
local icon_x, icon_y = 320 - 26, 140
|
||||
|
||||
local ass = assdraw.ass_new()
|
||||
ass:new_event()
|
||||
ass:pos(icon_x, icon_y)
|
||||
ass:append("{\\rDefault\\an7\\c&H430142&\\1a&H00&\\bord0\\shad0\\p6}m 1605 828 b 1605 1175 1324 1456 977 1456 631 1456 349 1175 349 828 349 482 631 200 977 200 1324 200 1605 482 1605 828{\\p0}")
|
||||
ass:new_event()
|
||||
ass:pos(icon_x, icon_y)
|
||||
ass:append("{\\rDefault\\an7\\c&HDDDBDD&\\1a&H00&\\bord0\\shad0\\p6}m 1296 910 b 1296 1131 1117 1310 897 1310 676 1310 497 1131 497 910 497 689 676 511 897 511 1117 511 1296 689 1296 910{\\p0}")
|
||||
ass:new_event()
|
||||
ass:pos(icon_x, icon_y)
|
||||
ass:append("{\\rDefault\\an7\\c&H691F69&\\1a&H00&\\bord0\\shad0\\p6}m 762 1113 l 762 708 b 881 776 1000 843 1119 911 1000 978 881 1046 762 1113{\\p0}")
|
||||
ass:new_event()
|
||||
ass:pos(icon_x, icon_y)
|
||||
ass:append("{\\rDefault\\an7\\c&H682167&\\1a&H00&\\bord0\\shad0\\p6}m 925 42 b 463 42 87 418 87 880 87 1343 463 1718 925 1718 1388 1718 1763 1343 1763 880 1763 418 1388 42 925 42 m 925 42 m 977 200 b 1324 200 1605 482 1605 828 1605 1175 1324 1456 977 1456 631 1456 349 1175 349 828 349 482 631 200 977 200{\\p0}")
|
||||
ass:new_event()
|
||||
ass:pos(icon_x, icon_y)
|
||||
ass:append("{\\rDefault\\an7\\c&H753074&\\1a&H00&\\bord0\\shad0\\p6}m 977 198 b 630 198 348 480 348 828 348 1176 630 1458 977 1458 1325 1458 1607 1176 1607 828 1607 480 1325 198 977 198 m 977 198 m 977 202 b 1323 202 1604 483 1604 828 1604 1174 1323 1454 977 1454 632 1454 351 1174 351 828 351 483 632 202 977 202{\\p0}")
|
||||
ass:new_event()
|
||||
ass:pos(icon_x, icon_y)
|
||||
ass:append("{\\rDefault\\an7\\c&HE5E5E5&\\1a&H00&\\bord0\\shad0\\p6}m 895 10 b 401 10 0 410 0 905 0 1399 401 1800 895 1800 1390 1800 1790 1399 1790 905 1790 410 1390 10 895 10 m 895 10 m 925 42 b 1388 42 1763 418 1763 880 1763 1343 1388 1718 925 1718 463 1718 87 1343 87 880 87 418 463 42 925 42{\\p0}")
|
||||
ass:new_event()
|
||||
ass:pos(320, icon_y+65)
|
||||
ass:an(8)
|
||||
ass:append("Drop files to play here.")
|
||||
mp.set_osd_ass(640, 360, ass.text)
|
||||
|
||||
mp.disable_key_bindings("showhide")
|
||||
|
||||
|
||||
elseif (state.fullscreen and user_opts.showfullscreen)
|
||||
or (not state.fullscreen and user_opts.showwindowed) then
|
||||
|
||||
-- render the OSC
|
||||
render()
|
||||
else
|
||||
-- Flush OSD
|
||||
mp.set_osd_ass(osc_param.playresy, osc_param.playresy, "")
|
||||
end
|
||||
end
|
||||
|
||||
function do_enable_keybindings()
|
||||
if state.enabled then
|
||||
mp.enable_key_bindings("showhide", "allow-vo-dragging|allow-hide-cursor")
|
||||
end
|
||||
end
|
||||
|
||||
function enable_osc(enable)
|
||||
state.enabled = enable
|
||||
if enable then
|
||||
mp.enable_key_bindings("showhide")
|
||||
do_enable_keybindings()
|
||||
show_osc()
|
||||
else
|
||||
hide_osc()
|
||||
@@ -1872,16 +1920,35 @@ mp.register_event("tracks-changed", request_init)
|
||||
mp.register_script_message("enable-osc", function() enable_osc(true) end)
|
||||
mp.register_script_message("disable-osc", function() enable_osc(false) end)
|
||||
|
||||
mp.observe_property("fullscreen", "bool", function(name, val) state.fullscreen = val end)
|
||||
mp.observe_property("fullscreen", "bool",
|
||||
function(name, val)
|
||||
state.fullscreen = val
|
||||
end
|
||||
)
|
||||
mp.observe_property("idle", "bool",
|
||||
function(name, val)
|
||||
state.idle = val
|
||||
tick()
|
||||
end
|
||||
)
|
||||
mp.observe_property("pause", "bool", pause_state)
|
||||
mp.observe_property("cache-idle", "bool", cache_state)
|
||||
|
||||
mp.observe_property("disc-menu-active", "bool", function(name, val)
|
||||
if val == true then
|
||||
hide_osc()
|
||||
mp.disable_key_bindings("showhide")
|
||||
else
|
||||
do_enable_keybindings()
|
||||
end
|
||||
end)
|
||||
|
||||
-- mouse show/hide bindings
|
||||
mp.set_key_bindings({
|
||||
{"mouse_move", function(e) process_event("mouse_move", nil) end},
|
||||
{"mouse_leave", mouse_leave},
|
||||
}, "showhide")
|
||||
mp.enable_key_bindings("showhide", "allow-vo-dragging|allow-hide-cursor")
|
||||
}, "showhide", "force")
|
||||
do_enable_keybindings()
|
||||
|
||||
--mouse input bindings
|
||||
mp.set_key_bindings({
|
||||
|
||||
+84
-22
@@ -3,17 +3,41 @@ local msg = require 'mp.msg'
|
||||
|
||||
local ytdl = {
|
||||
path = "youtube-dl",
|
||||
minver = "2014.11.09",
|
||||
minver = "2014.11.26",
|
||||
vercheck = nil,
|
||||
}
|
||||
|
||||
mp.add_hook("on_load", 10, function ()
|
||||
local function exec(args)
|
||||
local ret = utils.subprocess({args = args})
|
||||
return ret.status, ret.stdout
|
||||
end
|
||||
|
||||
local function exec(args)
|
||||
local ret = utils.subprocess({args = args})
|
||||
return ret.status, ret.stdout
|
||||
-- return if it was explicitly set on the command line
|
||||
local function option_was_set(name)
|
||||
return mp.get_property_bool("option-info/" .. name .. "/set-from-commandline",
|
||||
false)
|
||||
end
|
||||
|
||||
-- youtube-dl may set special http headers for some sites (user-agent, cookies)
|
||||
local function set_http_headers(http_headers)
|
||||
if not http_headers then
|
||||
return
|
||||
end
|
||||
local headers = {}
|
||||
local useragent = http_headers["User-Agent"]
|
||||
if useragent and not option_was_set("user-agent") then
|
||||
mp.set_property("file-local-options/user-agent", useragent)
|
||||
end
|
||||
local cookies = http_headers["Cookie"]
|
||||
if cookies then
|
||||
headers[#headers + 1] = "Cookie: " .. cookies
|
||||
end
|
||||
if #headers > 0 and not option_was_set("http-header-fields") then
|
||||
mp.set_property_native("file-local-options/http-header-fields", headers)
|
||||
end
|
||||
end
|
||||
|
||||
mp.add_hook("on_load", 10, function ()
|
||||
local url = mp.get_property("stream-open-filename")
|
||||
|
||||
if (url:find("http://") == 1) or (url:find("https://") == 1)
|
||||
@@ -87,7 +111,7 @@ mp.add_hook("on_load", 10, function ()
|
||||
return
|
||||
end
|
||||
|
||||
msg.info("youtube-dl succeeded!")
|
||||
msg.verbose("youtube-dl succeeded!")
|
||||
|
||||
-- what did we get?
|
||||
if not (json["direct"] == nil) and (json["direct"] == true) then
|
||||
@@ -97,30 +121,67 @@ mp.add_hook("on_load", 10, function ()
|
||||
elseif not (json["_type"] == nil) and (json["_type"] == "playlist") then
|
||||
-- a playlist
|
||||
|
||||
local playlist = "#EXTM3U\n"
|
||||
for i, entry in pairs(json.entries) do
|
||||
local site = entry.url
|
||||
|
||||
-- some extractors will still return the full info for
|
||||
-- all clips in the playlist and the URL will point
|
||||
-- directly to the file in that case, which we don't
|
||||
-- want so get the webpage URL instead, which is what
|
||||
-- we want
|
||||
if not (entry["webpage_url"] == nil) then
|
||||
site = entry["webpage_url"]
|
||||
end
|
||||
|
||||
playlist = playlist .. "ytdl://" .. site .. "\n"
|
||||
if (#json.entries == 0) then
|
||||
msg.warn("Got empty playlist, nothing to play.")
|
||||
return
|
||||
end
|
||||
|
||||
mp.set_property("stream-open-filename", "memory://" .. playlist)
|
||||
|
||||
-- some funky guessing to detect multi-arc videos
|
||||
if not (json.entries[1]["webpage_url"] == nil)
|
||||
and (json.entries[1]["webpage_url"] == json["webpage_url"]) then
|
||||
msg.verbose("multi-arc video detected, building EDL")
|
||||
|
||||
|
||||
local playlist = "edl://"
|
||||
for i, entry in pairs(json.entries) do
|
||||
|
||||
playlist = playlist .. entry.url .. ";"
|
||||
end
|
||||
|
||||
msg.debug("EDL: " .. playlist)
|
||||
|
||||
-- can't change the http headers for each entry, so use the 1st
|
||||
if json.entries[1] then
|
||||
set_http_headers(json.entries[1].http_headers)
|
||||
end
|
||||
|
||||
mp.set_property("stream-open-filename", playlist)
|
||||
if not (json.title == nil) then
|
||||
mp.set_property("file-local-options/media-title", json.title)
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
local playlist = "#EXTM3U\n"
|
||||
for i, entry in pairs(json.entries) do
|
||||
local site = entry.url
|
||||
|
||||
-- some extractors will still return the full info for
|
||||
-- all clips in the playlist and the URL will point
|
||||
-- directly to the file in that case, which we don't
|
||||
-- want so get the webpage URL instead, which is what
|
||||
-- we want
|
||||
if not (entry["webpage_url"] == nil) then
|
||||
site = entry["webpage_url"]
|
||||
end
|
||||
|
||||
playlist = playlist .. "ytdl://" .. site .. "\n"
|
||||
end
|
||||
|
||||
mp.set_property("stream-open-filename", "memory://" .. playlist)
|
||||
end
|
||||
|
||||
else -- probably a video
|
||||
local streamurl = ""
|
||||
|
||||
-- DASH?
|
||||
if not (json["requested_formats"] == nil) then
|
||||
msg.info("NOTE: Using DASH, expect inaccurate duration.")
|
||||
msg.info("Using DASH, expect inaccurate duration.")
|
||||
if not (json.duration == nil) then
|
||||
msg.info("Actual duration: " .. mp.format_time(json.duration))
|
||||
end
|
||||
|
||||
-- video url
|
||||
streamurl = json["requested_formats"][1].url
|
||||
|
||||
@@ -135,6 +196,7 @@ mp.add_hook("on_load", 10, function ()
|
||||
elseif not (json.url == nil) then
|
||||
-- normal video
|
||||
streamurl = json.url
|
||||
set_http_headers(json.http_headers)
|
||||
else
|
||||
msg.error("No URL found in JSON data.")
|
||||
return
|
||||
|
||||
+22
-18
@@ -46,6 +46,7 @@
|
||||
#include "options/parse_commandline.h"
|
||||
#include "common/playlist.h"
|
||||
#include "options/options.h"
|
||||
#include "options/path.h"
|
||||
#include "input/input.h"
|
||||
|
||||
#include "audio/decode/dec_audio.h"
|
||||
@@ -83,6 +84,10 @@
|
||||
|
||||
static bool terminal_initialized;
|
||||
|
||||
#ifndef FULLCONFIG
|
||||
#define FULLCONFIG "(missing)\n"
|
||||
#endif
|
||||
|
||||
enum exit_reason {
|
||||
EXIT_NONE,
|
||||
EXIT_NORMAL,
|
||||
@@ -107,10 +112,15 @@ void mp_print_version(struct mp_log *log, int always)
|
||||
{
|
||||
int v = always ? MSGL_INFO : MSGL_V;
|
||||
mp_msg(log, v,
|
||||
"%s (C) 2000-2014 mpv/MPlayer/mplayer2 projects\n built on %s\n",
|
||||
"%s (C) 2000-2015 mpv/MPlayer/mplayer2 projects\n built on %s\n",
|
||||
mpv_version, mpv_builddate);
|
||||
print_libav_versions(log, v);
|
||||
mp_msg(log, v, "\n");
|
||||
// Only in verbose mode.
|
||||
if (!always) {
|
||||
mp_msg(log, MSGL_V, "Configuration: " CONFIGURATION "\n");
|
||||
mp_msg(log, MSGL_V, "config.h:\n%s\n", FULLCONFIG);
|
||||
}
|
||||
}
|
||||
|
||||
static void shutdown_clients(struct MPContext *mpctx)
|
||||
@@ -306,7 +316,10 @@ static void osdep_preinit(int *p_argc, char ***p_argv)
|
||||
static int cfg_include(void *ctx, char *filename, int flags)
|
||||
{
|
||||
struct MPContext *mpctx = ctx;
|
||||
return m_config_parse_config_file(mpctx->mconfig, filename, NULL, flags);
|
||||
char *fname = mp_get_user_path(NULL, mpctx->global, filename);
|
||||
int r = m_config_parse_config_file(mpctx->mconfig, fname, NULL, flags);
|
||||
talloc_free(fname);
|
||||
return r;
|
||||
}
|
||||
|
||||
struct MPContext *mp_create(void)
|
||||
@@ -355,7 +368,7 @@ struct MPContext *mp_create(void)
|
||||
return mpctx;
|
||||
}
|
||||
|
||||
static void wakeup_playloop(void *ctx)
|
||||
void wakeup_playloop(void *ctx)
|
||||
{
|
||||
struct MPContext *mpctx = ctx;
|
||||
mp_input_wakeup(mpctx->input);
|
||||
@@ -457,15 +470,7 @@ int mp_initialize(struct MPContext *mpctx)
|
||||
mpctx->ipc_ctx = mp_init_ipc(mpctx->clients, mpctx->global);
|
||||
#endif
|
||||
|
||||
if (opts->shuffle)
|
||||
playlist_shuffle(mpctx->playlist);
|
||||
|
||||
if (opts->merge_files)
|
||||
merge_playlist_files(mpctx->playlist);
|
||||
|
||||
mpctx->playlist->current = mp_check_playlist_resume(mpctx, mpctx->playlist);
|
||||
if (!mpctx->playlist->current)
|
||||
mpctx->playlist->current = mpctx->playlist->first;
|
||||
prepare_playlist(mpctx, mpctx->playlist);
|
||||
|
||||
MP_STATS(mpctx, "end init");
|
||||
|
||||
@@ -493,6 +498,11 @@ int mpv_main(int argc, char *argv[])
|
||||
|
||||
mp_msg_update_msglevels(mpctx->global);
|
||||
|
||||
MP_VERBOSE(mpctx, "Command line:");
|
||||
for (int i = 0; i < argc; i++)
|
||||
MP_VERBOSE(mpctx, " '%s'", argv[i]);
|
||||
MP_VERBOSE(mpctx, "\n");
|
||||
|
||||
mp_print_version(mpctx->log, false);
|
||||
|
||||
mp_parse_cfgfiles(mpctx);
|
||||
@@ -512,12 +522,6 @@ int mpv_main(int argc, char *argv[])
|
||||
if (handle_help_options(mpctx))
|
||||
exit_player(mpctx, EXIT_NONE);
|
||||
|
||||
MP_VERBOSE(mpctx, "Configuration: " CONFIGURATION "\n");
|
||||
MP_VERBOSE(mpctx, "Command line:");
|
||||
for (int i = 0; i < argc; i++)
|
||||
MP_VERBOSE(mpctx, " '%s'", argv[i]);
|
||||
MP_VERBOSE(mpctx, "\n");
|
||||
|
||||
if (!mpctx->playlist->first && !opts->player_idle_mode) {
|
||||
mp_print_version(mpctx->log, true);
|
||||
MP_INFO(mpctx, "%s", mp_help_text);
|
||||
|
||||
+12
-21
@@ -154,6 +154,7 @@ void reset_playback_state(struct MPContext *mpctx)
|
||||
mpctx->playback_pts = MP_NOPTS_VALUE;
|
||||
mpctx->last_seek_pts = MP_NOPTS_VALUE;
|
||||
mpctx->cache_wait_time = 0;
|
||||
mpctx->step_frames = 0;
|
||||
mpctx->restart_complete = false;
|
||||
|
||||
#if HAVE_ENCODING
|
||||
@@ -167,6 +168,7 @@ static int mp_seek(MPContext *mpctx, struct seek_params seek,
|
||||
{
|
||||
struct MPOpts *opts = mpctx->opts;
|
||||
uint64_t prev_seek_ts = mpctx->vo_pts_history_seek_ts;
|
||||
int prev_step = mpctx->step_frames;
|
||||
|
||||
if (!mpctx->demuxer)
|
||||
return -1;
|
||||
@@ -214,16 +216,6 @@ static int mp_seek(MPContext *mpctx, struct seek_params seek,
|
||||
bool need_reset = false;
|
||||
demuxer_amount = timeline_set_from_time(mpctx, seek.amount,
|
||||
&need_reset);
|
||||
if (demuxer_amount == MP_NOPTS_VALUE) {
|
||||
assert(!need_reset);
|
||||
mpctx->stop_play = AT_END_OF_FILE;
|
||||
// When seeking outside of the file, but not when ending last segment.
|
||||
if (!timeline_fallthrough) {
|
||||
clear_audio_output_buffers(mpctx);
|
||||
reset_playback_state(mpctx);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
if (need_reset) {
|
||||
reinit_video_chain(mpctx);
|
||||
reinit_audio_chain(mpctx);
|
||||
@@ -272,6 +264,7 @@ static int mp_seek(MPContext *mpctx, struct seek_params seek,
|
||||
if (timeline_fallthrough) {
|
||||
// Important if video reinit happens.
|
||||
mpctx->vo_pts_history_seek_ts = prev_seek_ts;
|
||||
mpctx->step_frames = prev_step;
|
||||
} else {
|
||||
mpctx->vo_pts_history_seek_ts++;
|
||||
mpctx->backstep_active = false;
|
||||
@@ -320,10 +313,6 @@ void queue_seek(struct MPContext *mpctx, enum seek_type type, double amount,
|
||||
seek->exact = exact;
|
||||
if (seek->type == MPSEEK_ABSOLUTE)
|
||||
return;
|
||||
if (seek->amount == 0) {
|
||||
*seek = (struct seek_params){ 0 };
|
||||
return;
|
||||
}
|
||||
seek->type = MPSEEK_RELATIVE;
|
||||
return;
|
||||
case MPSEEK_ABSOLUTE:
|
||||
@@ -420,9 +409,9 @@ double get_current_pos_ratio(struct MPContext *mpctx, bool use_range)
|
||||
len = endpos - startpos;
|
||||
}
|
||||
double pos = get_current_time(mpctx);
|
||||
if (len > 0 && !demuxer->ts_resets_possible) {
|
||||
if (len > 0)
|
||||
ans = MPCLAMP((pos - start) / len, 0, 1);
|
||||
} else {
|
||||
if (ans < 0 || demuxer->ts_resets_possible) {
|
||||
int64_t size;
|
||||
if (demux_stream_control(demuxer, STREAM_CTRL_GET_SIZE, &size) > 0) {
|
||||
if (size > 0 && demuxer->filepos >= 0)
|
||||
@@ -437,10 +426,11 @@ double get_current_pos_ratio(struct MPContext *mpctx, bool use_range)
|
||||
return ans;
|
||||
}
|
||||
|
||||
// 0-100, -1 if unknown
|
||||
int get_percent_pos(struct MPContext *mpctx)
|
||||
{
|
||||
int pos = get_current_pos_ratio(mpctx, false) * 100;
|
||||
return MPCLAMP(pos, 0, 100);
|
||||
double pos = get_current_pos_ratio(mpctx, false);
|
||||
return pos < 0 ? -1 : pos * 100;
|
||||
}
|
||||
|
||||
// -2 is no chapters, -1 is before first chapter
|
||||
@@ -914,8 +904,8 @@ void run_playloop(struct MPContext *mpctx)
|
||||
fill_audio_out_buffers(mpctx, endpts); // actually play prepared buffer
|
||||
if (!mpctx->restart_complete) {
|
||||
mpctx->hrseek_active = false;
|
||||
mp_notify(mpctx, MPV_EVENT_PLAYBACK_RESTART, NULL);
|
||||
mpctx->restart_complete = true;
|
||||
mp_notify(mpctx, MPV_EVENT_PLAYBACK_RESTART, NULL);
|
||||
if (!mpctx->playing_msg_shown) {
|
||||
if (opts->playing_msg) {
|
||||
char *msg =
|
||||
@@ -961,10 +951,11 @@ void run_playloop(struct MPContext *mpctx)
|
||||
mpctx->audio_status == STATUS_EOF &&
|
||||
mpctx->video_status == STATUS_EOF)
|
||||
{
|
||||
if (end_is_new_segment) {
|
||||
int new_part = mpctx->timeline_part + 1;
|
||||
if (end_is_new_segment && new_part < mpctx->num_timeline_parts) {
|
||||
mp_seek(mpctx, (struct seek_params){
|
||||
.type = MPSEEK_ABSOLUTE,
|
||||
.amount = mpctx->timeline[mpctx->timeline_part+1].start
|
||||
.amount = mpctx->timeline[new_part].start
|
||||
}, true);
|
||||
} else
|
||||
mpctx->stop_play = AT_END_OF_FILE;
|
||||
|
||||
+1
-1
@@ -129,7 +129,7 @@ static char *create_fname(struct MPContext *mpctx, char *template,
|
||||
struct tm *local_time = localtime(&raw_time);
|
||||
|
||||
if (!template || *template == '\0')
|
||||
template = "shot%n";
|
||||
return NULL;
|
||||
|
||||
for (;;) {
|
||||
char *next = strchr(template, '%');
|
||||
|
||||
+12
-13
@@ -127,8 +127,8 @@ void uninit_sub_renderer(struct MPContext *mpctx)
|
||||
static void init_sub_renderer(struct MPContext *mpctx) {}
|
||||
void uninit_sub_renderer(struct MPContext *mpctx) {}
|
||||
|
||||
void mp_ass_configure(ASS_Renderer *priv, struct MPOpts *opts,
|
||||
struct mp_osd_res *dim) {}
|
||||
void mp_ass_configure_fonts(struct ass_renderer *a, struct MPOpts *b,
|
||||
struct mpv_global *c, struct mp_log *d) {}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -146,10 +146,9 @@ void uninit_stream_sub_decoders(struct demuxer *demuxer)
|
||||
void uninit_sub(struct MPContext *mpctx, int order)
|
||||
{
|
||||
if (mpctx->d_sub[order]) {
|
||||
mpctx->d_sub[order] = NULL; // Note: not free'd.
|
||||
int obj = order ? OSDTYPE_SUB2 : OSDTYPE_SUB;
|
||||
osd_set_sub(mpctx->osd, obj, NULL);
|
||||
reset_subtitles(mpctx, order);
|
||||
mpctx->d_sub[order] = NULL; // Note: not free'd.
|
||||
update_osd_sub_state(mpctx, order, NULL); // unset
|
||||
reselect_demux_streams(mpctx);
|
||||
}
|
||||
}
|
||||
@@ -193,24 +192,25 @@ void reset_subtitle_state(struct MPContext *mpctx)
|
||||
reset_subtitles(mpctx, 1);
|
||||
}
|
||||
|
||||
void get_osd_sub_state(struct MPContext *mpctx, int order,
|
||||
struct osd_sub_state *out_state)
|
||||
void update_osd_sub_state(struct MPContext *mpctx, int order,
|
||||
struct osd_sub_state *out_state)
|
||||
{
|
||||
struct MPOpts *opts = mpctx->opts;
|
||||
struct track *track = mpctx->current_track[order][STREAM_SUB];
|
||||
struct dec_sub *dec_sub = mpctx->d_sub[order];
|
||||
int obj = order ? OSDTYPE_SUB2 : OSDTYPE_SUB;
|
||||
bool textsub = dec_sub && sub_has_get_text(dec_sub);
|
||||
|
||||
struct osd_sub_state state = {
|
||||
.dec_sub = dec_sub,
|
||||
// Decides whether to use OSD path or normal subtitle rendering path.
|
||||
.render_bitmap_subs = opts->ass_enabled || !sub_has_get_text(dec_sub),
|
||||
.render_bitmap_subs = opts->ass_enabled || !textsub,
|
||||
.video_offset = get_track_video_offset(mpctx, track),
|
||||
};
|
||||
|
||||
// Secondary subs are rendered with the "text" renderer to transform them
|
||||
// to toptitles.
|
||||
if (order == 1 && sub_has_get_text(dec_sub))
|
||||
if (order == 1 && textsub)
|
||||
state.render_bitmap_subs = false;
|
||||
|
||||
if (!mpctx->current_track[0][STREAM_VIDEO])
|
||||
@@ -227,10 +227,9 @@ static void update_subtitle(struct MPContext *mpctx, int order)
|
||||
struct track *track = mpctx->current_track[order][STREAM_SUB];
|
||||
struct dec_sub *dec_sub = mpctx->d_sub[order];
|
||||
|
||||
if (!track)
|
||||
if (!track || !dec_sub)
|
||||
return;
|
||||
|
||||
assert(dec_sub);
|
||||
int obj = order ? OSDTYPE_SUB2 : OSDTYPE_SUB;
|
||||
|
||||
if (mpctx->d_video) {
|
||||
@@ -240,7 +239,7 @@ static void update_subtitle(struct MPContext *mpctx, int order)
|
||||
}
|
||||
|
||||
struct osd_sub_state state;
|
||||
get_osd_sub_state(mpctx, order, &state);
|
||||
update_osd_sub_state(mpctx, order, &state);
|
||||
|
||||
double refpts_s = mpctx->playback_pts - state.video_offset;
|
||||
double curpts_s = refpts_s - opts->sub_delay;
|
||||
@@ -344,5 +343,5 @@ void reinit_subs(struct MPContext *mpctx, int order)
|
||||
struct dec_sub *dec_sub = mpctx->d_sub[order];
|
||||
reinit_subdec(mpctx, track, dec_sub);
|
||||
|
||||
get_osd_sub_state(mpctx, order, NULL);
|
||||
update_osd_sub_state(mpctx, order, NULL);
|
||||
}
|
||||
@@ -381,6 +381,7 @@ static void build_timeline_loop(struct MPContext *mpctx,
|
||||
uint64_t *last_end_time,
|
||||
struct timeline_part **timeline,
|
||||
struct demux_chapter *chapters,
|
||||
int num_chapters,
|
||||
int *part_count,
|
||||
uint64_t skip,
|
||||
uint64_t limit)
|
||||
@@ -418,6 +419,10 @@ static void build_timeline_loop(struct MPContext *mpctx,
|
||||
* recursive ordered chapter editions? If so, more code will be
|
||||
* needed to add chapters for external non-ordered segment loading
|
||||
* as well since that part is not recursive. */
|
||||
|
||||
if (i >= num_chapters)
|
||||
break; // probably needed only for broken sources
|
||||
|
||||
if (!limit) {
|
||||
chapters[i].pts = *starttime / 1e9;
|
||||
chapters[i].name = talloc_strdup(chapters, c->name);
|
||||
@@ -477,11 +482,9 @@ static void build_timeline_loop(struct MPContext *mpctx,
|
||||
} else {
|
||||
build_timeline_loop(mpctx, sources, num_sources, j, starttime,
|
||||
missing_time, last_end_time, timeline,
|
||||
chapters, part_count, c->start, c->end);
|
||||
/* The loop call has added time as needed (we can't add it here
|
||||
* due to 'join_diff' in the add_timeline_part function. Since
|
||||
* the time has already been added as needed, the chapter has
|
||||
* an effective 0 length at this point. */
|
||||
chapters, num_chapters, part_count,
|
||||
c->start, c->end);
|
||||
// Already handled by the loop call.
|
||||
chapter_length = 0;
|
||||
}
|
||||
*last_end_time = c->end;
|
||||
@@ -562,20 +565,31 @@ void build_ordered_chapter_timeline(struct MPContext *mpctx)
|
||||
struct timeline_part *timeline = talloc_array_ptrtype(NULL, timeline, 0);
|
||||
struct demux_chapter *chapters =
|
||||
talloc_zero_array(NULL, struct demux_chapter, m->num_ordered_chapters);
|
||||
// Stupid hack, because fuck everything.
|
||||
for (int n = 0; n < m->num_ordered_chapters; n++)
|
||||
chapters[n].pts = -1;
|
||||
uint64_t starttime = 0;
|
||||
uint64_t missing_time = 0;
|
||||
uint64_t last_end_time = 0;
|
||||
int part_count = 0;
|
||||
build_timeline_loop(mpctx, sources, num_sources, 0, &starttime,
|
||||
&missing_time, &last_end_time, &timeline,
|
||||
chapters, &part_count, 0, 0);
|
||||
chapters, m->num_ordered_chapters, &part_count, 0, 0);
|
||||
|
||||
// Fuck everything (2): filter out all "unset" chapters.
|
||||
for (int n = m->num_ordered_chapters - 1; n >= 0; n--) {
|
||||
if (chapters[n].pts == -1)
|
||||
MP_TARRAY_REMOVE_AT(chapters, m->num_ordered_chapters, n);
|
||||
}
|
||||
|
||||
if (!part_count) {
|
||||
// None of the parts come from the file itself???
|
||||
talloc_free(sources);
|
||||
talloc_free(timeline);
|
||||
talloc_free(chapters);
|
||||
return;
|
||||
// None of the parts come from the file itself???
|
||||
// Broken file, but we need at least 1 valid timeline part - add a dummy.
|
||||
MP_WARN(mpctx, "Ordered chapters file with no parts?\n");
|
||||
struct timeline_part new = {
|
||||
.source = demuxer,
|
||||
};
|
||||
MP_TARRAY_APPEND(NULL, timeline, part_count, new);
|
||||
}
|
||||
|
||||
struct timeline_part new = {
|
||||
|
||||
+38
-15
@@ -66,7 +66,7 @@ static const char av_desync_help_text[] =
|
||||
"This means either the audio or the video is played too slowly.\n"
|
||||
"Possible reasons, problems, workarounds:\n"
|
||||
"- Your system is simply too slow for this file.\n"
|
||||
" Transcode it to a lower bitrate file with tools like HandBrake.\n"
|
||||
" Transcode it to a lower bitrate file with e.g. mpv encoding support.\n"
|
||||
"- Slow video output.\n"
|
||||
" Try a different --vo driver (--vo=help for a list). Make sure framedrop\n"
|
||||
" is not disabled, or experiment with different values for --framedrop.\n"
|
||||
@@ -74,7 +74,7 @@ static const char av_desync_help_text[] =
|
||||
" Download the file instead.\n"
|
||||
"- Try to find out whether audio/video/subs are causing this by experimenting\n"
|
||||
" with --no-video, --no-audio, or --no-sub.\n"
|
||||
"- If you swiched audio or video tracks, try seeking to force synchronization.\n"
|
||||
"- If you switched audio or video tracks, try seeking to force synchronization.\n"
|
||||
"If none of this helps you, file a bug report.\n\n";
|
||||
|
||||
void update_fps(struct MPContext *mpctx)
|
||||
@@ -178,6 +178,9 @@ static void recreate_video_filters(struct MPContext *mpctx)
|
||||
vf_destroy(d_video->vfilter);
|
||||
d_video->vfilter = vf_new(mpctx->global);
|
||||
d_video->vfilter->hwdec = d_video->hwdec_info;
|
||||
d_video->vfilter->wakeup_callback = wakeup_playloop;
|
||||
d_video->vfilter->wakeup_callback_ctx = mpctx;
|
||||
d_video->vfilter->container_fps = d_video->fps;
|
||||
|
||||
vf_append_filter_list(d_video->vfilter, opts->vf_settings);
|
||||
|
||||
@@ -287,6 +290,13 @@ int reinit_video_chain(struct MPContext *mpctx)
|
||||
d_video->fps = sh->video->fps;
|
||||
d_video->vo = mpctx->video_out;
|
||||
|
||||
if (opts->force_fps) {
|
||||
d_video->fps = opts->force_fps;
|
||||
MP_INFO(mpctx, "FPS forced to %5.3f.\n", d_video->fps);
|
||||
MP_INFO(mpctx, "Use --no-correct-pts to force FPS based timing.\n");
|
||||
}
|
||||
update_fps(mpctx);
|
||||
|
||||
vo_control(mpctx->video_out, VOCTRL_GET_HWDEC_INFO, &d_video->hwdec_info);
|
||||
|
||||
recreate_video_filters(mpctx);
|
||||
@@ -310,16 +320,6 @@ int reinit_video_chain(struct MPContext *mpctx)
|
||||
reset_video_state(mpctx);
|
||||
reset_subtitle_state(mpctx);
|
||||
|
||||
if (opts->force_fps) {
|
||||
d_video->fps = opts->force_fps;
|
||||
MP_INFO(mpctx, "FPS forced to be %5.3f.\n", d_video->fps);
|
||||
}
|
||||
if (!sh->video->fps && !opts->force_fps && !opts->correct_pts) {
|
||||
MP_ERR(mpctx, "FPS not specified in the "
|
||||
"header or invalid, use the -fps option.\n");
|
||||
}
|
||||
update_fps(mpctx);
|
||||
|
||||
return 1;
|
||||
|
||||
err_out:
|
||||
@@ -380,6 +380,8 @@ static int decode_image(struct MPContext *mpctx)
|
||||
return VD_WAIT;
|
||||
if (pkt && pkt->pts != MP_NOPTS_VALUE)
|
||||
pkt->pts += mpctx->video_offset;
|
||||
if (pkt && pkt->dts != MP_NOPTS_VALUE)
|
||||
pkt->dts += mpctx->video_offset;
|
||||
if ((pkt && pkt->pts >= mpctx->hrseek_pts - .005) ||
|
||||
d_video->has_broken_packet_pts ||
|
||||
!mpctx->opts->hr_seek_framedrop)
|
||||
@@ -430,7 +432,8 @@ static int video_filter(struct MPContext *mpctx, bool eof)
|
||||
return VD_ERROR;
|
||||
|
||||
// There is already a filtered frame available.
|
||||
if (vf_output_frame(vf, eof) > 0)
|
||||
// If vf_needs_input() returns > 0, the filter wants input anyway.
|
||||
if (vf_output_frame(vf, eof) > 0 && vf_needs_input(vf) < 1)
|
||||
return VD_PROGRESS;
|
||||
|
||||
// Decoder output is different from filter input?
|
||||
@@ -491,6 +494,20 @@ static int video_decode_and_filter(struct MPContext *mpctx)
|
||||
return r;
|
||||
}
|
||||
|
||||
static int video_feed_async_filter(struct MPContext *mpctx)
|
||||
{
|
||||
struct dec_video *d_video = mpctx->d_video;
|
||||
struct vf_chain *vf = d_video->vfilter;
|
||||
|
||||
if (vf->initialized < 0)
|
||||
return VD_ERROR;
|
||||
|
||||
if (vf_needs_input(vf) < 1)
|
||||
return 0;
|
||||
mpctx->sleeptime = 0; // retry until done
|
||||
return video_decode_and_filter(mpctx);
|
||||
}
|
||||
|
||||
/* Modify video timing to match the audio timeline. There are two main
|
||||
* reasons this is needed. First, video and audio can start from different
|
||||
* positions at beginning of file or after a seek (MPlayer starts both
|
||||
@@ -777,8 +794,12 @@ void write_video(struct MPContext *mpctx, double endpts)
|
||||
double time_frame = MPMAX(mpctx->time_frame, -1);
|
||||
int64_t pts = mp_time_us() + (int64_t)(time_frame * 1e6);
|
||||
|
||||
if (!vo_is_ready_for_frame(vo, pts))
|
||||
return; // wait until VO wakes us up to get more frames
|
||||
// wait until VO wakes us up to get more frames
|
||||
if (!vo_is_ready_for_frame(vo, pts)) {
|
||||
if (video_feed_async_filter(mpctx) < 0)
|
||||
goto error;
|
||||
return;
|
||||
}
|
||||
|
||||
int64_t duration = -1;
|
||||
double diff = -1;
|
||||
@@ -788,6 +809,8 @@ void write_video(struct MPContext *mpctx, double endpts)
|
||||
diff = vpts1 - vpts0;
|
||||
if (diff < 0 && mpctx->d_video->fps > 0)
|
||||
diff = 1.0 / mpctx->d_video->fps; // fallback to demuxer-reported fps
|
||||
if (opts->untimed || vo->driver->untimed)
|
||||
diff = -1; // disable frame dropping and aspects of frame timing
|
||||
if (diff >= 0) {
|
||||
// expected A/V sync correction is ignored
|
||||
diff /= opts->playback_speed;
|
||||
|
||||
Loaded 100 of 160 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user